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