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