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