CodeGenerator update.
[controller.git] / opendaylight / sal / yang-prototype / code-generator / maven-yang-plugin / src / main / java / org / opendaylight / controller / yang2sources / plugin / YangToSourcesMojo.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.yang2sources.plugin;
9
10 import java.io.Closeable;
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.FileOutputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.Enumeration;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.zip.ZipEntry;
26 import java.util.zip.ZipFile;
27
28 import org.apache.commons.io.IOUtils;
29 import org.apache.maven.model.Resource;
30 import org.apache.maven.plugin.AbstractMojo;
31 import org.apache.maven.plugin.MojoExecutionException;
32 import org.apache.maven.plugin.MojoFailureException;
33 import org.apache.maven.plugins.annotations.LifecyclePhase;
34 import org.apache.maven.plugins.annotations.Mojo;
35 import org.apache.maven.plugins.annotations.Parameter;
36 import org.apache.maven.plugins.annotations.ResolutionScope;
37 import org.apache.maven.project.MavenProject;
38 import org.opendaylight.controller.yang.model.api.Module;
39 import org.opendaylight.controller.yang.model.api.SchemaContext;
40 import org.opendaylight.controller.yang.model.parser.api.YangModelParser;
41 import org.opendaylight.controller.yang.parser.impl.YangParserImpl;
42 import org.opendaylight.controller.yang2sources.plugin.ConfigArg.CodeGeneratorArg;
43 import org.opendaylight.controller.yang2sources.plugin.ConfigArg.ResourceProviderArg;
44 import org.opendaylight.controller.yang2sources.spi.CodeGenerator;
45 import org.opendaylight.controller.yang2sources.spi.ResourceGenerator;
46
47 import com.google.common.annotations.VisibleForTesting;
48 import com.google.common.collect.Lists;
49 import com.google.common.collect.Maps;
50 import com.google.common.io.Files;
51
52 /**
53  * Generate sources from yang files using user provided set of
54  * {@link CodeGenerator}s. Steps of this process:
55  * <ol>
56  * <li>List yang files from {@link #yangFilesRootDir}</li>
57  * <li>Process yang files using {@link YangModelParserImpl}</li>
58  * <li>For each {@link CodeGenerator} from {@link #codeGenerators}:</li>
59  * <ol>
60  * <li>Instantiate using default constructor</li>
61  * <li>Call {@link CodeGenerator#generateSources(SchemaContext, File)}</li>
62  * </ol>
63  * </ol>
64  */
65 @Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE, requiresProject = true)
66 public final class YangToSourcesMojo extends AbstractMojo {
67     private static final String LOG_PREFIX = "yang-to-sources:";
68     private static final String INPUT_RESOURCE_DIR = "META-INF/yang/";
69     private static final String OUTPUT_RESOURCE_DIR = "/target/external-resources/";
70
71     /**
72      * Classes implementing {@link CodeGenerator} interface. An instance will be
73      * created out of every class using default constructor. Method {@link
74      * CodeGenerator#generateSources(SchemaContext, File, Set<String>
75      * yangModulesNames)} will be called on every instance.
76      */
77     @Parameter(required = true)
78     private CodeGeneratorArg[] codeGenerators;
79
80     /**
81      * Source directory that will be recursively searched for yang files (ending
82      * with .yang suffix).
83      */
84     @Parameter(required = true)
85     private String yangFilesRootDir;
86
87     /**
88      * Classes implementing {@link ResourceGenerator} interface. An instance
89      * will be created out of every class using default constructor. Method
90      * {@link ResourceGenerator#generateResourceFiles(Collection, File)} will be
91      * called on every instance.
92      */
93     @Parameter(required = true)
94     private ResourceProviderArg[] resourceProviders;
95
96     @Parameter(property = "project", required = true, readonly = true)
97     protected MavenProject project;
98
99     private transient final YangModelParser parser;
100
101     @VisibleForTesting
102     YangToSourcesMojo(ResourceProviderArg[] resourceProviderArgs,
103             CodeGeneratorArg[] codeGeneratorArgs, YangModelParser parser,
104             String yangFilesRootDir) {
105         super();
106         this.resourceProviders = resourceProviderArgs;
107         this.codeGenerators = codeGeneratorArgs;
108         this.yangFilesRootDir = yangFilesRootDir;
109         this.parser = parser;
110     }
111
112     public YangToSourcesMojo() {
113         super();
114         parser = new YangParserImpl();
115     }
116
117     @Override
118     public void execute() throws MojoExecutionException, MojoFailureException {
119         ContextHolder context = processYang();
120         generateSources(context);
121         generateResources();
122
123         closeResources();
124     }
125
126     /**
127      * Generate {@link SchemaContext} with {@link YangModelParserImpl}
128      */
129     private ContextHolder processYang() throws MojoExecutionException {
130         try {
131             List<InputStream> yangFiles = Util
132                     .listFilesAsStream(yangFilesRootDir);
133             Set<Module> yangModules = parser
134                     .parseYangModelsFromStreams(yangFiles);
135
136             List<InputStream> yangFilesFromDependencies = getFilesFromDependenciesAsStream();
137             Set<Module> yangModulesFromDependencies = parser
138                     .parseYangModelsFromStreams(yangFilesFromDependencies);
139
140             Set<Module> parsedYang = new HashSet<Module>(yangModules);
141             parsedYang.addAll(yangModulesFromDependencies);
142
143             if (yangFiles.isEmpty() && yangFilesFromDependencies.isEmpty()) {
144                 getLog().warn(
145                         Util.message(
146                                 "No %s file found in %s or in dependencies",
147                                 LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir));
148                 Set<Module> modules = Collections.emptySet();
149                 return new ContextHolder(null, modules);
150             }
151
152             SchemaContext resolveSchemaContext = parser
153                     .resolveSchemaContext(parsedYang);
154             getLog().info(
155                     Util.message("%s files parsed from %s", LOG_PREFIX,
156                             Util.YANG_SUFFIX, yangFiles));
157             return new ContextHolder(resolveSchemaContext, yangModules);
158
159             // MojoExecutionException is thrown since execution cannot continue
160         } catch (Exception e) {
161             String message = Util.message("Unable to parse %s files from %s",
162                     LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir);
163             getLog().error(message, e);
164             throw new MojoExecutionException(message, e);
165         }
166     }
167
168     private void generateResources() throws MojoExecutionException,
169             MojoFailureException {
170         if (resourceProviders.length == 0) {
171             getLog().warn(
172                     Util.message("No resource provider classes provided",
173                             LOG_PREFIX));
174             return;
175         }
176
177         Resource res = new Resource();
178         String baseDirName = project.getBasedir().getAbsolutePath();
179         res.setDirectory(baseDirName + OUTPUT_RESOURCE_DIR);
180         res.setTargetPath(INPUT_RESOURCE_DIR);
181         project.addResource(res);
182
183         Map<String, String> thrown = Maps.newHashMap();
184
185         Collection<File> yangFiles = new ArrayList<File>();
186
187         // load files from yang root
188         yangFiles.addAll(getFilesFromYangRoot());
189
190         // load files from dependencies
191         Collection<File> filesFromDependencies = getFilesFromDependencies();
192         yangFiles.addAll(filesFromDependencies);
193
194         for (ResourceProviderArg resourceProvider : resourceProviders) {
195             try {
196                 provideResourcesWithOneProvider(yangFiles, resourceProvider);
197             } catch (Exception e) {
198                 // try other generators, exception will be thrown after
199                 getLog().error(
200                         Util.message(
201                                 "Unable to provide resources with %s resource provider",
202                                 LOG_PREFIX,
203                                 resourceProvider.getResourceProviderClass()), e);
204                 thrown.put(resourceProvider.getResourceProviderClass(), e
205                         .getClass().getCanonicalName());
206             }
207         }
208
209         if (!thrown.isEmpty()) {
210             String message = Util
211                     .message(
212                             "One or more code resource provider failed, including failed list(resourceProviderClass=exception) %s",
213                             LOG_PREFIX, thrown.toString());
214             getLog().error(message);
215             throw new MojoFailureException(message);
216         }
217     }
218
219     private Collection<File> getFilesFromYangRoot() {
220         Collection<File> yangFilesLoaded = null;
221
222         File rootDir = new File(yangFilesRootDir);
223         try {
224             if (rootDir.isAbsolute()) {
225                 yangFilesLoaded = Util.listFiles(yangFilesRootDir);
226             } else {
227                 String path = project.getBasedir().getAbsolutePath()
228                         + File.separator + yangFilesRootDir;
229                 yangFilesLoaded = Util.listFiles(path);
230             }
231         } catch (FileNotFoundException e) {
232             getLog().warn(
233                     "yangFilesRootDir[" + rootDir.getAbsolutePath()
234                             + "] does not exists.");
235             yangFilesLoaded = new ArrayList<File>();
236         }
237
238         Collection<File> yangFiles = new ArrayList<File>(yangFilesLoaded);
239
240         try {
241             for (File yangFile : yangFilesLoaded) {
242                 InputStream is = new FileInputStream(yangFile);
243                 yangFiles.add(createFileFromStream(is,
244                         project.getBasedir().getAbsolutePath()
245                                 + OUTPUT_RESOURCE_DIR + yangFile.getName()));
246                 resources.add(is);
247             }
248         } catch (IOException e) {
249             getLog().warn("Exception while loading yang files.", e);
250         }
251         return yangFiles;
252     }
253
254     private Collection<File> getFilesFromDependencies() {
255         Collection<File> yangFiles = new ArrayList<File>();
256
257         try {
258             List<File> filesOnCp = Util.getClassPath(project);
259             List<String> filter = Lists.newArrayList(".yang");
260             for (File file : filesOnCp) {
261                 ZipFile zip = new ZipFile(file);
262                 Enumeration<? extends ZipEntry> entries = zip.entries();
263
264                 while (entries.hasMoreElements()) {
265                     ZipEntry entry = entries.nextElement();
266                     String entryName = entry.getName();
267                     if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
268                         if (entry.isDirectory()) {
269                             continue;
270                         }
271                         if (!Util.acceptedFilter(entryName, filter)) {
272                             continue;
273                         }
274                         InputStream entryStream = zip.getInputStream(entry);
275                         String newEntryName = entryName
276                                 .substring(INPUT_RESOURCE_DIR.length());
277                         File tmp = Files.createTempDir();
278                         File f = createFileFromStream(entryStream,
279                                 tmp.getAbsolutePath() + newEntryName);
280                         yangFiles.add(f);
281
282                         resources.add(entryStream);
283                     }
284                 }
285
286                 resources.add(zip);
287             }
288         } catch (Exception e) {
289             getLog().warn("Exception while loading external yang files.", e);
290         }
291         return yangFiles;
292     }
293
294     private File createFileFromStream(InputStream is, String absoluteName)
295             throws IOException {
296         File f = new File(absoluteName);
297         if (!f.exists()) {
298             f.getParentFile().mkdirs();
299         }
300         f.createNewFile();
301
302         FileOutputStream fos = new FileOutputStream(f);
303         IOUtils.copy(is, fos);
304         return f;
305     }
306
307     /**
308      * Instantiate provider from class and call required method
309      */
310     private void provideResourcesWithOneProvider(Collection<File> yangFiles,
311             ResourceProviderArg resourceProvider)
312             throws ClassNotFoundException, InstantiationException,
313             IllegalAccessException {
314
315         resourceProvider.check();
316
317         ResourceGenerator g = Util.getInstance(
318                 resourceProvider.getResourceProviderClass(),
319                 ResourceGenerator.class);
320         getLog().info(
321                 Util.message("Resource provider instantiated from %s",
322                         LOG_PREFIX, resourceProvider.getResourceProviderClass()));
323
324         g.generateResourceFiles(yangFiles, resourceProvider.getOutputBaseDir());
325         getLog().info(
326                 Util.message("Resource provider %s call successful",
327                         LOG_PREFIX, resourceProvider.getResourceProviderClass()));
328     }
329
330     /**
331      * Call generate on every generator from plugin configuration
332      */
333     private void generateSources(ContextHolder context)
334             throws MojoFailureException {
335         if (codeGenerators.length == 0) {
336             getLog().warn(
337                     Util.message("No code generators provided", LOG_PREFIX));
338             return;
339         }
340
341         Map<String, String> thrown = Maps.newHashMap();
342         for (CodeGeneratorArg codeGenerator : codeGenerators) {
343             try {
344                 generateSourcesWithOneGenerator(context, codeGenerator);
345             } catch (Exception e) {
346                 // try other generators, exception will be thrown after
347                 getLog().error(
348                         Util.message(
349                                 "Unable to generate sources with %s generator",
350                                 LOG_PREFIX,
351                                 codeGenerator.getCodeGeneratorClass()), e);
352                 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass()
353                         .getCanonicalName());
354             }
355         }
356
357         if (!thrown.isEmpty()) {
358             String message = Util
359                     .message(
360                             "One or more code generators failed, including failed list(generatorClass=exception) %s",
361                             LOG_PREFIX, thrown.toString());
362             getLog().error(message);
363             throw new MojoFailureException(message);
364         }
365     }
366
367     /**
368      * Instantiate generator from class and call required method
369      */
370     private void generateSourcesWithOneGenerator(ContextHolder context,
371             CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
372             InstantiationException, IllegalAccessException, IOException {
373
374         codeGeneratorCfg.check();
375
376         CodeGenerator g = Util.getInstance(
377                 codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
378         getLog().info(
379                 Util.message("Code generator instantiated from %s", LOG_PREFIX,
380                         codeGeneratorCfg.getCodeGeneratorClass()));
381
382         File outputDir = codeGeneratorCfg.getOutputBaseDir();
383         if (project != null && outputDir != null) {
384             project.addCompileSourceRoot(outputDir.getPath());
385         }
386         Collection<File> generated = g.generateSources(context.getContext(),
387                 outputDir, context.getYangModules());
388         getLog().info(
389                 Util.message("Sources generated by %s: %s", LOG_PREFIX,
390                         codeGeneratorCfg.getCodeGeneratorClass(), generated));
391     }
392
393     /**
394      * Collection of resources which should be closed after use.
395      */
396     private final List<Closeable> resources = new ArrayList<Closeable>();
397
398     /**
399      * Search for yang files in dependent projects.
400      *
401      * @return files found as List of InputStream
402      */
403     private List<InputStream> getFilesFromDependenciesAsStream() {
404         final List<InputStream> yangsFromDependencies = new ArrayList<InputStream>();
405         try {
406             List<File> filesOnCp = Util.getClassPath(project);
407
408             List<String> filter = Lists.newArrayList(".yang");
409             for (File file : filesOnCp) {
410                 ZipFile zip = new ZipFile(file);
411                 Enumeration<? extends ZipEntry> entries = zip.entries();
412                 while (entries.hasMoreElements()) {
413                     ZipEntry entry = entries.nextElement();
414                     String entryName = entry.getName();
415
416                     if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
417                         if (entry.isDirectory()) {
418                             continue;
419                         }
420                         if (!Util.acceptedFilter(entryName, filter)) {
421                             continue;
422                         }
423
424                         InputStream entryStream = zip.getInputStream(entry);
425                         yangsFromDependencies.add(entryStream);
426                         resources.add(entryStream);
427                     }
428
429                 }
430                 resources.add(zip);
431             }
432         } catch (Exception e) {
433             getLog().warn("Exception while searching yangs in dependencies", e);
434         }
435         return yangsFromDependencies;
436     }
437
438     /**
439      * Internal utility method for closing open resources.
440      */
441     private void closeResources() {
442         for (Closeable resource : resources) {
443             try {
444                 resource.close();
445             } catch (IOException e) {
446                 getLog().warn("Failed to close resources: " + resource, e);
447             }
448         }
449     }
450
451     private class ContextHolder {
452         private final SchemaContext context;
453         private final Set<Module> yangModules;
454
455         private ContextHolder(SchemaContext context,
456                 Set<Module> yangModules) {
457             this.context = context;
458             this.yangModules = yangModules;
459         }
460
461         public SchemaContext getContext() {
462             return context;
463         }
464
465         public Set<Module> getYangModules() {
466             return yangModules;
467         }
468     }
469
470 }