2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.yang2sources.plugin;
10 import java.io.Closeable;
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;
23 import java.util.zip.ZipEntry;
24 import java.util.zip.ZipFile;
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;
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;
51 * Generate sources from yang files using user provided set of
52 * {@link CodeGenerator}s. Steps of this process:
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>
58 * <li>Instantiate using default constructor</li>
59 * <li>Call {@link CodeGenerator#generateSources(SchemaContext, File)}</li>
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/";
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
75 @Parameter(required = true)
76 private CodeGeneratorArg[] codeGenerators;
79 * Source directory that will be recursively searched for yang files (ending
82 @Parameter(required = true)
83 private String yangFilesRootDir;
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.
91 @Parameter(required = true)
92 private ResourceProviderArg[] resourceProviders;
94 @Parameter(property = "project", required = true, readonly = true)
95 protected MavenProject project;
97 private transient final YangModelParser parser;
100 YangToSourcesMojo(ResourceProviderArg[] resourceProviderArgs,
101 CodeGeneratorArg[] codeGeneratorArgs, YangModelParser parser,
102 String yangFilesRootDir) {
104 this.resourceProviders = resourceProviderArgs;
105 this.codeGenerators = codeGeneratorArgs;
106 this.yangFilesRootDir = yangFilesRootDir;
107 this.parser = parser;
110 public YangToSourcesMojo() {
112 parser = new YangParserImpl();
116 public void execute() throws MojoExecutionException, MojoFailureException {
117 SchemaContext context = processYang();
118 generateSources(context);
125 * Generate {@link SchemaContext} with {@link YangModelParserImpl}
127 private SchemaContext processYang() throws MojoExecutionException {
129 Collection<InputStream> yangFiles = Util
130 .listFilesAsStream(yangFilesRootDir);
131 yangFiles.addAll(getFilesFromDependenciesAsStream());
133 if (yangFiles.isEmpty()) {
136 "No %s file found in %s or in dependencies",
137 LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir));
141 Set<Module> parsedYang = parser
142 .parseYangModelsFromStreams(new ArrayList<InputStream>(
144 SchemaContext resolveSchemaContext = parser
145 .resolveSchemaContext(parsedYang);
147 Util.message("%s files parsed from %s", LOG_PREFIX,
148 Util.YANG_SUFFIX, yangFiles));
149 return resolveSchemaContext;
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);
160 private void generateResources() throws MojoExecutionException,
161 MojoFailureException {
162 if (resourceProviders.length == 0) {
164 Util.message("No resource provider classes provided",
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);
175 Map<String, String> thrown = Maps.newHashMap();
177 Collection<File> yangFiles = new ArrayList<File>();
179 // load files from yang root
180 yangFiles.addAll(getFilesFromYangRoot());
182 // load files from dependencies
183 Collection<File> filesFromDependencies = getFilesFromDependencies();
184 yangFiles.addAll(filesFromDependencies);
186 for (ResourceProviderArg resourceProvider : resourceProviders) {
188 provideResourcesWithOneProvider(yangFiles, resourceProvider);
189 } catch (Exception e) {
190 // try other generators, exception will be thrown after
193 "Unable to provide resources with %s resource provider",
195 resourceProvider.getResourceProviderClass()), e);
196 thrown.put(resourceProvider.getResourceProviderClass(), e
197 .getClass().getCanonicalName());
201 if (!thrown.isEmpty()) {
202 String message = Util
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);
211 private Collection<File> getFilesFromYangRoot() {
212 Collection<File> yangFilesLoaded = null;
214 File rootDir = new File(yangFilesRootDir);
216 if (rootDir.isAbsolute()) {
217 yangFilesLoaded = Util.listFiles(yangFilesRootDir);
219 String path = project.getBasedir().getAbsolutePath()
220 + File.separator + yangFilesRootDir;
221 yangFilesLoaded = Util.listFiles(path);
223 } catch (FileNotFoundException e) {
225 "yangFilesRootDir[" + rootDir.getAbsolutePath()
226 + "] does not exists.");
227 yangFilesLoaded = new ArrayList<File>();
230 Collection<File> yangFiles = new ArrayList<File>(yangFilesLoaded);
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()));
240 } catch (IOException e) {
241 getLog().warn("Exception while loading yang files.", e);
246 private Collection<File> getFilesFromDependencies() {
247 Collection<File> yangFiles = new ArrayList<File>();
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();
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()) {
263 if (!Util.acceptedFilter(entryName, filter)) {
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);
274 resources.add(entryStream);
280 } catch (Exception e) {
281 getLog().warn("Exception while loading external yang files.", e);
286 private File createFileFromStream(InputStream is, String absoluteName)
288 File f = new File(absoluteName);
290 f.getParentFile().mkdirs();
294 FileOutputStream fos = new FileOutputStream(f);
295 IOUtils.copy(is, fos);
300 * Instantiate provider from class and call required method
302 private void provideResourcesWithOneProvider(Collection<File> yangFiles,
303 ResourceProviderArg resourceProvider)
304 throws ClassNotFoundException, InstantiationException,
305 IllegalAccessException {
307 resourceProvider.check();
309 ResourceGenerator g = Util.getInstance(
310 resourceProvider.getResourceProviderClass(),
311 ResourceGenerator.class);
313 Util.message("Resource provider instantiated from %s",
314 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
316 g.generateResourceFiles(yangFiles, resourceProvider.getOutputBaseDir());
318 Util.message("Resource provider %s call successful",
319 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
323 * Call generate on every generator from plugin configuration
325 private void generateSources(SchemaContext context)
326 throws MojoFailureException {
327 if (codeGenerators.length == 0) {
329 Util.message("No code generators provided", LOG_PREFIX));
333 Map<String, String> thrown = Maps.newHashMap();
334 for (CodeGeneratorArg codeGenerator : codeGenerators) {
336 generateSourcesWithOneGenerator(context, codeGenerator);
337 } catch (Exception e) {
338 // try other generators, exception will be thrown after
341 "Unable to generate sources with %s generator",
343 codeGenerator.getCodeGeneratorClass()), e);
344 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass()
345 .getCanonicalName());
349 if (!thrown.isEmpty()) {
350 String message = Util
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);
360 * Instantiate generator from class and call required method
362 private void generateSourcesWithOneGenerator(SchemaContext context,
363 CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
364 InstantiationException, IllegalAccessException, IOException {
366 codeGeneratorCfg.check();
368 CodeGenerator g = Util.getInstance(
369 codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
371 Util.message("Code generator instantiated from %s", LOG_PREFIX,
372 codeGeneratorCfg.getCodeGeneratorClass()));
374 File outputDir = codeGeneratorCfg.getOutputBaseDir();
375 if (project != null && outputDir != null) {
376 project.addCompileSourceRoot(outputDir.getPath());
378 Collection<File> generated = g.generateSources(context, outputDir);
380 Util.message("Sources generated by %s: %s", LOG_PREFIX,
381 codeGeneratorCfg.getCodeGeneratorClass(), generated));
385 * Collection of resources which should be closed after use.
387 private final List<Closeable> resources = new ArrayList<Closeable>();
390 * Search for yang files in dependent projects.
392 * @return files found as List of InputStream
394 private List<InputStream> getFilesFromDependenciesAsStream() {
395 final List<InputStream> yangsFromDependencies = new ArrayList<InputStream>();
397 List<File> filesOnCp = Util.getClassPath(project);
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();
407 if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
408 if (entry.isDirectory()) {
411 if (!Util.acceptedFilter(entryName, filter)) {
415 InputStream entryStream = zip.getInputStream(entry);
416 yangsFromDependencies.add(entryStream);
417 resources.add(entryStream);
423 } catch (Exception e) {
424 getLog().warn("Exception while searching yangs in dependencies", e);
426 return yangsFromDependencies;
430 * Internal utility method for closing open resources.
432 private void closeResources() {
433 for (Closeable resource : resources) {
436 } catch (IOException e) {
437 getLog().warn("Failed to close resources: " + resource, e);