/* * * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * */ package org.opendaylight.controller.cluster.datastore.node; import com.google.common.base.Preconditions; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import org.opendaylight.controller.cluster.datastore.node.utils.NodeIdentifierFactory; import org.opendaylight.controller.protobuff.messages.common.NormalizedNodeMessages.Node; import org.opendaylight.yangtools.concepts.Identifiable; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; import org.opendaylight.yangtools.yang.data.impl.schema.Builders; import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeAttrBuilder; import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder; import org.opendaylight.yangtools.yang.model.api.AugmentationSchema; import org.opendaylight.yangtools.yang.model.api.AugmentationTarget; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkArgument; /** * NormalizedNodeBuilder is a builder that walks through a tree like structure and constructs a * NormalizedNode from it. *

* A large part of this code has been copied over from a similar class in sal-common-impl which was * originally supposed to convert a CompositeNode to NormalizedNode * * @param */ public abstract class NodeToNormalizedNodeBuilder implements Identifiable { private final T identifier; protected static final Logger logger = LoggerFactory .getLogger(NodeToNormalizedNodeBuilder.class); @Override public T getIdentifier() { return identifier; } ; protected NodeToNormalizedNodeBuilder(final T identifier) { super(); this.identifier = identifier; } /** * @return Should return true if the node that this operation corresponds to is a mixin */ public boolean isMixin() { return false; } /** * @return Should return true if the node that this operation corresponds to has a 'key' * associated with it. This is typically true for a list-item or leaf-list entry in yang */ public boolean isKeyedEntry() { return false; } protected Set getQNameIdentifiers() { return Collections.singleton(identifier.getNodeType()); } public abstract NodeToNormalizedNodeBuilder getChild( final PathArgument child); public abstract NodeToNormalizedNodeBuilder getChild(QName child); public abstract NormalizedNode normalize(QName nodeType, Node node); private static abstract class SimpleTypeNormalization extends NodeToNormalizedNodeBuilder { protected SimpleTypeNormalization(final T identifier) { super(identifier); } @Override public NormalizedNode normalize(final QName nodeType, final Node node) { checkArgument(node != null); return normalizeImpl(nodeType, node); } protected abstract NormalizedNode normalizeImpl(QName nodeType, Node node); @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { return null; } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { return null; } @Override public NormalizedNode createDefault( final PathArgument currentArg) { // TODO Auto-generated method stub return null; } } private static final class LeafNormalization extends SimpleTypeNormalization { private final LeafSchemaNode schema; protected LeafNormalization(final LeafSchemaNode schema, final NodeIdentifier identifier) { super(identifier); this.schema = schema; } @Override protected NormalizedNode normalizeImpl(final QName nodeType, final Node node) { Object value = NodeValueCodec.toTypeSafeValue(this.schema, this.schema.getType(), node); return ImmutableNodes.leafNode(nodeType, value); } } private static final class LeafListEntryNormalization extends SimpleTypeNormalization { private final LeafListSchemaNode schema; public LeafListEntryNormalization(final LeafListSchemaNode potential) { super(new NodeWithValue(potential.getQName(), null)); this.schema = potential; } @Override protected NormalizedNode normalizeImpl(final QName nodeType, final Node node) { final Object data = node.getValue(); if (data == null) { Preconditions.checkArgument(false, "No data available in leaf list entry for " + nodeType); } Object value = NodeValueCodec.toTypeSafeValue(this.schema, this.schema.getType(), node); NodeWithValue nodeId = new NodeWithValue(nodeType, value); return Builders.leafSetEntryBuilder().withNodeIdentifier(nodeId) .withValue(value).build(); } @Override public boolean isKeyedEntry() { return true; } } private static abstract class NodeToNormalizationNodeOperation extends NodeToNormalizedNodeBuilder { protected NodeToNormalizationNodeOperation(final T identifier) { super(identifier); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public final NormalizedNodeContainer normalize( final QName nodeType, final Node node) { checkArgument(node != null); if (!node.getType().equals(AugmentationNode.class.getSimpleName()) && !node.getType().equals(ContainerNode.class.getSimpleName()) && !node.getType().equals(MapNode.class.getSimpleName())) { checkArgument(nodeType != null); } NormalizedNodeContainerBuilder builder = createBuilder(node); Set> usedMixins = new HashSet<>(); logNode(node); if (node.getChildCount() == 0 && ( node.getType().equals(LeafSetEntryNode.class.getSimpleName()) || node.getType().equals(LeafNode.class.getSimpleName()))) { PathArgument childPathArgument = NodeIdentifierFactory.getArgument(node.getPath()); final NormalizedNode child; if (childPathArgument instanceof NodeWithValue) { final NodeWithValue nodeWithValue = new NodeWithValue(childPathArgument.getNodeType(), node.getValue()); child = Builders.leafSetEntryBuilder() .withNodeIdentifier(nodeWithValue) .withValue(node.getValue()).build(); } else { child = ImmutableNodes.leafNode(childPathArgument.getNodeType(), node.getValue()); } builder.addChild(child); } final List children = node.getChildList(); for (Node nodeChild : children) { PathArgument childPathArgument = NodeIdentifierFactory.getArgument(nodeChild.getPath()); QName childNodeType = null; NodeToNormalizedNodeBuilder childOp = null; if (childPathArgument instanceof AugmentationIdentifier) { childOp = getChild(childPathArgument); checkArgument(childOp instanceof AugmentationNormalization, childPathArgument); } else { childNodeType = childPathArgument.getNodeType(); childOp = getChild(childNodeType); } // We skip unknown nodes if this node is mixin since // it's nodes and parent nodes are interleaved if (childOp == null && isMixin()) { continue; } else if (childOp == null) { logger.error( "childOp is null and this operation is not a mixin : this = {}", this.toString()); } checkArgument(childOp != null, "Node %s is not allowed inside %s", childNodeType, getIdentifier()); if (childOp.isMixin()) { if (usedMixins.contains(childOp)) { // We already run / processed that mixin, so to avoid // duplicate we are // skipping next nodes. continue; } // builder.addChild(childOp.normalize(nodeType, treeCacheNode)); final NormalizedNode childNode = childOp.normalize(childNodeType, nodeChild); if (childNode != null) builder.addChild(childNode); usedMixins.add(childOp); } else { final NormalizedNode childNode = childOp.normalize(childNodeType, nodeChild); if (childNode != null) builder.addChild(childNode); } } try { return (NormalizedNodeContainer) builder.build(); } catch (Exception e) { return null; } } private void logNode(Node node) { //let us find out the type of the node logger.debug("We got a {} , with identifier {} with {} children", node.getType(), node.getPath(), node.getChildList()); } @SuppressWarnings("rawtypes") protected abstract NormalizedNodeContainerBuilder createBuilder( final Node node); } private static abstract class DataContainerNormalizationOperation extends NodeToNormalizationNodeOperation { private final DataNodeContainer schema; private final Map> byQName; private final Map> byArg; protected DataContainerNormalizationOperation(final T identifier, final DataNodeContainer schema) { super(identifier); this.schema = schema; this.byArg = new ConcurrentHashMap<>(); this.byQName = new ConcurrentHashMap<>(); } @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { NodeToNormalizedNodeBuilder potential = byArg.get(child); if (potential != null) { return potential; } potential = fromSchema(schema, child); return register(potential); } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { if (child == null) { return null; } NodeToNormalizedNodeBuilder potential = byQName.get(child); if (potential != null) { return potential; } potential = fromSchemaAndPathArgument(schema, child); return register(potential); } private NodeToNormalizedNodeBuilder register( final NodeToNormalizedNodeBuilder potential) { if (potential != null) { byArg.put(potential.getIdentifier(), potential); for (QName qName : potential.getQNameIdentifiers()) { byQName.put(qName, potential); } } return potential; } } private static final class ListItemNormalization extends DataContainerNormalizationOperation { private final List keyDefinition; private final ListSchemaNode schemaNode; protected ListItemNormalization( final NodeIdentifierWithPredicates identifier, final ListSchemaNode schema) { super(identifier, schema); this.schemaNode = schema; keyDefinition = schema.getKeyDefinition(); } @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { NodeIdentifierWithPredicates nodeIdentifierWithPredicates = (NodeIdentifierWithPredicates) NodeIdentifierFactory .createPathArgument(node .getPath(), schemaNode); return Builders.mapEntryBuilder() .withNodeIdentifier( nodeIdentifierWithPredicates ); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { DataContainerNodeAttrBuilder builder = Builders.mapEntryBuilder().withNodeIdentifier( (NodeIdentifierWithPredicates) currentArg); for (Entry keyValue : ((NodeIdentifierWithPredicates) currentArg) .getKeyValues().entrySet()) { if (keyValue.getValue() == null) { throw new NullPointerException( "Null value found for path : " + currentArg); } builder.addChild(Builders.leafBuilder() // .withNodeIdentifier(new NodeIdentifier(keyValue.getKey())) .withValue(keyValue.getValue()).build()); } return builder.build(); } @Override public boolean isKeyedEntry() { return true; } } private static final class ContainerNormalization extends DataContainerNormalizationOperation { protected ContainerNormalization(final ContainerSchemaNode schema) { super(new NodeIdentifier(schema.getQName()), schema); } @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { return Builders.containerBuilder() .withNodeIdentifier(getIdentifier()); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { return Builders.containerBuilder() .withNodeIdentifier((NodeIdentifier) currentArg).build(); } } private static abstract class MixinNormalizationOp extends NodeToNormalizationNodeOperation { protected MixinNormalizationOp(final T identifier) { super(identifier); } @Override public final boolean isMixin() { return true; } } private static final class LeafListMixinNormalization extends MixinNormalizationOp { private final NodeToNormalizedNodeBuilder innerOp; public LeafListMixinNormalization(final LeafListSchemaNode potential) { super(new NodeIdentifier(potential.getQName())); innerOp = new LeafListEntryNormalization(potential); } @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { return Builders.leafSetBuilder() .withNodeIdentifier(getIdentifier()); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { return Builders.leafSetBuilder().withNodeIdentifier(getIdentifier()) .build(); } @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { if (child instanceof NodeWithValue) { return innerOp; } return null; } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { if (getIdentifier().getNodeType().equals(child)) { return innerOp; } return null; } } private static final class AugmentationNormalization extends MixinNormalizationOp { private final Map> byQName; private final Map> byArg; public AugmentationNormalization(final AugmentationSchema augmentation, final DataNodeContainer schema) { super(augmentationIdentifierFrom(augmentation)); ImmutableMap.Builder> byQNameBuilder = ImmutableMap.builder(); ImmutableMap.Builder> byArgBuilder = ImmutableMap.builder(); for (DataSchemaNode augNode : augmentation.getChildNodes()) { DataSchemaNode resolvedNode = schema.getDataChildByName(augNode.getQName()); NodeToNormalizedNodeBuilder resolvedOp = fromDataSchemaNode(resolvedNode); byArgBuilder.put(resolvedOp.getIdentifier(), resolvedOp); for (QName resQName : resolvedOp.getQNameIdentifiers()) { byQNameBuilder.put(resQName, resolvedOp); } } byQName = byQNameBuilder.build(); byArg = byArgBuilder.build(); } @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { return byArg.get(child); } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { return byQName.get(child); } @Override protected Set getQNameIdentifiers() { return getIdentifier().getPossibleChildNames(); } @SuppressWarnings("rawtypes") @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { return Builders.augmentationBuilder() .withNodeIdentifier(getIdentifier()); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { return Builders.augmentationBuilder() .withNodeIdentifier(getIdentifier()) .build(); } } private static final class ListMixinNormalization extends MixinNormalizationOp { private final ListItemNormalization innerNode; public ListMixinNormalization(final ListSchemaNode list) { super(new NodeIdentifier(list.getQName())); this.innerNode = new ListItemNormalization(new NodeIdentifierWithPredicates( list.getQName(), Collections.emptyMap()), list); } @SuppressWarnings("rawtypes") @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { return Builders.mapBuilder().withNodeIdentifier(getIdentifier()); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { return Builders.mapBuilder().withNodeIdentifier(getIdentifier()) .build(); } @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { if (child.getNodeType().equals(getIdentifier().getNodeType())) { return innerNode; } return null; } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { if (getIdentifier().getNodeType().equals(child)) { return innerNode; } return null; } } private static class ChoiceNodeNormalization extends MixinNormalizationOp { private final ImmutableMap> byQName; private final ImmutableMap> byArg; protected ChoiceNodeNormalization( final org.opendaylight.yangtools.yang.model.api.ChoiceNode schema) { super(new NodeIdentifier(schema.getQName())); ImmutableMap.Builder> byQNameBuilder = ImmutableMap.builder(); ImmutableMap.Builder> byArgBuilder = ImmutableMap.builder(); for (ChoiceCaseNode caze : schema.getCases()) { for (DataSchemaNode cazeChild : caze.getChildNodes()) { NodeToNormalizedNodeBuilder childOp = fromDataSchemaNode(cazeChild); byArgBuilder.put(childOp.getIdentifier(), childOp); for (QName qname : childOp.getQNameIdentifiers()) { byQNameBuilder.put(qname, childOp); } } } byQName = byQNameBuilder.build(); byArg = byArgBuilder.build(); } @Override public NodeToNormalizedNodeBuilder getChild( final PathArgument child) { return byArg.get(child); } @Override public NodeToNormalizedNodeBuilder getChild(final QName child) { return byQName.get(child); } @Override protected NormalizedNodeContainerBuilder createBuilder( final Node node) { return Builders.choiceBuilder().withNodeIdentifier(getIdentifier()); } @Override public NormalizedNode createDefault( final PathArgument currentArg) { return Builders.choiceBuilder().withNodeIdentifier(getIdentifier()) .build(); } } /** * Find an appropriate NormalizedNodeBuilder using both the schema and the * Path Argument * * @param schema * @param child * @return */ public static NodeToNormalizedNodeBuilder fromSchemaAndPathArgument( final DataNodeContainer schema, final QName child) { DataSchemaNode potential = schema.getDataChildByName(child); if (potential == null) { Iterable choices = FluentIterable.from(schema.getChildNodes()).filter( org.opendaylight.yangtools.yang.model.api.ChoiceNode.class); potential = findChoice(choices, child); } if (potential == null) { if (logger.isTraceEnabled()) { logger.trace("BAD CHILD = {}", child.toString()); } } checkArgument(potential != null, "Supplied QName %s is not valid according to schema %s", child, schema); // If the schema in an instance of DataSchemaNode and the potential // is augmenting something then there is a chance that this may be // and augmentation node if ((schema instanceof DataSchemaNode) && potential.isAugmenting()) { AugmentationNormalization augmentation = fromAugmentation(schema, (AugmentationTarget) schema, potential); // If an augmentation normalization (builder) is not found then // we fall through to the regular processing if(augmentation != null){ return augmentation; } } return fromDataSchemaNode(potential); } /** * Given a bunch of choice nodes and a the name of child find a choice node for that child which * has a non-null value * * @param choices * @param child * @return */ private static org.opendaylight.yangtools.yang.model.api.ChoiceNode findChoice( final Iterable choices, final QName child) { org.opendaylight.yangtools.yang.model.api.ChoiceNode foundChoice = null; choiceLoop: for (org.opendaylight.yangtools.yang.model.api.ChoiceNode choice : choices) { for (ChoiceCaseNode caze : choice.getCases()) { if (caze.getDataChildByName(child) != null) { foundChoice = choice; break choiceLoop; } } } return foundChoice; } /** * Create an AugmentationIdentifier based on the AugmentationSchema * * @param augmentation * @return */ public static AugmentationIdentifier augmentationIdentifierFrom( final AugmentationSchema augmentation) { ImmutableSet.Builder potentialChildren = ImmutableSet.builder(); for (DataSchemaNode child : augmentation.getChildNodes()) { potentialChildren.add(child.getQName()); } return new AugmentationIdentifier(potentialChildren.build()); } /** * Create an AugmentationNormalization based on the schema of the DataContainer, the * AugmentationTarget and the potential schema node * * @param schema * @param augments * @param potential * @return */ private static AugmentationNormalization fromAugmentation( final DataNodeContainer schema, final AugmentationTarget augments, final DataSchemaNode potential) { AugmentationSchema augmentation = null; for (AugmentationSchema aug : augments.getAvailableAugmentations()) { DataSchemaNode child = aug.getDataChildByName(potential.getQName()); if (child != null) { augmentation = aug; break; } } if (augmentation != null) { return new AugmentationNormalization(augmentation, schema); } else { return null; } } /** * @param schema * @param child * @return */ private static NodeToNormalizedNodeBuilder fromSchema( final DataNodeContainer schema, final PathArgument child) { if (child instanceof AugmentationIdentifier) { QName childQName = ((AugmentationIdentifier) child) .getPossibleChildNames().iterator().next(); return fromSchemaAndPathArgument(schema, childQName); } return fromSchemaAndPathArgument(schema, child.getNodeType()); } public static NodeToNormalizedNodeBuilder fromDataSchemaNode( final DataSchemaNode potential) { if (potential instanceof ContainerSchemaNode) { return new ContainerNormalization((ContainerSchemaNode) potential); } else if (potential instanceof ListSchemaNode) { return new ListMixinNormalization((ListSchemaNode) potential); } else if (potential instanceof LeafSchemaNode) { return new LeafNormalization((LeafSchemaNode) potential, new NodeIdentifier(potential.getQName())); } else if (potential instanceof org.opendaylight.yangtools.yang.model.api.ChoiceNode) { return new ChoiceNodeNormalization( (org.opendaylight.yangtools.yang.model.api.ChoiceNode) potential); } else if (potential instanceof LeafListSchemaNode) { return new LeafListMixinNormalization( (LeafListSchemaNode) potential); } return null; } public static NodeToNormalizedNodeBuilder from(final SchemaContext ctx) { return new ContainerNormalization(ctx); } public abstract NormalizedNode createDefault(PathArgument currentArg); }