Refactored yang-maven-plugin. Updated tests.
[controller.git] / opendaylight / sal / yang-prototype / code-generator / maven-yang-plugin / src / main / java / org / opendaylight / controller / yang2sources / plugin / YangToSourcesMojo.java
index 2b7dc33845d12e8a78833e57cc600b85377d4f7e..ab49a54fd1fa809a439ab42cc8c731973227cb54 100644 (file)
@@ -7,12 +7,26 @@
  */
 package org.opendaylight.controller.yang2sources.plugin;
 
+import java.io.Closeable;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.model.Resource;
 import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugin.MojoFailureException;
@@ -24,12 +38,16 @@ import org.apache.maven.project.MavenProject;
 import org.opendaylight.controller.yang.model.api.Module;
 import org.opendaylight.controller.yang.model.api.SchemaContext;
 import org.opendaylight.controller.yang.model.parser.api.YangModelParser;
-import org.opendaylight.controller.yang.model.parser.impl.YangModelParserImpl;
+import org.opendaylight.controller.yang.parser.impl.YangParserImpl;
 import org.opendaylight.controller.yang2sources.plugin.ConfigArg.CodeGeneratorArg;
+import org.opendaylight.controller.yang2sources.plugin.ConfigArg.ResourceProviderArg;
 import org.opendaylight.controller.yang2sources.spi.CodeGenerator;
+import org.opendaylight.controller.yang2sources.spi.ResourceGenerator;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.io.Files;
 
 /**
  * Generate sources from yang files using user provided set of
@@ -46,14 +64,15 @@ import com.google.common.collect.Maps;
  */
 @Mojo(name = "generate-sources", defaultPhase = LifecyclePhase.GENERATE_SOURCES, requiresDependencyResolution = ResolutionScope.COMPILE, requiresProject = true)
 public final class YangToSourcesMojo extends AbstractMojo {
-
     private static final String LOG_PREFIX = "yang-to-sources:";
+    private static final String INPUT_RESOURCE_DIR = "META-INF/yang/";
+    private static final String OUTPUT_RESOURCE_DIR = "/target/external-resources/";
 
     /**
      * Classes implementing {@link CodeGenerator} interface. An instance will be
-     * created out of every class using default constructor. Method
-     * {@link CodeGenerator#generateSources(SchemaContext, File)} will be called
-     * on every instance.
+     * created out of every class using default constructor. Method {@link
+     * CodeGenerator#generateSources(SchemaContext, File, Set<String>
+     * yangModulesNames)} will be called on every instance.
      */
     @Parameter(required = true)
     private CodeGeneratorArg[] codeGenerators;
@@ -65,15 +84,26 @@ public final class YangToSourcesMojo extends AbstractMojo {
     @Parameter(required = true)
     private String yangFilesRootDir;
 
+    /**
+     * Classes implementing {@link ResourceGenerator} interface. An instance
+     * will be created out of every class using default constructor. Method
+     * {@link ResourceGenerator#generateResourceFiles(Collection, File)} will be
+     * called on every instance.
+     */
+    @Parameter(required = true)
+    private ResourceProviderArg[] resourceProviders;
+
     @Parameter(property = "project", required = true, readonly = true)
     protected MavenProject project;
 
     private transient final YangModelParser parser;
 
     @VisibleForTesting
-    YangToSourcesMojo(CodeGeneratorArg[] codeGeneratorArgs,
-            YangModelParser parser, String yangFilesRootDir) {
+    YangToSourcesMojo(ResourceProviderArg[] resourceProviderArgs,
+            CodeGeneratorArg[] codeGeneratorArgs, YangModelParser parser,
+            String yangFilesRootDir) {
         super();
+        this.resourceProviders = resourceProviderArgs;
         this.codeGenerators = codeGeneratorArgs;
         this.yangFilesRootDir = yangFilesRootDir;
         this.parser = parser;
@@ -81,37 +111,55 @@ public final class YangToSourcesMojo extends AbstractMojo {
 
     public YangToSourcesMojo() {
         super();
-        parser = new YangModelParserImpl();
+        parser = new YangParserImpl();
     }
 
     @Override
     public void execute() throws MojoExecutionException, MojoFailureException {
-        SchemaContext context = processYang();
+        ContextHolder context = processYang();
         generateSources(context);
+        generateResources();
+
+        closeResources();
     }
 
     /**
      * Generate {@link SchemaContext} with {@link YangModelParserImpl}
      */
-    private SchemaContext processYang() throws MojoExecutionException {
+    private ContextHolder processYang() throws MojoExecutionException {
         try {
-            Collection<File> yangFiles = Util.listFiles(yangFilesRootDir);
+            List<InputStream> yangFiles = Util
+                    .listFilesAsStream(yangFilesRootDir);
+            Set<Module> yangModules = parser
+                    .parseYangModelsFromStreams(yangFiles);
+
+            Set<String> yangModulesNames = new HashSet<String>();
+            for (Module module : yangModules) {
+                yangModulesNames.add(module.getName());
+            }
+
+            List<InputStream> yangFilesFromDependencies = getFilesFromDependenciesAsStream();
+            Set<Module> yangModulesFromDependencies = parser
+                    .parseYangModelsFromStreams(yangFilesFromDependencies);
 
-            if (yangFiles.isEmpty()) {
+            Set<Module> parsedYang = new HashSet<Module>(yangModules);
+            parsedYang.addAll(yangModulesFromDependencies);
+
+            if (yangFiles.isEmpty() && yangFilesFromDependencies.isEmpty()) {
                 getLog().warn(
-                        Util.message("No %s file found in %s", LOG_PREFIX,
-                                Util.YANG_SUFFIX, yangFilesRootDir));
-                return null;
+                        Util.message(
+                                "No %s file found in %s or in dependencies",
+                                LOG_PREFIX, Util.YANG_SUFFIX, yangFilesRootDir));
+                Set<String> names = Collections.emptySet();
+                return new ContextHolder(null, names);
             }
 
-            Set<Module> parsedYang = parser
-                    .parseYangModels(new ArrayList<File>(yangFiles));
             SchemaContext resolveSchemaContext = parser
                     .resolveSchemaContext(parsedYang);
             getLog().info(
                     Util.message("%s files parsed from %s", LOG_PREFIX,
                             Util.YANG_SUFFIX, yangFiles));
-            return resolveSchemaContext;
+            return new ContextHolder(resolveSchemaContext, yangModulesNames);
 
             // MojoExecutionException is thrown since execution cannot continue
         } catch (Exception e) {
@@ -122,10 +170,172 @@ public final class YangToSourcesMojo extends AbstractMojo {
         }
     }
 
+    private void generateResources() throws MojoExecutionException,
+            MojoFailureException {
+        if (resourceProviders.length == 0) {
+            getLog().warn(
+                    Util.message("No resource provider classes provided",
+                            LOG_PREFIX));
+            return;
+        }
+
+        Resource res = new Resource();
+        String baseDirName = project.getBasedir().getAbsolutePath();
+        res.setDirectory(baseDirName + OUTPUT_RESOURCE_DIR);
+        res.setTargetPath(INPUT_RESOURCE_DIR);
+        project.addResource(res);
+
+        Map<String, String> thrown = Maps.newHashMap();
+
+        Collection<File> yangFiles = new ArrayList<File>();
+
+        // load files from yang root
+        yangFiles.addAll(getFilesFromYangRoot());
+
+        // load files from dependencies
+        Collection<File> filesFromDependencies = getFilesFromDependencies();
+        yangFiles.addAll(filesFromDependencies);
+
+        for (ResourceProviderArg resourceProvider : resourceProviders) {
+            try {
+                provideResourcesWithOneProvider(yangFiles, resourceProvider);
+            } catch (Exception e) {
+                // try other generators, exception will be thrown after
+                getLog().error(
+                        Util.message(
+                                "Unable to provide resources with %s resource provider",
+                                LOG_PREFIX,
+                                resourceProvider.getResourceProviderClass()), e);
+                thrown.put(resourceProvider.getResourceProviderClass(), e
+                        .getClass().getCanonicalName());
+            }
+        }
+
+        if (!thrown.isEmpty()) {
+            String message = Util
+                    .message(
+                            "One or more code resource provider failed, including failed list(resourceProviderClass=exception) %s",
+                            LOG_PREFIX, thrown.toString());
+            getLog().error(message);
+            throw new MojoFailureException(message);
+        }
+    }
+
+    private Collection<File> getFilesFromYangRoot() {
+        Collection<File> yangFilesLoaded = null;
+
+        File rootDir = new File(yangFilesRootDir);
+        try {
+            if (rootDir.isAbsolute()) {
+                yangFilesLoaded = Util.listFiles(yangFilesRootDir);
+            } else {
+                String path = project.getBasedir().getAbsolutePath()
+                        + File.separator + yangFilesRootDir;
+                yangFilesLoaded = Util.listFiles(path);
+            }
+        } catch (FileNotFoundException e) {
+            getLog().warn(
+                    "yangFilesRootDir[" + rootDir.getAbsolutePath()
+                            + "] does not exists.");
+            yangFilesLoaded = new ArrayList<File>();
+        }
+
+        Collection<File> yangFiles = new ArrayList<File>(yangFilesLoaded);
+
+        try {
+            for (File yangFile : yangFilesLoaded) {
+                InputStream is = new FileInputStream(yangFile);
+                yangFiles.add(createFileFromStream(is,
+                        project.getBasedir().getAbsolutePath()
+                                + OUTPUT_RESOURCE_DIR + yangFile.getName()));
+                resources.add(is);
+            }
+        } catch (IOException e) {
+            getLog().warn("Exception while loading yang files.", e);
+        }
+        return yangFiles;
+    }
+
+    private Collection<File> getFilesFromDependencies() {
+        Collection<File> yangFiles = new ArrayList<File>();
+
+        try {
+            List<File> filesOnCp = Util.getClassPath(project);
+            List<String> filter = Lists.newArrayList(".yang");
+            for (File file : filesOnCp) {
+                ZipFile zip = new ZipFile(file);
+                Enumeration<? extends ZipEntry> entries = zip.entries();
+
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = entries.nextElement();
+                    String entryName = entry.getName();
+                    if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
+                        if (entry.isDirectory()) {
+                            continue;
+                        }
+                        if (!Util.acceptedFilter(entryName, filter)) {
+                            continue;
+                        }
+                        InputStream entryStream = zip.getInputStream(entry);
+                        String newEntryName = entryName
+                                .substring(INPUT_RESOURCE_DIR.length());
+                        File tmp = Files.createTempDir();
+                        File f = createFileFromStream(entryStream,
+                                tmp.getAbsolutePath() + newEntryName);
+                        yangFiles.add(f);
+
+                        resources.add(entryStream);
+                    }
+                }
+
+                resources.add(zip);
+            }
+        } catch (Exception e) {
+            getLog().warn("Exception while loading external yang files.", e);
+        }
+        return yangFiles;
+    }
+
+    private File createFileFromStream(InputStream is, String absoluteName)
+            throws IOException {
+        File f = new File(absoluteName);
+        if (!f.exists()) {
+            f.getParentFile().mkdirs();
+        }
+        f.createNewFile();
+
+        FileOutputStream fos = new FileOutputStream(f);
+        IOUtils.copy(is, fos);
+        return f;
+    }
+
+    /**
+     * Instantiate provider from class and call required method
+     */
+    private void provideResourcesWithOneProvider(Collection<File> yangFiles,
+            ResourceProviderArg resourceProvider)
+            throws ClassNotFoundException, InstantiationException,
+            IllegalAccessException {
+
+        resourceProvider.check();
+
+        ResourceGenerator g = Util.getInstance(
+                resourceProvider.getResourceProviderClass(),
+                ResourceGenerator.class);
+        getLog().info(
+                Util.message("Resource provider instantiated from %s",
+                        LOG_PREFIX, resourceProvider.getResourceProviderClass()));
+
+        g.generateResourceFiles(yangFiles, resourceProvider.getOutputBaseDir());
+        getLog().info(
+                Util.message("Resource provider %s call successful",
+                        LOG_PREFIX, resourceProvider.getResourceProviderClass()));
+    }
+
     /**
      * Call generate on every generator from plugin configuration
      */
-    private void generateSources(SchemaContext context)
+    private void generateSources(ContextHolder context)
             throws MojoFailureException {
         if (codeGenerators.length == 0) {
             getLog().warn(
@@ -134,12 +344,9 @@ public final class YangToSourcesMojo extends AbstractMojo {
         }
 
         Map<String, String> thrown = Maps.newHashMap();
-
         for (CodeGeneratorArg codeGenerator : codeGenerators) {
             try {
-
                 generateSourcesWithOneGenerator(context, codeGenerator);
-
             } catch (Exception e) {
                 // try other generators, exception will be thrown after
                 getLog().error(
@@ -165,9 +372,9 @@ public final class YangToSourcesMojo extends AbstractMojo {
     /**
      * Instantiate generator from class and call required method
      */
-    private void generateSourcesWithOneGenerator(SchemaContext context,
+    private void generateSourcesWithOneGenerator(ContextHolder context,
             CodeGeneratorArg codeGeneratorCfg) throws ClassNotFoundException,
-            InstantiationException, IllegalAccessException {
+            InstantiationException, IllegalAccessException, IOException {
 
         codeGeneratorCfg.check();
 
@@ -181,10 +388,88 @@ public final class YangToSourcesMojo extends AbstractMojo {
         if (project != null && outputDir != null) {
             project.addCompileSourceRoot(outputDir.getPath());
         }
-        Collection<File> generated = g.generateSources(context, outputDir);
+        Collection<File> generated = g.generateSources(context.getContext(),
+                outputDir, context.getYangModulesNames());
         getLog().info(
                 Util.message("Sources generated by %s: %s", LOG_PREFIX,
                         codeGeneratorCfg.getCodeGeneratorClass(), generated));
     }
 
+    /**
+     * Collection of resources which should be closed after use.
+     */
+    private final List<Closeable> resources = new ArrayList<Closeable>();
+
+    /**
+     * Search for yang files in dependent projects.
+     *
+     * @return files found as List of InputStream
+     */
+    private List<InputStream> getFilesFromDependenciesAsStream() {
+        final List<InputStream> yangsFromDependencies = new ArrayList<InputStream>();
+        try {
+            List<File> filesOnCp = Util.getClassPath(project);
+
+            List<String> filter = Lists.newArrayList(".yang");
+            for (File file : filesOnCp) {
+                ZipFile zip = new ZipFile(file);
+                Enumeration<? extends ZipEntry> entries = zip.entries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = entries.nextElement();
+                    String entryName = entry.getName();
+
+                    if (entryName.startsWith(INPUT_RESOURCE_DIR)) {
+                        if (entry.isDirectory()) {
+                            continue;
+                        }
+                        if (!Util.acceptedFilter(entryName, filter)) {
+                            continue;
+                        }
+
+                        InputStream entryStream = zip.getInputStream(entry);
+                        yangsFromDependencies.add(entryStream);
+                        resources.add(entryStream);
+                    }
+
+                }
+                resources.add(zip);
+            }
+        } catch (Exception e) {
+            getLog().warn("Exception while searching yangs in dependencies", e);
+        }
+        return yangsFromDependencies;
+    }
+
+    /**
+     * Internal utility method for closing open resources.
+     */
+    private void closeResources() {
+        for (Closeable resource : resources) {
+            try {
+                resource.close();
+            } catch (IOException e) {
+                getLog().warn("Failed to close resources: " + resource, e);
+            }
+        }
+    }
+
+    private class ContextHolder {
+        private final SchemaContext context;
+        private final Set<String> yangModulesNames;
+
+        private ContextHolder(SchemaContext context,
+                Set<String> yangModulesNames) {
+            this.context = context;
+            this.yangModulesNames = yangModulesNames;
+        }
+
+        public SchemaContext getContext() {
+            return context;
+        }
+
+        public Set<String> getYangModulesNames() {
+            return yangModulesNames;
+        }
+    }
+
 }