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.Collections;
20 import java.util.Enumeration;
21 import java.util.HashSet;
22 import java.util.List;
25 import java.util.zip.ZipEntry;
26 import java.util.zip.ZipFile;
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;
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;
53 * Generate sources from yang files using user provided set of
54 * {@link CodeGenerator}s. Steps of this process:
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>
60 * <li>Instantiate using default constructor</li>
61 * <li>Call {@link CodeGenerator#generateSources(SchemaContext, File)}</li>
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/";
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.
77 @Parameter(required = true)
78 private CodeGeneratorArg[] codeGenerators;
81 * Source directory that will be recursively searched for yang files (ending
84 @Parameter(required = true)
85 private String yangFilesRootDir;
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.
93 @Parameter(required = true)
94 private ResourceProviderArg[] resourceProviders;
96 @Parameter(property = "project", required = true, readonly = true)
97 protected MavenProject project;
99 private transient final YangModelParser parser;
102 YangToSourcesMojo(ResourceProviderArg[] resourceProviderArgs,
103 CodeGeneratorArg[] codeGeneratorArgs, YangModelParser parser,
104 String yangFilesRootDir) {
106 this.resourceProviders = resourceProviderArgs;
107 this.codeGenerators = codeGeneratorArgs;
108 this.yangFilesRootDir = yangFilesRootDir;
109 this.parser = parser;
112 public YangToSourcesMojo() {
114 parser = new YangParserImpl();
118 public void execute() throws MojoExecutionException, MojoFailureException {
119 ContextHolder context = processYang();
120 generateSources(context);
127 * Generate {@link SchemaContext} with {@link YangModelParserImpl}
129 private ContextHolder processYang() throws MojoExecutionException {
131 List<InputStream> yangFiles = Util
132 .listFilesAsStream(yangFilesRootDir);
133 Set<Module> yangModules = parser
134 .parseYangModelsFromStreams(yangFiles);
136 Set<String> yangModulesNames = new HashSet<String>();
137 for (Module module : yangModules) {
138 yangModulesNames.add(module.getName());
141 List<InputStream> yangFilesFromDependencies = getFilesFromDependenciesAsStream();
142 Set<Module> yangModulesFromDependencies = parser
143 .parseYangModelsFromStreams(yangFilesFromDependencies);
145 Set<Module> parsedYang = new HashSet<Module>(yangModules);
146 parsedYang.addAll(yangModulesFromDependencies);
148 if (yangFiles.isEmpty() && yangFilesFromDependencies.isEmpty()) {
151 "No %s file found in %s or in dependencies",
152 LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir));
153 Set<String> names = Collections.emptySet();
154 return new ContextHolder(null, names);
157 SchemaContext resolveSchemaContext = parser
158 .resolveSchemaContext(parsedYang);
160 Util.message("%s files parsed from %s", LOG_PREFIX,
161 Util.YANG_SUFFIX, yangFiles));
162 return new ContextHolder(resolveSchemaContext, yangModulesNames);
164 // MojoExecutionException is thrown since execution cannot continue
165 } catch (Exception e) {
166 String message = Util.message("Unable to parse %s files from %s",
167 LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir);
168 getLog().error(message, e);
169 throw new MojoExecutionException(message, e);
173 private void generateResources() throws MojoExecutionException,
174 MojoFailureException {
175 if (resourceProviders.length == 0) {
177 Util.message("No resource provider classes provided",
182 Resource res = new Resource();
183 String baseDirName = project.getBasedir().getAbsolutePath();
184 res.setDirectory(baseDirName + OUTPUT_RESOURCE_DIR);
185 res.setTargetPath(INPUT_RESOURCE_DIR);
186 project.addResource(res);
188 Map<String, String> thrown = Maps.newHashMap();
190 Collection<File> yangFiles = new ArrayList<File>();
192 // load files from yang root
193 yangFiles.addAll(getFilesFromYangRoot());
195 // load files from dependencies
196 Collection<File> filesFromDependencies = getFilesFromDependencies();
197 yangFiles.addAll(filesFromDependencies);
199 for (ResourceProviderArg resourceProvider : resourceProviders) {
201 provideResourcesWithOneProvider(yangFiles, resourceProvider);
202 } catch (Exception e) {
203 // try other generators, exception will be thrown after
206 "Unable to provide resources with %s resource provider",
208 resourceProvider.getResourceProviderClass()), e);
209 thrown.put(resourceProvider.getResourceProviderClass(), e
210 .getClass().getCanonicalName());
214 if (!thrown.isEmpty()) {
215 String message = Util
217 "One or more code resource provider failed, including failed list(resourceProviderClass=exception) %s",
218 LOG_PREFIX, thrown.toString());
219 getLog().error(message);
220 throw new MojoFailureException(message);
224 private Collection<File> getFilesFromYangRoot() {
225 Collection<File> yangFilesLoaded = null;
227 File rootDir = new File(yangFilesRootDir);
229 if (rootDir.isAbsolute()) {
230 yangFilesLoaded = Util.listFiles(yangFilesRootDir);
232 String path = project.getBasedir().getAbsolutePath()
233 + File.separator + yangFilesRootDir;
234 yangFilesLoaded = Util.listFiles(path);
236 } catch (FileNotFoundException e) {
238 "yangFilesRootDir[" + rootDir.getAbsolutePath()
239 + "] does not exists.");
240 yangFilesLoaded = new ArrayList<File>();
243 Collection<File> yangFiles = new ArrayList<File>(yangFilesLoaded);
246 for (File yangFile : yangFilesLoaded) {
247 InputStream is = new FileInputStream(yangFile);
248 yangFiles.add(createFileFromStream(is,
249 project.getBasedir().getAbsolutePath()
250 + OUTPUT_RESOURCE_DIR + yangFile.getName()));
253 } catch (IOException e) {
254 getLog().warn("Exception while loading yang files.", e);
259 private Collection<File> getFilesFromDependencies() {
260 Collection<File> yangFiles = new ArrayList<File>();
263 List<File> filesOnCp = Util.getClassPath(project);
264 List<String> filter = Lists.newArrayList(".yang");
265 for (File file : filesOnCp) {
266 ZipFile zip = new ZipFile(file);
267 Enumeration<? extends ZipEntry> entries = zip.entries();
269 while (entries.hasMoreElements()) {
270 ZipEntry entry = entries.nextElement();
271 String entryName = entry.getName();
272 if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
273 if (entry.isDirectory()) {
276 if (!Util.acceptedFilter(entryName, filter)) {
279 InputStream entryStream = zip.getInputStream(entry);
280 String newEntryName = entryName
281 .substring(INPUT_RESOURCE_DIR.length());
282 File tmp = Files.createTempDir();
283 File f = createFileFromStream(entryStream,
284 tmp.getAbsolutePath() + newEntryName);
287 resources.add(entryStream);
293 } catch (Exception e) {
294 getLog().warn("Exception while loading external yang files.", e);
299 private File createFileFromStream(InputStream is, String absoluteName)
301 File f = new File(absoluteName);
303 f.getParentFile().mkdirs();
307 FileOutputStream fos = new FileOutputStream(f);
308 IOUtils.copy(is, fos);
313 * Instantiate provider from class and call required method
315 private void provideResourcesWithOneProvider(Collection<File> yangFiles,
316 ResourceProviderArg resourceProvider)
317 throws ClassNotFoundException, InstantiationException,
318 IllegalAccessException {
320 resourceProvider.check();
322 ResourceGenerator g = Util.getInstance(
323 resourceProvider.getResourceProviderClass(),
324 ResourceGenerator.class);
326 Util.message("Resource provider instantiated from %s",
327 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
329 g.generateResourceFiles(yangFiles, resourceProvider.getOutputBaseDir());
331 Util.message("Resource provider %s call successful",
332 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
336 * Call generate on every generator from plugin configuration
338 private void generateSources(ContextHolder context)
339 throws MojoFailureException {
340 if (codeGenerators.length == 0) {
342 Util.message("No code generators provided", LOG_PREFIX));
346 Map<String, String> thrown = Maps.newHashMap();
347 for (CodeGeneratorArg codeGenerator : codeGenerators) {
349 generateSourcesWithOneGenerator(context, codeGenerator);
350 } catch (Exception e) {
351 // try other generators, exception will be thrown after
354 "Unable to generate sources with %s generator",
356 codeGenerator.getCodeGeneratorClass()), e);
357 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass()
358 .getCanonicalName());
362 if (!thrown.isEmpty()) {
363 String message = Util
365 "One or more code generators failed, including failed list(generatorClass=exception) %s",
366 LOG_PREFIX, thrown.toString());
367 getLog().error(message);
368 throw new MojoFailureException(message);
373 * Instantiate generator from class and call required method
375 private void generateSourcesWithOneGenerator(ContextHolder context,
376 CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
377 InstantiationException, IllegalAccessException, IOException {
379 codeGeneratorCfg.check();
381 CodeGenerator g = Util.getInstance(
382 codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
384 Util.message("Code generator instantiated from %s", LOG_PREFIX,
385 codeGeneratorCfg.getCodeGeneratorClass()));
387 File outputDir = codeGeneratorCfg.getOutputBaseDir();
388 if (project != null && outputDir != null) {
389 project.addCompileSourceRoot(outputDir.getPath());
391 Collection<File> generated = g.generateSources(context.getContext(),
392 outputDir, context.getYangModulesNames());
394 Util.message("Sources generated by %s: %s", LOG_PREFIX,
395 codeGeneratorCfg.getCodeGeneratorClass(), generated));
399 * Collection of resources which should be closed after use.
401 private final List<Closeable> resources = new ArrayList<Closeable>();
404 * Search for yang files in dependent projects.
406 * @return files found as List of InputStream
408 private List<InputStream> getFilesFromDependenciesAsStream() {
409 final List<InputStream> yangsFromDependencies = new ArrayList<InputStream>();
411 List<File> filesOnCp = Util.getClassPath(project);
413 List<String> filter = Lists.newArrayList(".yang");
414 for (File file : filesOnCp) {
415 ZipFile zip = new ZipFile(file);
416 Enumeration<? extends ZipEntry> entries = zip.entries();
417 while (entries.hasMoreElements()) {
418 ZipEntry entry = entries.nextElement();
419 String entryName = entry.getName();
421 if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
422 if (entry.isDirectory()) {
425 if (!Util.acceptedFilter(entryName, filter)) {
429 InputStream entryStream = zip.getInputStream(entry);
430 yangsFromDependencies.add(entryStream);
431 resources.add(entryStream);
437 } catch (Exception e) {
438 getLog().warn("Exception while searching yangs in dependencies", e);
440 return yangsFromDependencies;
444 * Internal utility method for closing open resources.
446 private void closeResources() {
447 for (Closeable resource : resources) {
450 } catch (IOException e) {
451 getLog().warn("Failed to close resources: " + resource, e);
456 private class ContextHolder {
457 private final SchemaContext context;
458 private final Set<String> yangModulesNames;
460 private ContextHolder(SchemaContext context,
461 Set<String> yangModulesNames) {
462 this.context = context;
463 this.yangModulesNames = yangModulesNames;
466 public SchemaContext getContext() {
470 public Set<String> getYangModulesNames() {
471 return yangModulesNames;