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