Initial code drop of yang model driven configuration system
[controller.git] / opendaylight / config / yang-jmx-generator-plugin / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / plugin / JMXGenerator.java
diff --git a/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java b/opendaylight/config/yang-jmx-generator-plugin/src/main/java/org/opendaylight/controller/config/yangjmxgenerator/plugin/JMXGenerator.java
new file mode 100644 (file)
index 0000000..743ffba
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.config.yangjmxgenerator.plugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.opendaylight.controller.config.spi.ModuleFactory;
+import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator;
+import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
+import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
+import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang2sources.spi.CodeGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.impl.StaticLoggerBinder;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * This class interfaces with yang-maven-plugin. Gets parsed yang modules in
+ * {@link SchemaContext}, and parameters form the plugin configuration, and
+ * writes service interfaces and/or modules.
+ */
+public class JMXGenerator implements CodeGenerator {
+
+    static final String NAMESPACE_TO_PACKAGE_DIVIDER = "==";
+    static final String NAMESPACE_TO_PACKAGE_PREFIX = "namespaceToPackage";
+    static final String MODULE_FACTORY_FILE_BOOLEAN = "moduleFactoryFile";
+
+    private PackageTranslator packageTranslator;
+    private final CodeWriter codeWriter;
+    private static final Logger logger = LoggerFactory
+            .getLogger(JMXGenerator.class);
+    private Map<String, String> namespaceToPackageMapping;
+    private File resourceBaseDir;
+    private File projectBaseDir;
+    private boolean generateModuleFactoryFile = true;
+
+    public JMXGenerator() {
+        this.codeWriter = new FreeMarkerCodeWriterImpl();
+    }
+
+    public JMXGenerator(CodeWriter codeWriter) {
+        this.codeWriter = codeWriter;
+    }
+
+    @Override
+    public Collection<File> generateSources(SchemaContext context,
+            File outputBaseDir, Set<Module> yangModulesInCurrentMavenModule) {
+
+        Preconditions.checkArgument(context != null, "Null context received");
+        Preconditions.checkArgument(outputBaseDir != null,
+                "Null outputBaseDir received");
+
+        Preconditions
+                .checkArgument(namespaceToPackageMapping != null && !namespaceToPackageMapping.isEmpty(),
+                        "No namespace to package mapping provided in additionalConfiguration");
+
+        packageTranslator = new PackageTranslator(namespaceToPackageMapping);
+
+        if (!outputBaseDir.exists())
+            outputBaseDir.mkdirs();
+
+        GeneratedFilesTracker generatedFiles = new GeneratedFilesTracker();
+        Map<QName, ServiceInterfaceEntry> qNamesToSIEs = new HashMap<>();
+
+        // create SIE structure qNamesToSIEs
+        for (Module module : context.getModules()) {
+            String packageName = packageTranslator.getPackageName(module);
+            Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
+                    .create(module, packageName);
+
+            for (Entry<QName, ServiceInterfaceEntry> sieEntry : namesToSIEntries
+                    .entrySet()) {
+
+                // merge value into qNamesToSIEs
+                if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) {
+                    qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue());
+                } else {
+                    throw new IllegalStateException(
+                            "Cannot add two SIE with same qname "
+                                    + sieEntry.getValue());
+                }
+            }
+            if (yangModulesInCurrentMavenModule.contains(module)) {
+                // write this sie to disk
+                for (ServiceInterfaceEntry sie : namesToSIEntries.values()) {
+                    try {
+                        generatedFiles.addFile(codeWriter.writeSie(sie,
+                                outputBaseDir));
+                    } catch (Exception e) {
+                        throw new RuntimeException(
+                                "Error occurred during SIE source generate phase",
+                                e);
+                    }
+                }
+            }
+        }
+
+        File mainBaseDir = concatFolders(projectBaseDir, "src", "main", "java");
+        Preconditions.checkNotNull(resourceBaseDir,
+                "resource base dir attribute was null");
+
+        StringBuffer fullyQualifiedNamesOfFactories = new StringBuffer();
+        // create MBEs
+        for (Module module : yangModulesInCurrentMavenModule) {
+            String packageName = packageTranslator.getPackageName(module);
+            Map<String /* MB identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
+                    .create(module, qNamesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)),
+                            packageName);
+
+            for (Entry<String, ModuleMXBeanEntry> mbeEntry : namesToMBEs
+                    .entrySet()) {
+                ModuleMXBeanEntry mbe = mbeEntry.getValue();
+                try {
+                    List<File> files1 = codeWriter.writeMbe(mbe, outputBaseDir,
+                            mainBaseDir, resourceBaseDir);
+                    generatedFiles.addFile(files1);
+                } catch (Exception e) {
+                    throw new RuntimeException(
+                            "Error occurred during MBE source generate phase",
+                            e);
+                }
+                fullyQualifiedNamesOfFactories.append(mbe
+                        .getFullyQualifiedName(mbe.getStubFactoryName()));
+                fullyQualifiedNamesOfFactories.append("\n");
+            }
+        }
+        // create ModuleFactory file if needed
+        if (fullyQualifiedNamesOfFactories.length() > 0
+                && generateModuleFactoryFile) {
+            File serviceLoaderFile = JMXGenerator.concatFolders(
+                    resourceBaseDir, "META-INF", "services",
+                    ModuleFactory.class.getName());
+            // if this file does not exist, create empty file
+            serviceLoaderFile.getParentFile().mkdirs();
+            try {
+                serviceLoaderFile.createNewFile();
+                FileUtils.write(serviceLoaderFile,
+                        fullyQualifiedNamesOfFactories.toString());
+            } catch (IOException e) {
+                String message = "Cannot write to " + serviceLoaderFile;
+                logger.error(message);
+                throw new RuntimeException(message, e);
+            }
+        }
+        return generatedFiles.getFiles();
+    }
+
+    static File concatFolders(File projectBaseDir, String... folderNames) {
+        StringBuilder b = new StringBuilder();
+        for (String folder : folderNames) {
+            b.append(folder);
+            b.append(File.separator);
+        }
+        return new File(projectBaseDir, b.toString());
+    }
+
+    @Override
+    public void setAdditionalConfig(Map<String, String> additionalCfg) {
+        if (logger != null)
+            logger.debug(getClass().getCanonicalName(),
+                    ": Additional configuration received: ",
+                    additionalCfg.toString());
+        this.namespaceToPackageMapping = extractNamespaceMapping(additionalCfg);
+        this.generateModuleFactoryFile = extractModuleFactoryBoolean(additionalCfg);
+    }
+
+    private boolean extractModuleFactoryBoolean(
+            Map<String, String> additionalCfg) {
+        String bool = additionalCfg.get(MODULE_FACTORY_FILE_BOOLEAN);
+        if (bool == null)
+            return true;
+        if (bool.equals("false"))
+            return false;
+        return true;
+    }
+
+    @Override
+    public void setLog(Log log) {
+        StaticLoggerBinder.getSingleton().setLog(log);
+    }
+
+    private static Map<String, String> extractNamespaceMapping(
+            Map<String, String> additionalCfg) {
+        Map<String, String> namespaceToPackage = Maps.newHashMap();
+        for (String key : additionalCfg.keySet()) {
+            if (key.startsWith(NAMESPACE_TO_PACKAGE_PREFIX)) {
+                String mapping = additionalCfg.get(key);
+                NamespaceMapping mappingResolved = extractNamespaceMapping(mapping);
+                namespaceToPackage.put(mappingResolved.namespace,
+                        mappingResolved.packageName);
+            }
+        }
+        return namespaceToPackage;
+    }
+
+    static Pattern namespaceMappingPattern = Pattern.compile("(.+)"
+            + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)");
+
+    private static NamespaceMapping extractNamespaceMapping(String mapping) {
+        Matcher matcher = namespaceMappingPattern.matcher(mapping);
+        Preconditions
+                .checkArgument(matcher.matches(), String.format("Namespace to package mapping:%s is in invalid " +
+                        "format, requested format is: %s", mapping, namespaceMappingPattern));
+        return new NamespaceMapping(matcher.group(1), matcher.group(2));
+    }
+
+    private static class NamespaceMapping {
+        public NamespaceMapping(String namespace, String packagename) {
+            this.namespace = namespace;
+            this.packageName = packagename;
+        }
+
+        private final String namespace, packageName;
+    }
+
+    @Override
+    public void setResourceBaseDir(File resourceDir) {
+        this.resourceBaseDir = resourceDir;
+    }
+
+    @Override
+    public void setMavenProject(MavenProject project) {
+        this.projectBaseDir = project.getBasedir();
+
+        if (logger != null)
+            logger.debug(getClass().getCanonicalName(), " project base dir: ",
+                    projectBaseDir);
+    }
+
+    @VisibleForTesting
+    static class GeneratedFilesTracker {
+        private final Set<File> files = Sets.newHashSet();
+
+        void addFile(File file) {
+            if (files.contains(file)) {
+                List<File> undeletedFiles = Lists.newArrayList();
+                for (File presentFile : files) {
+                    if (presentFile.delete() == false) {
+                        undeletedFiles.add(presentFile);
+                    }
+                }
+                if (undeletedFiles.isEmpty() == false) {
+                    logger.error(
+                            "Illegal state occurred: Unable to delete already generated files, undeleted files: {}",
+                            undeletedFiles);
+                }
+                throw new IllegalStateException(
+                        "Name conflict in generated files, file" + file
+                                + " present twice");
+            }
+            files.add(file);
+        }
+
+        void addFile(Collection<File> files) {
+            for (File file : files) {
+                addFile(file);
+            }
+        }
+
+        public Set<File> getFiles() {
+            return files;
+        }
+    }
+}