/*
*
* 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);
}