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