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