Initial code drop of netconf protocol implementation
[controller.git] / opendaylight / config / yang-jmx-generator-plugin / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / plugin / JMXGenerator.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.config.yangjmxgenerator.plugin;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Preconditions;
12 import com.google.common.collect.Lists;
13 import com.google.common.collect.Maps;
14 import com.google.common.collect.Sets;
15 import org.apache.commons.io.FileUtils;
16 import org.apache.maven.plugin.logging.Log;
17 import org.apache.maven.project.MavenProject;
18 import org.opendaylight.controller.config.spi.ModuleFactory;
19 import org.opendaylight.controller.config.yangjmxgenerator.ModuleMXBeanEntry;
20 import org.opendaylight.controller.config.yangjmxgenerator.PackageTranslator;
21 import org.opendaylight.controller.config.yangjmxgenerator.ServiceInterfaceEntry;
22 import org.opendaylight.controller.config.yangjmxgenerator.TypeProviderWrapper;
23 import org.opendaylight.yangtools.sal.binding.yang.types.TypeProviderImpl;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.model.api.Module;
26 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
27 import org.opendaylight.yangtools.yang2sources.spi.CodeGenerator;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.slf4j.impl.StaticLoggerBinder;
31
32 import java.io.File;
33 import java.io.IOException;
34 import java.util.*;
35 import java.util.Map.Entry;
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39 /**
40  * This class interfaces with yang-maven-plugin. Gets parsed yang modules in
41  * {@link SchemaContext}, and parameters form the plugin configuration, and
42  * writes service interfaces and/or modules.
43  */
44 public class JMXGenerator implements CodeGenerator {
45
46     static final String NAMESPACE_TO_PACKAGE_DIVIDER = "==";
47     static final String NAMESPACE_TO_PACKAGE_PREFIX = "namespaceToPackage";
48     static final String MODULE_FACTORY_FILE_BOOLEAN = "moduleFactoryFile";
49
50     private PackageTranslator packageTranslator;
51     private final CodeWriter codeWriter;
52     private static final Logger logger = LoggerFactory
53             .getLogger(JMXGenerator.class);
54     private Map<String, String> namespaceToPackageMapping;
55     private File resourceBaseDir;
56     private File projectBaseDir;
57     private boolean generateModuleFactoryFile = true;
58
59     public JMXGenerator() {
60         this.codeWriter = new FreeMarkerCodeWriterImpl();
61     }
62
63     public JMXGenerator(CodeWriter codeWriter) {
64         this.codeWriter = codeWriter;
65     }
66
67     @Override
68     public Collection<File> generateSources(SchemaContext context,
69             File outputBaseDir, Set<Module> yangModulesInCurrentMavenModule) {
70
71         Preconditions.checkArgument(context != null, "Null context received");
72         Preconditions.checkArgument(outputBaseDir != null,
73                 "Null outputBaseDir received");
74
75         Preconditions
76                 .checkArgument(namespaceToPackageMapping != null && !namespaceToPackageMapping.isEmpty(),
77                         "No namespace to package mapping provided in additionalConfiguration");
78
79         packageTranslator = new PackageTranslator(namespaceToPackageMapping);
80
81         if (!outputBaseDir.exists())
82             outputBaseDir.mkdirs();
83
84         GeneratedFilesTracker generatedFiles = new GeneratedFilesTracker();
85         Map<QName, ServiceInterfaceEntry> qNamesToSIEs = new HashMap<>();
86
87         // create SIE structure qNamesToSIEs
88         for (Module module : context.getModules()) {
89             String packageName = packageTranslator.getPackageName(module);
90             Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
91                     .create(module, packageName);
92
93             for (Entry<QName, ServiceInterfaceEntry> sieEntry : namesToSIEntries
94                     .entrySet()) {
95
96                 // merge value into qNamesToSIEs
97                 if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) {
98                     qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue());
99                 } else {
100                     throw new IllegalStateException(
101                             "Cannot add two SIE with same qname "
102                                     + sieEntry.getValue());
103                 }
104             }
105             if (yangModulesInCurrentMavenModule.contains(module)) {
106                 // write this sie to disk
107                 for (ServiceInterfaceEntry sie : namesToSIEntries.values()) {
108                     try {
109                         generatedFiles.addFile(codeWriter.writeSie(sie,
110                                 outputBaseDir));
111                     } catch (Exception e) {
112                         throw new RuntimeException(
113                                 "Error occurred during SIE source generate phase",
114                                 e);
115                     }
116                 }
117             }
118         }
119
120         File mainBaseDir = concatFolders(projectBaseDir, "src", "main", "java");
121         Preconditions.checkNotNull(resourceBaseDir,
122                 "resource base dir attribute was null");
123
124         StringBuffer fullyQualifiedNamesOfFactories = new StringBuffer();
125         // create MBEs
126         for (Module module : yangModulesInCurrentMavenModule) {
127             String packageName = packageTranslator.getPackageName(module);
128             Map<String /* MB identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
129                     .create(module, qNamesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)),
130                             packageName);
131
132             for (Entry<String, ModuleMXBeanEntry> mbeEntry : namesToMBEs
133                     .entrySet()) {
134                 ModuleMXBeanEntry mbe = mbeEntry.getValue();
135                 try {
136                     List<File> files1 = codeWriter.writeMbe(mbe, outputBaseDir,
137                             mainBaseDir, resourceBaseDir);
138                     generatedFiles.addFile(files1);
139                 } catch (Exception e) {
140                     throw new RuntimeException(
141                             "Error occurred during MBE source generate phase",
142                             e);
143                 }
144                 fullyQualifiedNamesOfFactories.append(mbe
145                         .getFullyQualifiedName(mbe.getStubFactoryName()));
146                 fullyQualifiedNamesOfFactories.append("\n");
147             }
148         }
149         // create ModuleFactory file if needed
150         if (fullyQualifiedNamesOfFactories.length() > 0
151                 && generateModuleFactoryFile) {
152             File serviceLoaderFile = JMXGenerator.concatFolders(
153                     resourceBaseDir, "META-INF", "services",
154                     ModuleFactory.class.getName());
155             // if this file does not exist, create empty file
156             serviceLoaderFile.getParentFile().mkdirs();
157             try {
158                 serviceLoaderFile.createNewFile();
159                 FileUtils.write(serviceLoaderFile,
160                         fullyQualifiedNamesOfFactories.toString());
161             } catch (IOException e) {
162                 String message = "Cannot write to " + serviceLoaderFile;
163                 logger.error(message);
164                 throw new RuntimeException(message, e);
165             }
166         }
167         return generatedFiles.getFiles();
168     }
169
170     static File concatFolders(File projectBaseDir, String... folderNames) {
171         StringBuilder b = new StringBuilder();
172         for (String folder : folderNames) {
173             b.append(folder);
174             b.append(File.separator);
175         }
176         return new File(projectBaseDir, b.toString());
177     }
178
179     @Override
180     public void setAdditionalConfig(Map<String, String> additionalCfg) {
181         if (logger != null)
182             logger.debug(getClass().getCanonicalName(),
183                     ": Additional configuration received: ",
184                     additionalCfg.toString());
185         this.namespaceToPackageMapping = extractNamespaceMapping(additionalCfg);
186         this.generateModuleFactoryFile = extractModuleFactoryBoolean(additionalCfg);
187     }
188
189     private boolean extractModuleFactoryBoolean(
190             Map<String, String> additionalCfg) {
191         String bool = additionalCfg.get(MODULE_FACTORY_FILE_BOOLEAN);
192         if (bool == null)
193             return true;
194         if (bool.equals("false"))
195             return false;
196         return true;
197     }
198
199     @Override
200     public void setLog(Log log) {
201         StaticLoggerBinder.getSingleton().setLog(log);
202     }
203
204     private static Map<String, String> extractNamespaceMapping(
205             Map<String, String> additionalCfg) {
206         Map<String, String> namespaceToPackage = Maps.newHashMap();
207         for (String key : additionalCfg.keySet()) {
208             if (key.startsWith(NAMESPACE_TO_PACKAGE_PREFIX)) {
209                 String mapping = additionalCfg.get(key);
210                 NamespaceMapping mappingResolved = extractNamespaceMapping(mapping);
211                 namespaceToPackage.put(mappingResolved.namespace,
212                         mappingResolved.packageName);
213             }
214         }
215         return namespaceToPackage;
216     }
217
218     static Pattern namespaceMappingPattern = Pattern.compile("(.+)"
219             + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)");
220
221     private static NamespaceMapping extractNamespaceMapping(String mapping) {
222         Matcher matcher = namespaceMappingPattern.matcher(mapping);
223         Preconditions
224                 .checkArgument(matcher.matches(), String.format("Namespace to package mapping:%s is in invalid " +
225                         "format, requested format is: %s", mapping, namespaceMappingPattern));
226         return new NamespaceMapping(matcher.group(1), matcher.group(2));
227     }
228
229     private static class NamespaceMapping {
230         public NamespaceMapping(String namespace, String packagename) {
231             this.namespace = namespace;
232             this.packageName = packagename;
233         }
234
235         private final String namespace, packageName;
236     }
237
238     @Override
239     public void setResourceBaseDir(File resourceDir) {
240         this.resourceBaseDir = resourceDir;
241     }
242
243     @Override
244     public void setMavenProject(MavenProject project) {
245         this.projectBaseDir = project.getBasedir();
246
247         if (logger != null)
248             logger.debug(getClass().getCanonicalName(), " project base dir: ",
249                     projectBaseDir);
250     }
251
252     @VisibleForTesting
253     static class GeneratedFilesTracker {
254         private final Set<File> files = Sets.newHashSet();
255
256         void addFile(File file) {
257             if (files.contains(file)) {
258                 List<File> undeletedFiles = Lists.newArrayList();
259                 for (File presentFile : files) {
260                     if (presentFile.delete() == false) {
261                         undeletedFiles.add(presentFile);
262                     }
263                 }
264                 if (undeletedFiles.isEmpty() == false) {
265                     logger.error(
266                             "Illegal state occurred: Unable to delete already generated files, undeleted files: {}",
267                             undeletedFiles);
268                 }
269                 throw new IllegalStateException(
270                         "Name conflict in generated files, file" + file
271                                 + " present twice");
272             }
273             files.add(file);
274         }
275
276         void addFile(Collection<File> files) {
277             for (File file : files) {
278                 addFile(file);
279             }
280         }
281
282         public Set<File> getFiles() {
283             return files;
284         }
285     }
286 }