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