2 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.config.yangjmxgenerator;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Preconditions.checkState;
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;
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;
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.
66 public class RuntimeBeanEntry {
68 private static final Function<SchemaNode, QName> QNAME_FROM_NODE = new Function<SchemaNode, QName>() {
70 public QName apply(final SchemaNode input) {
71 return input.getQName();
75 private static final Function<UnknownSchemaNode, String> UNKNOWN_NODE_TO_STRING = new Function<UnknownSchemaNode, String>() {
77 public String apply(final UnknownSchemaNode input) {
78 return input.getQName().getLocalName() + input.getNodeParameter();
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;
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) {
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);
106 this.keyYangName = keyYangName;
107 Map<String, AttributeIfc> map = new HashMap<>();
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);
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());
124 keyJavaName = Optional.absent();
126 attributeMap = Collections.unmodifiableMap(map);
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.
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) {
141 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
142 packageName, container, typeProviderWrapper, currentModule,
144 Map<String, RuntimeBeanEntry> result = new HashMap<>();
146 List<AttributeIfc> attributes;
148 if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
149 attributes = attributesRpcsAndRuntimeBeans.getAttributes();
150 rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
152 // create artificial root if not defined in yang
153 attributes = Collections.emptyList();
154 rpcs = Collections.emptySet();
156 RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
157 container, moduleYangName, attributes, javaNamePrefix,
158 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
160 Deque<RuntimeBeanEntry> stack = new LinkedList<>();
161 stack.add(rootRuntimeBeanEntry);
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);
169 result.put(first.getJavaNameOfRuntimeMXBean(), first);
170 stack.addAll(first.getChildren());
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()) {
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));
183 for (RpcDefinition rpc : currentModule.getRpcs()) {
184 ContainerSchemaNode input = rpc.getInput();
186 for (UsesNode uses : input.getUses()) {
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)) {
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
202 QName identityQName = QName.create(
203 currentModule.getNamespace(),
204 currentModule.getRevision(),
206 Preconditions.checkArgument(allIdentitiesInModule.contains(identityQName),
207 "Identity referenced by rpc not found. Identity: %s, rpc: %s", localIdentityName, rpc);
208 result.put(identityQName, rpc);
220 * Get direct descendants of this subtree, together with attributes defined
223 private static AttributesRpcsAndRuntimeBeans extractSubtree(
224 final String packageName, final DataNodeContainer subtree,
225 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
226 final SchemaContext ctx) {
228 Multimap<QName, RpcDefinition> identitiesToRpcs = getIdentitiesToRpcs(ctx);
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
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,
251 runtimeBeanEntries.add(hierarchicalChild);
252 } else /* ordinary list attribute */{
253 ListAttribute listAttribute = ListAttribute.create(
254 (ListSchemaNode) child, typeProviderWrapper, packageName);
255 attributes.add(listAttribute);
258 } else if (child instanceof LeafListSchemaNode) {
259 ListAttribute listAttribute = ListAttribute.create(
260 (LeafListSchemaNode) child, typeProviderWrapper);
261 attributes.add(listAttribute);
263 throw new IllegalStateException("Unexpected running-data node "
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);
291 throw new IllegalArgumentException(
292 "More than one child node in rpc output is not supported. "
293 + "Error occured in " + rpcDefinition);
295 List<JavaAttribute> parameters = new ArrayList<>();
296 for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput()
298 if (childNode.isAddedByUses() == false) { // skip
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);
309 Rpc newRpc = new Rpc(returnType, name, rpcDefinition
310 .getQName().getLocalName(), parameters);
315 return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
320 * Find "proper" qname of unknown node in case it comes from a grouping
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);
332 return identityQName;
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);
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);
349 throw new IllegalStateException("Unknown output data node " + child + " for rpc");
353 private static Collection<DataSchemaNode> sortAttributes(final Collection<DataSchemaNode> childNodes) {
354 final TreeSet<DataSchemaNode> dataSchemaNodes = new TreeSet<>(new Comparator<DataSchemaNode>() {
356 public int compare(final DataSchemaNode o1, final DataSchemaNode o2) {
357 return o1.getQName().getLocalName().compareTo(o2.getQName().getLocalName());
360 dataSchemaNodes.addAll(childNodes);
361 return dataSchemaNodes;
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)) {
375 private static RuntimeBeanEntry createHierarchical(final String packageName,
376 final ListSchemaNode listSchemaNode,
377 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
378 final SchemaContext ctx) {
380 // supported are numeric types, strings, enums
381 // get all attributes
382 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
383 packageName, listSchemaNode, typeProviderWrapper,
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()
393 keyYangName = Optional.of(keyQName.getLocalName());
396 throw new IllegalArgumentException(
397 "More than one key is not supported in " + listSchemaNode);
400 String javaNamePrefix = TypeProviderWrapper
401 .findJavaNamePrefix(listSchemaNode);
403 RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
404 listSchemaNode, listSchemaNode.getQName().getLocalName(),
405 javaNamePrefix, false, keyYangName,
406 attributesRpcsAndRuntimeBeans.getAttributes(),
407 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
408 attributesRpcsAndRuntimeBeans.getRpcs());
410 return rbFromAttributes;
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);
422 public boolean isRoot() {
426 public Optional<String> getKeyYangName() {
430 public Optional<String> getKeyJavaName() {
434 public Collection<AttributeIfc> getAttributes() {
435 return attributeMap.values();
438 public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
442 public String getYangName() {
446 public String getPackageName() {
450 public String getJavaNamePrefix() {
451 return javaNamePrefix;
454 public List<RuntimeBeanEntry> getChildren() {
458 public Set<Rpc> getRpcs() {
462 private static class AttributesRpcsAndRuntimeBeans {
463 private final List<RuntimeBeanEntry> runtimeBeanEntries;
464 private final List<AttributeIfc> attributes;
465 private final Set<Rpc> rpcs;
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;
475 private List<AttributeIfc> getAttributes() {
479 public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
480 return runtimeBeanEntries;
483 public boolean isEmpty() {
484 return attributes.isEmpty() && rpcs.isEmpty();
487 private Set<Rpc> getRpcs() {
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;
498 Rpc(final AttributeIfc returnType, final String name, final String yangName,
499 final List<JavaAttribute> parameters) {
500 this.returnType = returnType;
502 this.parameters = parameters;
503 this.yangName = yangName;
506 public String getYangName() {
510 public String getName() {
514 public List<JavaAttribute> getParameters() {
518 public AttributeIfc getReturnType() {
523 private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
525 public String getJavaNameOfRuntimeMXBean() {
526 return getJavaNameOfRuntimeMXBean(javaNamePrefix);
529 public String getFullyQualifiedName(final String typeName) {
530 return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
534 private static String getJavaNameOfRuntimeMXBean(final String javaNamePrefix) {
535 return javaNamePrefix + MXBEAN_SUFFIX;
539 public String toString() {
540 return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
541 + yangName + '\'' + ", packageName='" + packageName + '\''
542 + ", keyYangName=" + keyYangName + '}';