Initial code drop of yang model driven configuration system
[controller.git] / opendaylight / config / yang-jmx-generator / src / main / java / org / opendaylight / controller / config / yangjmxgenerator / RuntimeBeanEntry.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;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
12
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Deque;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.LinkedList;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23
24 import org.opendaylight.controller.config.yangjmxgenerator.attribute.AttributeIfc;
25 import org.opendaylight.controller.config.yangjmxgenerator.attribute.JavaAttribute;
26 import org.opendaylight.controller.config.yangjmxgenerator.attribute.ListAttribute;
27 import org.opendaylight.controller.config.yangjmxgenerator.attribute.TOAttribute;
28 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.FullyQualifiedNameHelper;
29 import org.opendaylight.controller.config.yangjmxgenerator.plugin.util.NameConflictException;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
32 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
33 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
34 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
36 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.Module;
39 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
40 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.UnknownSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.UsesNode;
43
44 import com.google.common.annotations.VisibleForTesting;
45 import com.google.common.base.Optional;
46 import com.google.common.collect.Lists;
47
48 /**
49  * Holds information about runtime bean to be generated. There are two kinds of
50  * RuntimeBeanEntry instances: if isRoot flag is set to true, this bean
51  * represents state that must be present at time of configuration module
52  * instantiation. Root RB must have depthLevel set to 0 and cannot have
53  * children. There might be other RBs defined in yang, but no other RB can have
54  * isRoot set to true. At least one RB must be root and all other RBs must be
55  * lined via children so that a tree with all beans can be created.
56  */
57 public class RuntimeBeanEntry {
58     private final String packageName;
59     private final String yangName, javaNamePrefix;
60     private final boolean isRoot;
61     private final Optional<String> keyYangName, keyJavaName;
62     private final Map<String, AttributeIfc> attributeMap;
63     private final List<RuntimeBeanEntry> children;
64     private final Set<Rpc> rpcs;
65
66     @VisibleForTesting
67     public RuntimeBeanEntry(String packageName,
68             DataSchemaNode nodeForReporting, String yangName,
69             String javaNamePrefix, boolean isRoot,
70             Optional<String> keyYangName, List<AttributeIfc> attributes,
71             List<RuntimeBeanEntry> children, Set<Rpc> rpcs) {
72
73         checkArgument(isRoot == false || keyYangName.isPresent() == false,
74                 "Root RuntimeBeanEntry must not have key " + "set");
75         this.packageName = packageName;
76         this.isRoot = isRoot;
77         this.yangName = yangName;
78         this.javaNamePrefix = javaNamePrefix;
79         this.children = Collections.unmodifiableList(children);
80         this.rpcs = Collections.unmodifiableSet(rpcs);
81
82         this.keyYangName = keyYangName;
83         Map<String, AttributeIfc> map = new HashMap<>();
84
85         for (AttributeIfc a : attributes) {
86             checkState(map.containsKey(a.getAttributeYangName()) == false,
87                     "Attribute already defined: " + a.getAttributeYangName()
88                             + " in " + nodeForReporting);
89             map.put(a.getAttributeYangName(), a);
90         }
91
92         if (keyYangName.isPresent()) {
93             AttributeIfc keyJavaName = map.get(keyYangName.get());
94             checkArgument(keyJavaName != null, "Key " + keyYangName.get()
95                     + " not found in attribute " + "list " + attributes
96                     + " in " + nodeForReporting);
97             this.keyJavaName = Optional
98                     .of(keyJavaName.getUpperCaseCammelCase());
99         } else {
100             keyJavaName = Optional.absent();
101         }
102         attributeMap = Collections.unmodifiableMap(map);
103     }
104
105     /**
106      * @return map containing all class names as key, extracted RuntimeBeans as
107      *         values. If more than zero values is returned, exactly one
108      *         RuntimeBeanEntry will have isRoot set to true, even if yang does
109      *         not contain special configuration for it.
110      */
111     public static Map<String, RuntimeBeanEntry> extractClassNameToRuntimeBeanMap(
112             String packageName, ChoiceCaseNode container,
113             String moduleYangName, TypeProviderWrapper typeProviderWrapper,
114             String javaNamePrefix, Module currentModule) {
115
116         Map<QName, Set<RpcDefinition>> identitiesToRpcs = getIdentitiesToRpcs(currentModule);
117
118         AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
119                 packageName, container, typeProviderWrapper, currentModule,
120                 identitiesToRpcs);
121         Map<String, RuntimeBeanEntry> result = new HashMap<>();
122
123         List<AttributeIfc> attributes;
124         Set<Rpc> rpcs;
125         if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
126             attributes = attributesRpcsAndRuntimeBeans.getAttributes();
127             rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
128         } else {
129             // create artificial root if not defined in yang
130             attributes = Collections.emptyList();
131             rpcs = Collections.emptySet();
132         }
133         RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
134                 container, moduleYangName, attributes, javaNamePrefix,
135                 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
136
137         Deque<RuntimeBeanEntry> stack = new LinkedList<>();
138         stack.add(rootRuntimeBeanEntry);
139
140         while (stack.isEmpty() == false) {
141             RuntimeBeanEntry first = stack.pollFirst();
142             if (result.containsKey(first.getJavaNameOfRuntimeMXBean())) {
143                 throw new NameConflictException(
144                         first.getJavaNameOfRuntimeMXBean(), null, null);
145             }
146             result.put(first.getJavaNameOfRuntimeMXBean(), first);
147             stack.addAll(first.getChildren());
148         }
149         return result;
150     }
151
152     private static Map<QName/* of identity */, Set<RpcDefinition>> getIdentitiesToRpcs(
153             Module currentModule) {
154         // currently only looks for local identities (found in currentModule)
155         Map<QName, Set<RpcDefinition>> result = new HashMap<>();
156         for (IdentitySchemaNode identity : currentModule.getIdentities()) {
157             // add all
158             result.put(identity.getQName(), new HashSet<RpcDefinition>());
159         }
160
161         for (RpcDefinition rpc : currentModule.getRpcs()) {
162             ContainerSchemaNode input = rpc.getInput();
163             for (UsesNode uses : input.getUses()) {
164
165                 if (uses.getGroupingPath().getPath().size() != 1)
166                     continue;
167
168                 // check grouping path
169                 QName qname = uses.getGroupingPath().getPath().get(0);
170                 if (false == qname
171                         .equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME))
172                     continue;
173
174                 for (SchemaNode refinedNode : uses.getRefines().values()) {
175
176                     for (UnknownSchemaNode unknownSchemaNode : refinedNode
177                             .getUnknownSchemaNodes()) {
178                         if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
179                                 .equals(unknownSchemaNode.getNodeType())) {
180                             String localIdentityName = unknownSchemaNode
181                                     .getNodeParameter();
182                             QName identityQName = new QName(
183                                     currentModule.getNamespace(),
184                                     currentModule.getRevision(),
185                                     localIdentityName);
186                             Set<RpcDefinition> rpcDefinitions = result
187                                     .get(identityQName);
188                             if (rpcDefinitions == null) {
189                                 throw new IllegalArgumentException(
190                                         "Identity referenced by rpc not found. Identity:"
191                                                 + localIdentityName + " , rpc "
192                                                 + rpc);
193                             }
194                             rpcDefinitions.add(rpc);
195                         }
196                     }
197
198                 }
199             }
200         }
201         return result;
202     }
203
204     /**
205      * Get direct descendants of this subtree, together with attributes defined
206      * in subtree.
207      */
208     private static AttributesRpcsAndRuntimeBeans extractSubtree(
209             String packageName, DataNodeContainer subtree,
210             TypeProviderWrapper typeProviderWrapper, Module currentModule,
211             Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
212
213         List<AttributeIfc> attributes = Lists.newArrayList();
214         // List<JavaAttribute> javaAttributes = new ArrayList<>();
215         // List<TOAttribute> toAttributes = new ArrayList<>();
216         List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
217         for (DataSchemaNode child : subtree.getChildNodes()) {
218             // child leaves can be java attributes, TO attributes, or child
219             // runtime beans
220             if (child instanceof LeafSchemaNode) {
221                 // just save the attribute
222                 LeafSchemaNode leaf = (LeafSchemaNode) child;
223                 attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
224             } else if (child instanceof ContainerSchemaNode) {
225                 ContainerSchemaNode container = (ContainerSchemaNode) child;
226                 // this can be either TO or hierarchical RB
227                 TOAttribute toAttribute = TOAttribute.create(container,
228                         typeProviderWrapper);
229                 attributes.add(toAttribute);
230             } else if (child instanceof ListSchemaNode) {
231                 if (isInnerStateBean(child)) {
232                     ListSchemaNode listSchemaNode = (ListSchemaNode) child;
233                     RuntimeBeanEntry hierarchicalChild = createHierarchical(
234                             packageName, listSchemaNode, typeProviderWrapper,
235                             currentModule, identitiesToRpcs);
236                     runtimeBeanEntries.add(hierarchicalChild);
237                 } else /* ordinary list attribute */{
238                     ListAttribute listAttribute = ListAttribute.create(
239                             (ListSchemaNode) child, typeProviderWrapper);
240                     attributes.add(listAttribute);
241                 }
242
243             } else {
244                 throw new IllegalStateException("Unknown running-data node "
245                         + child + " , " + "" + "expected leaf or container");
246             }
247         }
248         Set<Rpc> rpcs = new HashSet<>();
249         SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
250         for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
251                 .getUnknownSchemaNodes()) {
252             if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
253                     .equals(unknownSchemaNode.getNodeType())) {
254                 String localIdentityName = unknownSchemaNode.getNodeParameter();
255                 QName identityQName = new QName(currentModule.getNamespace(),
256                         currentModule.getRevision(), localIdentityName);
257                 Set<RpcDefinition> rpcDefinitions = identitiesToRpcs
258                         .get(identityQName);
259                 if (rpcDefinitions == null) {
260                     throw new IllegalArgumentException("Cannot find identity "
261                             + localIdentityName + " to be used as "
262                             + "context reference when resolving "
263                             + unknownSchemaNode);
264                 }
265                 // convert RpcDefinition to Rpc
266                 for (RpcDefinition rpcDefinition : rpcDefinitions) {
267                     String name = ModuleMXBeanEntry
268                             .findJavaParameter(rpcDefinition);
269                     String returnType;
270                     if (rpcDefinition.getOutput() == null
271                             || rpcDefinition.getOutput().getChildNodes().size() == 0) {
272                         returnType = "void";
273                     } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
274                         DataSchemaNode returnDSN = rpcDefinition.getOutput()
275                                 .getChildNodes().iterator().next();
276                         checkArgument(
277                                 returnDSN instanceof LeafSchemaNode,
278                                 "Unexpected type of rpc return type. "
279                                         + "Currently only leafs and empty output nodes are supported, got "
280                                         + returnDSN);
281                         LeafSchemaNode returnLeaf = (LeafSchemaNode) returnDSN;
282                         // We currently expect leaf defined in output element in yang to be named result
283                         // FIXME: value of result is fully qualified name - should be extended to accept TOs
284                         String localName = returnLeaf.getQName().getLocalName();
285                         checkArgument(
286                                 localName.equals("result"),
287                                 "Unexpected name of leaf in output element, expected leaf named result, was %s at %s",
288                                 localName, currentModule.getName());
289
290                         returnType = typeProviderWrapper.getType(returnLeaf)
291                                 .getFullyQualifiedName();
292                     } else {
293                         throw new IllegalArgumentException(
294                                 "More than one child node in rpc output is not supported. "
295                                         + "Error occured in " + rpcDefinition);
296                     }
297                     List<JavaAttribute> parameters = new ArrayList<>();
298                     for (DataSchemaNode childNode : rpcDefinition.getInput()
299                             .getChildNodes()) {
300                         if (childNode.isAddedByUses() == false) { // skip
301                                                                   // refined
302                                                                   // context-instance
303                             checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
304                                     + "Currently only leafs and empty output nodes are supported, got " + childNode);
305                             JavaAttribute javaAttribute = new JavaAttribute(
306                                     (LeafSchemaNode) childNode,
307                                     typeProviderWrapper);
308                             parameters.add(javaAttribute);
309                         }
310                     }
311                     Rpc newRpc = new Rpc(returnType, name, rpcDefinition
312                             .getQName().getLocalName(), parameters);
313                     rpcs.add(newRpc);
314                 }
315             }
316         }
317         return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
318                 attributes, rpcs);
319     }
320
321     private static boolean isInnerStateBean(DataSchemaNode child) {
322         for (UnknownSchemaNode unknownSchemaNode : child
323                 .getUnknownSchemaNodes()) {
324             if (unknownSchemaNode.getNodeType().equals(
325                     ConfigConstants.INNER_STATE_BEAN_EXTENSION_QNAME))
326                 return true;
327         }
328         return false;
329     }
330
331     private static RuntimeBeanEntry createHierarchical(String packageName,
332             ListSchemaNode listSchemaNode,
333             TypeProviderWrapper typeProviderWrapper, Module currentModule,
334             Map<QName, Set<RpcDefinition>> identitiesToRpcs) {
335
336         // supported are numeric types, strings, enums
337         // get all attributes
338         AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
339                 packageName, listSchemaNode, typeProviderWrapper,
340                 currentModule, identitiesToRpcs);
341
342         Optional<String> keyYangName;
343         if (listSchemaNode.getKeyDefinition().size() == 0) {
344             keyYangName = Optional.absent();
345         } else if (listSchemaNode.getKeyDefinition().size() == 1) {
346             // key must be either null or one of supported key types
347             QName keyQName = listSchemaNode.getKeyDefinition().iterator()
348                     .next();
349             keyYangName = Optional.of(keyQName.getLocalName());
350
351         } else {
352             throw new IllegalArgumentException(
353                     "More than one key is not supported in " + listSchemaNode);
354         }
355
356         String javaNamePrefix = ModuleMXBeanEntry
357                 .findJavaNamePrefix(listSchemaNode);
358
359         RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
360                 listSchemaNode, listSchemaNode.getQName().getLocalName(),
361                 javaNamePrefix, false, keyYangName,
362                 attributesRpcsAndRuntimeBeans.getAttributes(),
363                 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
364                 attributesRpcsAndRuntimeBeans.getRpcs());
365
366         return rbFromAttributes;
367     }
368
369     private static RuntimeBeanEntry createRoot(String packageName,
370             DataSchemaNode nodeForReporting, String attributeYangName,
371             List<AttributeIfc> attributes, String javaNamePrefix,
372             List<RuntimeBeanEntry> children, Set<Rpc> rpcs) {
373         return new RuntimeBeanEntry(packageName, nodeForReporting,
374                 attributeYangName, javaNamePrefix, true,
375                 Optional.<String> absent(), attributes, children, rpcs);
376     }
377
378     public boolean isRoot() {
379         return isRoot;
380     }
381
382     public Optional<String> getKeyYangName() {
383         return keyYangName;
384     }
385
386     public Optional<String> getKeyJavaName() {
387         return keyJavaName;
388     }
389
390     public Collection<AttributeIfc> getAttributes() {
391         return attributeMap.values();
392     }
393
394     public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
395         return attributeMap;
396     }
397
398     public String getYangName() {
399         return yangName;
400     }
401
402     public String getPackageName() {
403         return packageName;
404     }
405
406     public String getJavaNamePrefix() {
407         return javaNamePrefix;
408     }
409
410     public List<RuntimeBeanEntry> getChildren() {
411         return children;
412     }
413
414     public Set<Rpc> getRpcs() {
415         return rpcs;
416     }
417
418     private static class AttributesRpcsAndRuntimeBeans {
419         private final List<RuntimeBeanEntry> runtimeBeanEntries;
420         private final List<AttributeIfc> attributes;
421         private final Set<Rpc> rpcs;
422
423         public AttributesRpcsAndRuntimeBeans(
424                 List<RuntimeBeanEntry> runtimeBeanEntries,
425                 List<AttributeIfc> attributes, Set<Rpc> rpcs) {
426             this.runtimeBeanEntries = runtimeBeanEntries;
427             this.attributes = attributes;
428             this.rpcs = rpcs;
429         }
430
431         private List<AttributeIfc> getAttributes() {
432             return attributes;
433         }
434
435         public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
436             return runtimeBeanEntries;
437         }
438
439         public boolean isEmpty() {
440             return attributes.isEmpty() && rpcs.isEmpty();
441         }
442
443         private Set<Rpc> getRpcs() {
444             return rpcs;
445         }
446     }
447
448     public static class Rpc {
449         private final String name;
450         private final List<JavaAttribute> parameters;
451         private final String returnType;
452         private final String yangName;
453
454         Rpc(String returnType, String name, String yangName,
455                 List<JavaAttribute> parameters) {
456             this.returnType = returnType;
457             this.name = name;
458             this.parameters = parameters;
459             this.yangName = yangName;
460         }
461
462         public String getYangName() {
463             return yangName;
464         }
465
466         public String getName() {
467             return name;
468         }
469
470         public List<JavaAttribute> getParameters() {
471             return parameters;
472         }
473
474         public String getReturnType() {
475             return returnType;
476         }
477     }
478
479     private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
480
481     public String getJavaNameOfRuntimeMXBean() {
482         return getJavaNameOfRuntimeMXBean(javaNamePrefix);
483     }
484
485     public String getFullyQualifiedName(String typeName) {
486         return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
487                 typeName);
488     }
489
490     private static String getJavaNameOfRuntimeMXBean(String javaNamePrefix) {
491         return javaNamePrefix + MXBEAN_SUFFIX;
492     }
493
494     @Override
495     public String toString() {
496         return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
497                 + yangName + '\'' + ", packageName='" + packageName + '\''
498                 + ", keyYangName=" + keyYangName + '}';
499     }
500 }