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;
50 * Generate sources from yang files using user provided set of
51 * {@link CodeGenerator}s. Steps of this process:
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>
57 * <li>Instantiate using default constructor</li>
58 * <li>Call {@link CodeGenerator#generateSources(SchemaContext, File)}</li>
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/";
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
74 @Parameter(required = true)
75 private CodeGeneratorArg[] codeGenerators;
78 * Source directory that will be recursively searched for yang files (ending
81 @Parameter(required = true)
82 private String yangFilesRootDir;
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.
90 @Parameter(required = true)
91 private ResourceProviderArg[] resourceProviders;
93 @Parameter(property = "project", required = true, readonly = true)
94 protected MavenProject project;
96 private transient final YangModelParser parser;
99 YangToSourcesMojo(ResourceProviderArg[] resourceProviderArgs,
100 CodeGeneratorArg[] codeGeneratorArgs, YangModelParser parser,
101 String yangFilesRootDir) {
103 this.resourceProviders = resourceProviderArgs;
104 this.codeGenerators = codeGeneratorArgs;
105 this.yangFilesRootDir = yangFilesRootDir;
106 this.parser = parser;
109 public YangToSourcesMojo() {
111 parser = new YangParserImpl();
115 public void execute() throws MojoExecutionException, MojoFailureException {
116 SchemaContext context = processYang();
117 generateSources(context);
124 * Generate {@link SchemaContext} with {@link YangModelParserImpl}
126 private SchemaContext processYang() throws MojoExecutionException {
128 Collection<InputStream> yangFiles = Util
129 .listFilesAsStream(yangFilesRootDir);
130 yangFiles.addAll(getFilesFromDependenciesAsStream());
132 if (yangFiles.isEmpty()) {
134 Util.message("No %s file found in %s", LOG_PREFIX,
135 Util.YANG_SUFFIX, yangFilesRootDir));
139 Set<Module> parsedYang = parser
140 .parseYangModelsFromStreams(new ArrayList<InputStream>(
142 SchemaContext resolveSchemaContext = parser
143 .resolveSchemaContext(parsedYang);
145 Util.message("%s files parsed from %s", LOG_PREFIX,
146 Util.YANG_SUFFIX, yangFiles));
147 return resolveSchemaContext;
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);
158 private void generateResources() throws MojoExecutionException,
159 MojoFailureException {
160 if (resourceProviders.length == 0) {
162 Util.message("No resource provider classes provided",
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);
173 Map<String, String> thrown = Maps.newHashMap();
175 Collection<File> yangFiles = new ArrayList<File>();
177 // load files from yang root
178 yangFiles.addAll(getFilesFromYangRoot());
180 // load files from dependencies
181 yangFiles.addAll(getFilesFromDependencies());
184 for (ResourceProviderArg resourceProvider : resourceProviders) {
186 provideResourcesWithOneProvider(yangFiles, resourceProvider);
187 } catch (Exception e) {
188 // try other generators, exception will be thrown after
191 "Unable to provide resources with %s resource provider",
193 resourceProvider.getResourceProviderClass()), e);
194 thrown.put(resourceProvider.getResourceProviderClass(), e
195 .getClass().getCanonicalName());
199 if (!thrown.isEmpty()) {
200 String message = Util
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);
209 private Collection<File> getFilesFromYangRoot() {
210 Collection<File> yangFilesLoaded = null;
212 File rootDir = new File(yangFilesRootDir);
214 if(!rootDir.isAbsolute()) {
215 yangFilesLoaded = Util.listFiles(project.getBasedir().getAbsolutePath() + yangFilesRootDir);
217 yangFilesLoaded = Util.listFiles(yangFilesRootDir);
220 } catch(FileNotFoundException e) {
221 getLog().warn("Directory '" + yangFilesRootDir + "' does not exists.");
222 yangFilesLoaded = new ArrayList<File>();
224 Collection<File> yangFiles = new ArrayList<File>(yangFilesLoaded);
227 for(File yangFile : yangFilesLoaded) {
228 InputStream is = new FileInputStream(yangFile);
229 yangFiles.add(createFileFromStream(is, project.getBasedir().getAbsolutePath() + OUTPUT_RESOURCE_DIR + yangFile.getName()));
232 } catch(IOException e) {
233 getLog().warn("Exception while loading yang files.", e);
238 private Collection<File> getFilesFromDependencies() {
239 Collection<File> yangFiles = new ArrayList<File>();
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();
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()) {
255 if (!Util.acceptedFilter(entryName, filter)) {
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);
263 resources.add(entryStream);
269 } catch (Exception e) {
270 getLog().warn("Exception while loading external yang files.", e);
275 private File createFileFromStream(InputStream is, String absoluteName) throws IOException {
276 File f = new File(absoluteName);
278 f.getParentFile().mkdirs();
282 FileOutputStream fos = new FileOutputStream(f);
283 IOUtils.copy(is, fos);
288 * Instantiate provider from class and call required method
290 private void provideResourcesWithOneProvider(Collection<File> yangFiles,
291 ResourceProviderArg resourceProvider)
292 throws ClassNotFoundException, InstantiationException,
293 IllegalAccessException {
295 resourceProvider.check();
297 ResourceGenerator g = Util.getInstance(
298 resourceProvider.getResourceProviderClass(),
299 ResourceGenerator.class);
301 Util.message("Resource provider instantiated from %s",
302 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
304 g.generateResourceFiles(yangFiles, resourceProvider.getOutputBaseDir());
306 Util.message("Resource provider %s call successful",
307 LOG_PREFIX, resourceProvider.getResourceProviderClass()));
311 * Call generate on every generator from plugin configuration
313 private void generateSources(SchemaContext context)
314 throws MojoFailureException {
315 if (codeGenerators.length == 0) {
317 Util.message("No code generators provided", LOG_PREFIX));
321 Map<String, String> thrown = Maps.newHashMap();
322 for (CodeGeneratorArg codeGenerator : codeGenerators) {
324 generateSourcesWithOneGenerator(context, codeGenerator);
325 } catch (Exception e) {
326 // try other generators, exception will be thrown after
329 "Unable to generate sources with %s generator",
331 codeGenerator.getCodeGeneratorClass()), e);
332 thrown.put(codeGenerator.getCodeGeneratorClass(), e.getClass()
333 .getCanonicalName());
337 if (!thrown.isEmpty()) {
338 String message = Util
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);
348 * Instantiate generator from class and call required method
350 private void generateSourcesWithOneGenerator(SchemaContext context,
351 CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
352 InstantiationException, IllegalAccessException, IOException {
354 codeGeneratorCfg.check();
356 CodeGenerator g = Util.getInstance(
357 codeGeneratorCfg.getCodeGeneratorClass(), CodeGenerator.class);
359 Util.message("Code generator instantiated from %s", LOG_PREFIX,
360 codeGeneratorCfg.getCodeGeneratorClass()));
362 File outputDir = codeGeneratorCfg.getOutputBaseDir();
363 if (project != null && outputDir != null) {
364 project.addCompileSourceRoot(outputDir.getPath());
366 Collection<File> generated = g.generateSources(context, outputDir);
368 Util.message("Sources generated by %s: %s", LOG_PREFIX,
369 codeGeneratorCfg.getCodeGeneratorClass(), generated));
373 * Collection of resources which should be closed after use.
375 private final List<Closeable> resources = new ArrayList<Closeable>();
378 * Search for yang files in dependent projects.
380 * @return files found as List of InputStream
382 private List<InputStream> getFilesFromDependenciesAsStream() {
383 final List<InputStream> yangsFromDependencies = new ArrayList<InputStream>();
385 List<File> filesOnCp = Util.getClassPath(project);
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();
395 if(entryName.startsWith(INPUT_RESOURCE_DIR)) {
396 if(entry.isDirectory()) {
399 if (!Util.acceptedFilter(entryName, filter)) {
403 InputStream entryStream = zip.getInputStream(entry);
404 yangsFromDependencies.add(entryStream);
405 resources.add(entryStream);
411 } catch (Exception e) {
412 getLog().warn("Exception while searching yangs in dependencies", e);
414 return yangsFromDependencies;
418 * Internal utility method for closing open resources.
420 private void closeResources() {
421 for (Closeable resource : resources) {
424 } catch (IOException e) {
425 getLog().warn("Failed to close resources: "+ resource, e);