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;
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;
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;
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.
65 public class RuntimeBeanEntry {
67 private static final Function<UnknownSchemaNode, String> UNKNOWN_NODE_TO_STRING =
68 input -> input.getQName().getLocalName() + input.getNodeParameter();
70 private final String packageName;
71 private final String yangName, javaNamePrefix;
72 private final boolean isRoot;
73 private final Optional<String> keyYangName, keyJavaName;
74 private final Map<String, AttributeIfc> attributeMap;
75 private final List<RuntimeBeanEntry> children;
76 private final Set<Rpc> rpcs;
79 RuntimeBeanEntry(final String packageName,
80 final DataNodeContainer nodeForReporting, final String yangName,
81 final String javaNamePrefix, final boolean isRoot,
82 final Optional<String> keyYangName, final List<AttributeIfc> attributes,
83 final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
85 checkArgument(isRoot == false || keyYangName.isPresent() == false,
86 "Root RuntimeBeanEntry must not have key set");
87 this.packageName = packageName;
89 this.yangName = yangName;
90 this.javaNamePrefix = javaNamePrefix;
91 this.children = Collections.unmodifiableList(children);
92 this.rpcs = Collections.unmodifiableSet(rpcs);
94 this.keyYangName = keyYangName;
95 Map<String, AttributeIfc> map = new HashMap<>();
97 for (AttributeIfc a : attributes) {
98 checkState(map.containsKey(a.getAttributeYangName()) == false,
99 "Attribute already defined: %s in %s", a.getAttributeYangName(), nodeForReporting);
100 map.put(a.getAttributeYangName(), a);
103 if (keyYangName.isPresent()) {
104 AttributeIfc keyJavaName = map.get(keyYangName.get());
105 checkArgument(keyJavaName != null, "Key %s not found in attribute list %s in %s", keyYangName.get(),
106 attributes, nodeForReporting);
107 this.keyJavaName = Optional
108 .of(keyJavaName.getUpperCaseCammelCase());
110 keyJavaName = Optional.absent();
112 attributeMap = Collections.unmodifiableMap(map);
116 * @return map containing all class names as key, extracted RuntimeBeans as
117 * values. If more than zero values is returned, exactly one
118 * RuntimeBeanEntry will have isRoot set to true, even if yang does
119 * not contain special configuration for it.
121 public static Map<String, RuntimeBeanEntry> extractClassNameToRuntimeBeanMap(
122 final String packageName, final DataNodeContainer container,
123 final String moduleYangName, final TypeProviderWrapper typeProviderWrapper,
124 final String javaNamePrefix, final Module currentModule, final SchemaContext schemaContext) {
127 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
128 packageName, container, typeProviderWrapper, currentModule,
130 Map<String, RuntimeBeanEntry> result = new HashMap<>();
132 List<AttributeIfc> attributes;
134 if (attributesRpcsAndRuntimeBeans.isEmpty() == false) {
135 attributes = attributesRpcsAndRuntimeBeans.getAttributes();
136 rpcs = attributesRpcsAndRuntimeBeans.getRpcs();
138 // create artificial root if not defined in yang
139 attributes = Collections.emptyList();
140 rpcs = Collections.emptySet();
142 RuntimeBeanEntry rootRuntimeBeanEntry = createRoot(packageName,
143 container, moduleYangName, attributes, javaNamePrefix,
144 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(), rpcs);
146 Deque<RuntimeBeanEntry> stack = new LinkedList<>();
147 stack.add(rootRuntimeBeanEntry);
149 while (stack.isEmpty() == false) {
150 RuntimeBeanEntry first = stack.pollFirst();
151 if (result.containsKey(first.getJavaNameOfRuntimeMXBean())) {
152 throw new NameConflictException(
153 first.getJavaNameOfRuntimeMXBean(), null, null);
155 result.put(first.getJavaNameOfRuntimeMXBean(), first);
156 stack.addAll(first.getChildren());
161 private static Multimap<QName/* of identity */, RpcDefinition> getIdentitiesToRpcs(
162 final SchemaContext schemaCtx) {
163 Multimap<QName, RpcDefinition> result = HashMultimap.create();
164 for (Module currentModule : schemaCtx.getModules()) {
166 // Find all identities in current module for later identity->rpc mapping
167 Set<QName> allIdentitiesInModule =
168 Sets.newHashSet(Collections2.transform(currentModule.getIdentities(), SchemaNode::getQName));
170 for (RpcDefinition rpc : currentModule.getRpcs()) {
171 ContainerSchemaNode input = rpc.getInput();
173 for (UsesNode uses : input.getUses()) {
175 // Check if the rpc is config rpc by looking for input argument rpc-context-ref
176 Iterator<QName> pathFromRoot = uses.getGroupingPath().getPathFromRoot().iterator();
177 if (!pathFromRoot.hasNext() ||
178 !pathFromRoot.next().equals(ConfigConstants.RPC_CONTEXT_REF_GROUPING_QNAME)) {
182 for (SchemaNode refinedNode : uses.getRefines().values()) {
183 for (UnknownSchemaNode unknownSchemaNode : refinedNode
184 .getUnknownSchemaNodes()) {
185 if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
186 .equals(unknownSchemaNode.getNodeType())) {
187 String localIdentityName = unknownSchemaNode
189 QName identityQName = QName.create(
190 currentModule.getNamespace(),
191 currentModule.getRevision(),
193 Preconditions.checkArgument(allIdentitiesInModule.contains(identityQName),
194 "Identity referenced by rpc not found. Identity: %s, rpc: %s", localIdentityName, rpc);
195 result.put(identityQName, rpc);
207 * Get direct descendants of this subtree, together with attributes defined
210 private static AttributesRpcsAndRuntimeBeans extractSubtree(
211 final String packageName, final DataNodeContainer subtree,
212 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
213 final SchemaContext ctx) {
215 Multimap<QName, RpcDefinition> identitiesToRpcs = getIdentitiesToRpcs(ctx);
217 List<AttributeIfc> attributes = Lists.newArrayList();
218 List<RuntimeBeanEntry> runtimeBeanEntries = new ArrayList<>();
219 for (DataSchemaNode child : subtree.getChildNodes()) {
220 // child leaves can be java attributes, TO attributes, or child
222 if (child instanceof LeafSchemaNode) {
223 // just save the attribute
224 LeafSchemaNode leaf = (LeafSchemaNode) child;
225 attributes.add(new JavaAttribute(leaf, typeProviderWrapper));
226 } else if (child instanceof ContainerSchemaNode) {
227 ContainerSchemaNode container = (ContainerSchemaNode) child;
228 // this can be either TO or hierarchical RB
229 TOAttribute toAttribute = TOAttribute.create(container,
230 typeProviderWrapper, packageName);
231 attributes.add(toAttribute);
232 } else if (child instanceof ListSchemaNode) {
233 if (isInnerStateBean(child)) {
234 ListSchemaNode listSchemaNode = (ListSchemaNode) child;
235 RuntimeBeanEntry hierarchicalChild = createHierarchical(
236 packageName, listSchemaNode, typeProviderWrapper,
238 runtimeBeanEntries.add(hierarchicalChild);
239 } else /* ordinary list attribute */{
240 ListAttribute listAttribute = ListAttribute.create(
241 (ListSchemaNode) child, typeProviderWrapper, packageName);
242 attributes.add(listAttribute);
245 } else if (child instanceof LeafListSchemaNode) {
246 ListAttribute listAttribute = ListAttribute.create(
247 (LeafListSchemaNode) child, typeProviderWrapper);
248 attributes.add(listAttribute);
250 throw new IllegalStateException("Unexpected running-data node "
254 Set<Rpc> rpcs = new HashSet<>();
255 SchemaNode subtreeSchemaNode = (SchemaNode) subtree;
256 for (UnknownSchemaNode unknownSchemaNode : subtreeSchemaNode
257 .getUnknownSchemaNodes()) {
258 if (ConfigConstants.RPC_CONTEXT_INSTANCE_EXTENSION_QNAME
259 .equals(unknownSchemaNode.getNodeType())) {
260 String localIdentityName = unknownSchemaNode.getNodeParameter();
261 QName identityQName = unknownSchemaNode.isAddedByUses() ?
262 findQNameFromGrouping(subtree, ctx, unknownSchemaNode, localIdentityName) :
263 QName.create(currentModule.getNamespace(), currentModule.getRevision(), localIdentityName);
264 // convert RpcDefinition to Rpc
265 for (RpcDefinition rpcDefinition : identitiesToRpcs.get(identityQName)) {
266 String name = TypeProviderWrapper
267 .findJavaParameter(rpcDefinition);
268 AttributeIfc returnType;
269 if (rpcDefinition.getOutput() == null
270 || rpcDefinition.getOutput().getChildNodes().isEmpty()) {
271 returnType = VoidAttribute.getInstance();
272 } else if (rpcDefinition.getOutput().getChildNodes().size() == 1) {
273 DataSchemaNode returnDSN = rpcDefinition.getOutput()
274 .getChildNodes().iterator().next();
275 returnType = getReturnTypeAttribute(returnDSN, typeProviderWrapper, packageName);
278 throw new IllegalArgumentException(
279 "More than one child node in rpc output is not supported. "
280 + "Error occured in " + rpcDefinition);
282 List<JavaAttribute> parameters = new ArrayList<>();
283 for (DataSchemaNode childNode : sortAttributes(rpcDefinition.getInput()
285 if (childNode.isAddedByUses() == false) { // skip
288 checkArgument(childNode instanceof LeafSchemaNode, "Unexpected type of rpc input type. "
289 + "Currently only leafs and empty output nodes are supported, got " + childNode);
290 JavaAttribute javaAttribute = new JavaAttribute(
291 (LeafSchemaNode) childNode,
292 typeProviderWrapper);
293 parameters.add(javaAttribute);
296 Rpc newRpc = new Rpc(returnType, name, rpcDefinition
297 .getQName().getLocalName(), parameters);
302 return new AttributesRpcsAndRuntimeBeans(runtimeBeanEntries,
307 * Find "proper" qname of unknown node in case it comes from a grouping
309 private static QName findQNameFromGrouping(final DataNodeContainer subtree, final SchemaContext ctx, final UnknownSchemaNode unknownSchemaNode, final String localIdentityName) {
310 QName identityQName = null;
311 for (UsesNode usesNode : subtree.getUses()) {
312 SchemaNode dataChildByName = SchemaContextUtil.findDataSchemaNode(ctx, usesNode.getGroupingPath());
313 Module m = SchemaContextUtil.findParentModule(ctx, dataChildByName);
314 List<UnknownSchemaNode> unknownSchemaNodes = dataChildByName.getUnknownSchemaNodes();
315 if(Collections2.transform(unknownSchemaNodes, UNKNOWN_NODE_TO_STRING).contains(UNKNOWN_NODE_TO_STRING.apply(unknownSchemaNode))) {
316 identityQName = QName.create(dataChildByName.getQName(), localIdentityName);
319 return identityQName;
322 private static AttributeIfc getReturnTypeAttribute(final DataSchemaNode child, final TypeProviderWrapper typeProviderWrapper,
323 final String packageName) {
324 if (child instanceof LeafSchemaNode) {
325 LeafSchemaNode leaf = (LeafSchemaNode) child;
326 return new JavaAttribute(leaf, typeProviderWrapper);
327 } else if (child instanceof ContainerSchemaNode) {
328 ContainerSchemaNode container = (ContainerSchemaNode) child;
329 TOAttribute toAttribute = TOAttribute.create(container, typeProviderWrapper, packageName);
331 } else if (child instanceof ListSchemaNode) {
332 return ListAttribute.create((ListSchemaNode) child, typeProviderWrapper, packageName);
333 } else if (child instanceof LeafListSchemaNode) {
334 return ListAttribute.create((LeafListSchemaNode) child, typeProviderWrapper);
336 throw new IllegalStateException("Unknown output data node " + child + " for rpc");
340 private static Collection<DataSchemaNode> sortAttributes(final Collection<DataSchemaNode> childNodes) {
341 final TreeSet<DataSchemaNode> dataSchemaNodes =
342 new TreeSet<>(Comparator.comparing(o -> o.getQName().getLocalName()));
343 dataSchemaNodes.addAll(childNodes);
344 return dataSchemaNodes;
347 private static boolean isInnerStateBean(final DataSchemaNode child) {
348 for (UnknownSchemaNode unknownSchemaNode : child
349 .getUnknownSchemaNodes()) {
350 if (unknownSchemaNode.getNodeType().equals(
351 ConfigConstants.INNER_STATE_BEAN_EXTENSION_QNAME)) {
358 private static RuntimeBeanEntry createHierarchical(final String packageName,
359 final ListSchemaNode listSchemaNode,
360 final TypeProviderWrapper typeProviderWrapper, final Module currentModule,
361 final SchemaContext ctx) {
363 // supported are numeric types, strings, enums
364 // get all attributes
365 AttributesRpcsAndRuntimeBeans attributesRpcsAndRuntimeBeans = extractSubtree(
366 packageName, listSchemaNode, typeProviderWrapper,
369 Optional<String> keyYangName;
370 if (listSchemaNode.getKeyDefinition().isEmpty()) {
371 keyYangName = Optional.absent();
372 } else if (listSchemaNode.getKeyDefinition().size() == 1) {
373 // key must be either null or one of supported key types
374 QName keyQName = listSchemaNode.getKeyDefinition().iterator()
376 keyYangName = Optional.of(keyQName.getLocalName());
379 throw new IllegalArgumentException(
380 "More than one key is not supported in " + listSchemaNode);
383 String javaNamePrefix = TypeProviderWrapper
384 .findJavaNamePrefix(listSchemaNode);
386 RuntimeBeanEntry rbFromAttributes = new RuntimeBeanEntry(packageName,
387 listSchemaNode, listSchemaNode.getQName().getLocalName(),
388 javaNamePrefix, false, keyYangName,
389 attributesRpcsAndRuntimeBeans.getAttributes(),
390 attributesRpcsAndRuntimeBeans.getRuntimeBeanEntries(),
391 attributesRpcsAndRuntimeBeans.getRpcs());
393 return rbFromAttributes;
396 private static RuntimeBeanEntry createRoot(final String packageName,
397 final DataNodeContainer nodeForReporting, final String attributeYangName,
398 final List<AttributeIfc> attributes, final String javaNamePrefix,
399 final List<RuntimeBeanEntry> children, final Set<Rpc> rpcs) {
400 return new RuntimeBeanEntry(packageName, nodeForReporting,
401 attributeYangName, javaNamePrefix, true,
402 Optional.<String> absent(), attributes, children, rpcs);
405 public boolean isRoot() {
409 public Optional<String> getKeyYangName() {
413 public Optional<String> getKeyJavaName() {
417 public Collection<AttributeIfc> getAttributes() {
418 return attributeMap.values();
421 public Map<String, AttributeIfc> getYangPropertiesToTypesMap() {
425 public String getYangName() {
429 public String getPackageName() {
433 public String getJavaNamePrefix() {
434 return javaNamePrefix;
437 public List<RuntimeBeanEntry> getChildren() {
441 public Set<Rpc> getRpcs() {
445 private static class AttributesRpcsAndRuntimeBeans {
446 private final List<RuntimeBeanEntry> runtimeBeanEntries;
447 private final List<AttributeIfc> attributes;
448 private final Set<Rpc> rpcs;
450 public AttributesRpcsAndRuntimeBeans(
451 final List<RuntimeBeanEntry> runtimeBeanEntries,
452 final List<AttributeIfc> attributes, final Set<Rpc> rpcs) {
453 this.runtimeBeanEntries = runtimeBeanEntries;
454 this.attributes = attributes;
458 private List<AttributeIfc> getAttributes() {
462 public List<RuntimeBeanEntry> getRuntimeBeanEntries() {
463 return runtimeBeanEntries;
466 public boolean isEmpty() {
467 return attributes.isEmpty() && rpcs.isEmpty();
470 private Set<Rpc> getRpcs() {
475 public static class Rpc {
476 private final String name;
477 private final List<JavaAttribute> parameters;
478 private final AttributeIfc returnType;
479 private final String yangName;
481 Rpc(final AttributeIfc returnType, final String name, final String yangName,
482 final List<JavaAttribute> parameters) {
483 this.returnType = returnType;
485 this.parameters = parameters;
486 this.yangName = yangName;
489 public String getYangName() {
493 public String getName() {
497 public List<JavaAttribute> getParameters() {
501 public AttributeIfc getReturnType() {
506 private static final String MXBEAN_SUFFIX = "RuntimeMXBean";
508 public String getJavaNameOfRuntimeMXBean() {
509 return getJavaNameOfRuntimeMXBean(javaNamePrefix);
512 public String getFullyQualifiedName(final String typeName) {
513 return FullyQualifiedNameHelper.getFullyQualifiedName(packageName,
517 private static String getJavaNameOfRuntimeMXBean(final String javaNamePrefix) {
518 return javaNamePrefix + MXBEAN_SUFFIX;
522 public String toString() {
523 return "RuntimeBeanEntry{" + "isRoot=" + isRoot + ", yangName='"
524 + yangName + '\'' + ", packageName='" + packageName + '\''
525 + ", keyYangName=" + keyYangName + '}';