10f19e8399c0fd01ab11b787fcb02951892a778f
[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.Collection;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Map.Entry;
39 import java.util.Set;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
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         Map<QName, ServiceInterfaceEntry> qNamesToSIEs = new HashMap<>();
90
91         // create SIE structure qNamesToSIEs
92         for (Module module : context.getModules()) {
93             String packageName = packageTranslator.getPackageName(module);
94             Map<QName, ServiceInterfaceEntry> namesToSIEntries = ServiceInterfaceEntry
95                     .create(module, packageName);
96
97             for (Entry<QName, ServiceInterfaceEntry> sieEntry : namesToSIEntries
98                     .entrySet()) {
99
100                 // merge value into qNamesToSIEs
101                 if (qNamesToSIEs.containsKey(sieEntry.getKey()) == false) {
102                     qNamesToSIEs.put(sieEntry.getKey(), sieEntry.getValue());
103                 } else {
104                     throw new IllegalStateException(
105                             "Cannot add two SIE with same qname "
106                                     + sieEntry.getValue());
107                 }
108             }
109             if (yangModulesInCurrentMavenModule.contains(module)) {
110                 // write this sie to disk
111                 for (ServiceInterfaceEntry sie : namesToSIEntries.values()) {
112                     try {
113                         generatedFiles.addFile(codeWriter.writeSie(sie,
114                                 outputBaseDir));
115                     } catch (Exception e) {
116                         throw new RuntimeException(
117                                 "Error occurred during SIE source generate phase",
118                                 e);
119                     }
120                 }
121             }
122         }
123
124         File mainBaseDir = concatFolders(projectBaseDir, "src", "main", "java");
125         Preconditions.checkNotNull(resourceBaseDir,
126                 "resource base dir attribute was null");
127
128         StringBuffer fullyQualifiedNamesOfFactories = new StringBuffer();
129         // create MBEs
130         for (Module module : yangModulesInCurrentMavenModule) {
131             String packageName = packageTranslator.getPackageName(module);
132             Map<String /* MB identity local name */, ModuleMXBeanEntry> namesToMBEs = ModuleMXBeanEntry
133                     .create(module, qNamesToSIEs, context, new TypeProviderWrapper(new TypeProviderImpl(context)),
134                             packageName);
135
136             for (Entry<String, ModuleMXBeanEntry> mbeEntry : namesToMBEs
137                     .entrySet()) {
138                 ModuleMXBeanEntry mbe = mbeEntry.getValue();
139                 try {
140                     List<File> files1 = codeWriter.writeMbe(mbe, outputBaseDir,
141                             mainBaseDir, resourceBaseDir);
142                     generatedFiles.addFile(files1);
143                 } catch (Exception e) {
144                     throw new RuntimeException(
145                             "Error occurred during MBE source generate phase",
146                             e);
147                 }
148                 fullyQualifiedNamesOfFactories.append(mbe
149                         .getFullyQualifiedName(mbe.getStubFactoryName()));
150                 fullyQualifiedNamesOfFactories.append("\n");
151             }
152         }
153         // create ModuleFactory file if needed
154         if (fullyQualifiedNamesOfFactories.length() > 0
155                 && generateModuleFactoryFile) {
156             File serviceLoaderFile = JMXGenerator.concatFolders(
157                     resourceBaseDir, "META-INF", "services",
158                     ModuleFactory.class.getName());
159             // if this file does not exist, create empty file
160             serviceLoaderFile.getParentFile().mkdirs();
161             try {
162                 serviceLoaderFile.createNewFile();
163                 FileUtils.write(serviceLoaderFile,
164                         fullyQualifiedNamesOfFactories.toString());
165             } catch (IOException e) {
166                 String message = "Cannot write to " + serviceLoaderFile;
167                 logger.error(message);
168                 throw new RuntimeException(message, e);
169             }
170         }
171         return generatedFiles.getFiles();
172     }
173
174     static File concatFolders(File projectBaseDir, String... folderNames) {
175         StringBuilder b = new StringBuilder();
176         for (String folder : folderNames) {
177             b.append(folder);
178             b.append(File.separator);
179         }
180         return new File(projectBaseDir, b.toString());
181     }
182
183     @Override
184     public void setAdditionalConfig(Map<String, String> additionalCfg) {
185         if (logger != null)
186             logger.debug(getClass().getCanonicalName(),
187                     ": Additional configuration received: ",
188                     additionalCfg.toString());
189         this.namespaceToPackageMapping = extractNamespaceMapping(additionalCfg);
190         this.generateModuleFactoryFile = extractModuleFactoryBoolean(additionalCfg);
191     }
192
193     private boolean extractModuleFactoryBoolean(
194             Map<String, String> additionalCfg) {
195         String bool = additionalCfg.get(MODULE_FACTORY_FILE_BOOLEAN);
196         if (bool == null)
197             return true;
198         if (bool.equals("false"))
199             return false;
200         return true;
201     }
202
203     @Override
204     public void setLog(Log log) {
205         StaticLoggerBinder.getSingleton().setLog(log);
206     }
207
208     private static Map<String, String> extractNamespaceMapping(
209             Map<String, String> additionalCfg) {
210         Map<String, String> namespaceToPackage = Maps.newHashMap();
211         for (String key : additionalCfg.keySet()) {
212             if (key.startsWith(NAMESPACE_TO_PACKAGE_PREFIX)) {
213                 String mapping = additionalCfg.get(key);
214                 NamespaceMapping mappingResolved = extractNamespaceMapping(mapping);
215                 namespaceToPackage.put(mappingResolved.namespace,
216                         mappingResolved.packageName);
217             }
218         }
219         return namespaceToPackage;
220     }
221
222     static Pattern namespaceMappingPattern = Pattern.compile("(.+)"
223             + NAMESPACE_TO_PACKAGE_DIVIDER + "(.+)");
224
225     private static NamespaceMapping extractNamespaceMapping(String mapping) {
226         Matcher matcher = namespaceMappingPattern.matcher(mapping);
227         Preconditions
228                 .checkArgument(matcher.matches(), String.format("Namespace to package mapping:%s is in invalid " +
229                         "format, requested format is: %s", mapping, namespaceMappingPattern));
230         return new NamespaceMapping(matcher.group(1), matcher.group(2));
231     }
232
233     private static class NamespaceMapping {
234         public NamespaceMapping(String namespace, String packagename) {
235             this.namespace = namespace;
236             this.packageName = packagename;
237         }
238
239         private final String namespace, packageName;
240     }
241
242     @Override
243     public void setResourceBaseDir(File resourceDir) {
244         this.resourceBaseDir = resourceDir;
245     }
246
247     @Override
248     public void setMavenProject(MavenProject project) {
249         this.projectBaseDir = project.getBasedir();
250
251         if (logger != null)
252             logger.debug(getClass().getCanonicalName(), " project base dir: ",
253                     projectBaseDir);
254     }
255
256     @VisibleForTesting
257     static class GeneratedFilesTracker {
258         private final Set<File> files = Sets.newHashSet();
259
260         void addFile(File file) {
261             if (files.contains(file)) {
262                 List<File> undeletedFiles = Lists.newArrayList();
263                 for (File presentFile : files) {
264                     if (presentFile.delete() == false) {
265                         undeletedFiles.add(presentFile);
266                     }
267                 }
268                 if (undeletedFiles.isEmpty() == false) {
269                     logger.error(
270                             "Illegal state occurred: Unable to delete already generated files, undeleted files: {}",
271                             undeletedFiles);
272                 }
273                 throw new IllegalStateException(
274                         "Name conflict in generated files, file" + file
275                                 + " present twice");
276             }
277             files.add(file);
278         }
279
280         void addFile(Collection<File> files) {
281             for (File file : files) {
282                 addFile(file);
283             }
284         }
285
286         public Set<File> getFiles() {
287             return files;
288         }
289     }
290 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.