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