summaryrefslogtreecommitdiffstats
path: root/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
diff options
context:
space:
mode:
Diffstat (limited to 'infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java')
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java493
1 files changed, 127 insertions, 366 deletions
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
index 863f8abb2..74e21dfa1 100644
--- a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java
@@ -16,41 +16,28 @@
package io.fd.honeycomb.data.impl;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.APPEARED;
import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.DELETE;
-import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.DISAPPEARED;
import static org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType.WRITE;
import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.MixinNode;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
+import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
import org.opendaylight.yangtools.yang.model.api.AugmentationSchema;
-import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
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.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,7 +50,6 @@ final class ModificationDiff {
private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap());
private static final EnumSet VALID_MODIFICATIONS = EnumSet.of(WRITE, DELETE);
- private static final EnumSet IGNORED_MODIFICATIONS = EnumSet.of(APPEARED, DISAPPEARED);
private final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates;
@@ -105,391 +91,166 @@ final class ModificationDiff {
return new ModificationDiff(ImmutableMap.of(modification.getId(), NormalizedNodeUpdate.create(modification)));
}
- /**
- * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so
- * are complex nodes which direct leaves were not modified.
- */
- @Nonnull
- static ModificationDiff recursivelyFromCandidate(@Nonnull final Modification modification) {
- // recursively process child nodes for exact modifications
- return recursivelyChildrenFromCandidate(modification)
- // also add modification on current level, if elligible
- .merge(isModification(modification)
- ? ModificationDiff.create(modification)
- : EMPTY_DIFF);
- }
-
- /**
- * Same as {@link #recursivelyFromCandidate(Modification)} but does
- * not process the root node for modifications, since it's the artificial data root, that has no child leaves but
- * always is marked as SUBTREE_MODIFIED.
- */
- @Nonnull
- static ModificationDiff recursivelyFromCandidateRoot(@Nonnull final DataTreeCandidateNode currentCandidate,
- @Nonnull final SchemaContext ctx) {
- return recursivelyChildrenFromCandidate(new Modification(YangInstanceIdentifier.EMPTY, currentCandidate, ctx));
- }
-
- /**
- * Check whether current node was modified. {@link MixinNode}s are ignored
- * and only nodes which direct leaves(or choices) are modified are considered a modification.
- */
- private static Boolean isModification(@Nonnull final Modification modification) {
- // Disappear is not a modification
- if (IGNORED_MODIFICATIONS.contains(modification.getModificationType())) {
- return false;
- // Mixin nodes are not considered modifications
- } else if (modification.isMixin() && !modification.is(AugmentationSchema.class)) {
- return false;
- } else {
- return isCurrentModified(modification);
- }
- }
-
- private static Boolean isCurrentModified(@Nonnull final Modification modification) {
- // First check if it's an empty presence node
- final boolean emptyPresenceNode = isEmptyPresenceNode(modification);
-
- // Check if there are any modified leaves and if so, consider current node as modified
- final Boolean directLeavesModified = emptyPresenceNode
- || modification.streamChildren()
- // Checking leaf or leaf-lists children for direct modification, which means that leafs of leaf lists
- // trigger a modification on parent node
- .filter(child -> child.is(LeafSchemaNode.class) || child.is(LeafListSchemaNode.class))
- // For some reason, we get modifications on unmodified list keys
- // and that messes up our modifications collection here, so we need to skip
- .filter(Modification::isBeforeAndAfterDifferent)
- .filter(child -> VALID_MODIFICATIONS.contains(child.getModificationType()))
- .findFirst()
- .isPresent();
-
- // Also as fallback check choices (choices do not exist in BA world and if anything within a choice was modified,
- // consider its parent as being modified)
- final boolean modified = directLeavesModified
- || modification.streamChildren()
- .filter(child -> child.is(ChoiceSchemaNode.class))
- // Recursively check each choice if there was any change to it
- .filter(ModificationDiff::isCurrentModified)
- .findFirst()
- .isPresent();
-
- if (modified) {
- LOG.debug("Modification detected as {} at {}",
- modification.getModificationType(), modification.getId());
- }
-
- return modified;
- }
-
- /**
- * Check if new data are empty but still to be considered as a modification, meaning it's presence has a meaning
- * e.g. containers with presence statement.
- */
- private static boolean isEmptyPresenceNode(@Nonnull final Modification modification) {
- return modification.is(ContainerSchemaNode.class)
- && ((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
- && modification.getChildNodes().isEmpty()
- && VALID_MODIFICATIONS.contains(modification.getModificationType());
- }
-
- /**
- * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
- */
- private static ModificationDiff recursivelyChildrenFromCandidate(@Nonnull final Modification modification) {
- // recursively process child nodes for specific modifications
- return modification.streamChildren()
- .filter(child -> !child.is(LeafSchemaNode.class))
- .map(ModificationDiff::recursivelyFromCandidate)
- .reduce(ModificationDiff::merge)
- .orElse(EMPTY_DIFF);
- }
-
@Override
public String toString() {
return "ModificationDiff{updates=" + updates + '}';
}
- /**
- * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
- */
- static final class NormalizedNodeUpdate {
+ static final class ModificationDiffBuilder {
+ private NormalizedNodeRewriteDeleteRegistry registry;
+ private SchemaContext ctx;
- @Nonnull
- private final YangInstanceIdentifier id;
- @Nullable
- private final NormalizedNode<?, ?> dataBefore;
- @Nullable
- private final NormalizedNode<?, ?> dataAfter;
-
- private NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id,
- @Nullable final NormalizedNode<?, ?> dataBefore,
- @Nullable final NormalizedNode<?, ?> dataAfter) {
- this.id = checkNotNull(id);
- this.dataAfter = dataAfter;
- this.dataBefore = dataBefore;
+ ModificationDiffBuilder setCtx(final SchemaContext ctx) {
+ this.ctx = ctx;
+ registry = new NormalizedNodeRewriteDeleteRegistry(ctx);
+ return this;
}
- @Nullable
- public NormalizedNode<?, ?> getDataBefore() {
- return dataBefore;
- }
+ ModificationDiff build(@Nonnull final DataTreeCandidateNode currentCandidate) {
+ checkNotNull(currentCandidate, "Data tree candidate cannot be null");
+ checkNotNull(ctx, "Schema ctx cannot be null");
- @Nullable
- public NormalizedNode<?, ?> getDataAfter() {
- return dataAfter;
+ return recursivelyFromCandidateRoot(currentCandidate, ctx);
}
+ /**
+ * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so
+ * are complex nodes which direct leaves were not modified.
+ */
@Nonnull
- public YangInstanceIdentifier getId() {
- return id;
- }
-
- static NormalizedNodeUpdate create(@Nonnull final Modification modification) {
- final com.google.common.base.Optional<NormalizedNode<?, ?>> beforeData =
- modification.getDataBefore();
- final com.google.common.base.Optional<NormalizedNode<?, ?>> afterData =
- modification.getDataAfter();
- checkArgument(beforeData.isPresent() || afterData.isPresent(),
- "Both before and after data are null for %s", modification.getId());
- return NormalizedNodeUpdate.create(modification.getId(), beforeData.orNull(), afterData.orNull());
- }
-
- static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
- @Nullable final NormalizedNode<?, ?> dataBefore,
- @Nullable final NormalizedNode<?, ?> dataAfter) {
- return new NormalizedNodeUpdate(id, dataBefore, dataAfter);
- }
-
- @Override
- public boolean equals(final Object other) {
- if (this == other) {
- return true;
- }
- if (other == null || getClass() != other.getClass()) {
- return false;
- }
-
- final NormalizedNodeUpdate that = (NormalizedNodeUpdate) other;
-
- return id.equals(that.id);
-
- }
-
- @Override
- public int hashCode() {
- return id.hashCode();
- }
-
- @Override
- public String toString() {
- return "NormalizedNodeUpdate{" + "id=" + id
- + ", dataBefore=" + dataBefore
- + ", dataAfter=" + dataAfter
- + '}';
- }
- }
-
- /**
- * Intermediate representation of a modification + its schema.
- */
- private static final class Modification {
- private final YangInstanceIdentifier id;
- private final DataTreeCandidateNode dataCandidate;
- // Using Object as type for schema node since it's the only type that's a parent to all schema node types from
- // yangtools. The hierarchy does not use e.g. SchemaNode class for all types
- private final Object parentNode;
- private final Object schemaNode;
- private final boolean updateParentNode;
-
- private Modification(final YangInstanceIdentifier id,
- final DataTreeCandidateNode dataCandidate,
- final Object parentNode,
- final Object schemaNode,
- final boolean updateParentNode) {
- this.id = id;
- this.dataCandidate = dataCandidate;
- this.parentNode = parentNode;
- this.schemaNode = schemaNode;
- // controls process of updating parent node while moving down the schema tree:
- this.updateParentNode = updateParentNode;
+ ModificationDiff recursivelyFromCandidate(@Nonnull final Modification modification) {
+ // recursively process child nodes for exact modifications
+ return recursivelyChildrenFromCandidate(modification)
+ // also add modification on current level, if elligible
+ .merge(isModification(modification)
+ ? ModificationDiff.create(modification)
+ // Modification that writes only non-presence container to override nested nodes wont have
+ // child nodes(in data tree candidate) so logic before will not detected such change, so checking directly
+ : isNonPresenceOverride(modification)
+ ? detectUnderDisappearedNonPresenceContainer(modification)
+ : EMPTY_DIFF);
}
- Modification(final YangInstanceIdentifier id,
- final DataTreeCandidateNode dataCandidate,
- final Object parentNode,
- final Object schemaNode) {
- this(id, dataCandidate, parentNode, schemaNode, true);
- }
+ private ModificationDiff detectUnderDisappearedNonPresenceContainer(
+ @Nonnull final Modification modification) {
+ final com.google.common.base.Optional<NormalizedNode<?, ?>> dataBefore = modification.getDataBefore();
- Modification(final YangInstanceIdentifier id,
- final DataTreeCandidateNode dataCandidate,
- final Object schemaNode) {
- this(id, dataCandidate, schemaNode, schemaNode);
- }
+ // is disappear case
+ if (dataBefore.isPresent()) {
+ final NormalizedNode<?, ?> parentData = dataBefore.get();
- List<Modification> getChildNodes() {
- return streamChildren().collect(Collectors.toList());
- }
+ // have potential to extract children
+ if (parentData instanceof AbstractImmutableDataContainerNode) {
+ final AbstractImmutableDataContainerNode<YangInstanceIdentifier.PathArgument> parentContainerNode =
+ (AbstractImmutableDataContainerNode) parentData;
- YangInstanceIdentifier getId() {
- return id;
- }
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates =
+ parentContainerNode.getChildren().entrySet().stream()
+ .flatMap(entry -> registry.normalizedUpdates(modification.getId(), entry).stream())
+ .collect(Collectors.toMap(NormalizedNodeUpdate::getId, update -> update));
- ModificationType getModificationType() {
- return dataCandidate.getModificationType();
- }
-
- com.google.common.base.Optional<NormalizedNode<?, ?>> getDataBefore() {
- return dataCandidate.getDataBefore();
- }
-
- com.google.common.base.Optional<NormalizedNode<?, ?>> getDataAfter() {
- return dataCandidate.getDataAfter();
- }
-
- Object getSchemaNode() {
- return schemaNode;
- }
-
- boolean is(final Class<?> schemaType) {
- return schemaType.isAssignableFrom(schemaNode.getClass());
+ return new ModificationDiff(updates);
+ }
+ }
+ return EMPTY_DIFF;
}
- boolean isMixin() {
- // Checking whether node is a mixin is not performed on schema, but on data since mixin is
- // only a NormalizedNode concept, not a schema concept
- return dataCandidate.getDataBefore().orNull() instanceof MixinNode ||
- dataCandidate.getDataAfter().orNull() instanceof MixinNode;
+ /**
+ * Same as {@link #recursivelyFromCandidate(Modification)} but does not process the root node for modifications,
+ * since it's the artificial data root, that has no child leaves but always is marked as SUBTREE_MODIFIED.
+ */
+ @Nonnull
+ ModificationDiff recursivelyFromCandidateRoot(@Nonnull final DataTreeCandidateNode currentCandidate,
+ @Nonnull final SchemaContext ctx) {
+ return recursivelyChildrenFromCandidate(
+ new Modification(YangInstanceIdentifier.EMPTY, currentCandidate, ctx));
}
- private boolean isBeforeAndAfterDifferent() {
- if (dataCandidate.getDataBefore().isPresent()) {
- return !dataCandidate.getDataBefore().get().equals(dataCandidate.getDataAfter().orNull());
+ /**
+ * Check whether current node was modified. {@link MixinNode}s are ignored
+ * and only nodes which direct leaves(or choices) are modified are considered a modification.
+ */
+ private Boolean isModification(@Nonnull final Modification modification) {
+ // APPEAR/DISAPPEAR are not valid modifications, but some of the children can be modified
+ // aka. list entry added to nested list under non-presence container, which would be resolved as APPEAR for
+ // that container, but MERGE for nested list
+ if (modification.isMixin() && !modification.is(AugmentationSchema.class)) {
+ return false;
+ } else {
+ return isCurrentModified(modification);
}
-
- // considering not a modification if data after is also null
- return dataCandidate.getDataAfter().isPresent();
}
- private AugmentationSchema findAugmentation(Object currentNode,
- final YangInstanceIdentifier.AugmentationIdentifier identifier) {
- if (currentNode != null) {
- // check if identifier points to some augmentation of currentNode
- if (currentNode instanceof AugmentationTarget) {
- Optional<AugmentationSchema> augmentationSchema =
- ((AugmentationTarget) currentNode).getAvailableAugmentations().stream()
- .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
- aug.getChildNodes().stream()
- .map(SchemaNode::getQName)
- .collect(Collectors.toSet()))))
- .findFirst();
- if (augmentationSchema.isPresent()) {
- return augmentationSchema.get();
- }
- }
-
- // continue search:
- Collection<DataSchemaNode> childNodes = Collections.emptyList();
- if (currentNode instanceof DataNodeContainer) {
- childNodes = ((DataNodeContainer) currentNode).getChildNodes();
- } else if (currentNode instanceof ChoiceSchemaNode) {
- childNodes = ((ChoiceSchemaNode) currentNode).getCases().stream()
- .flatMap(cas -> cas.getChildNodes().stream()).collect(Collectors.toList());
- }
- return childNodes.stream().map(n -> findAugmentation(n, identifier)).filter(n -> n != null).findFirst()
- .orElse(null);
- } else {
- return null;
+ private Boolean isCurrentModified(@Nonnull final Modification modification) {
+ // First check if it's an empty presence node
+ final boolean emptyPresenceNode = isEmptyPresenceNode(modification);
+
+ // Check if there are any modified leaves and if so, consider current node as modified
+ final Boolean directLeavesModified = emptyPresenceNode
+ || modification.streamChildren()
+ // Checking leaf or leaf-lists children for direct modification, which means that leafs of leaf lists
+ // trigger a modification on parent node
+ .filter(child -> child.is(LeafSchemaNode.class) || child.is(LeafListSchemaNode.class))
+ // For some reason, we get modifications on unmodified list keys
+ // and that messes up our modifications collection here, so we need to skip
+ .filter(Modification::isBeforeAndAfterDifferent)
+ .filter(child -> VALID_MODIFICATIONS.contains(child.getModificationType()))
+ .findFirst()
+ .isPresent();
+
+ // Also as fallback check choices (choices do not exist in BA world and if anything within a choice was modified,
+ // consider its parent as being modified)
+ final boolean modified = directLeavesModified
+ || modification.streamChildren()
+ .filter(child -> child.is(ChoiceSchemaNode.class))
+ // Recursively check each choice if there was any change to it
+ .filter(this::isCurrentModified)
+ .findFirst()
+ .isPresent();
+
+ if (modified) {
+ LOG.debug("Modification detected as {} at {}",
+ modification.getModificationType(), modification.getId());
}
- }
- Stream<Modification> streamChildren() {
- return dataCandidate.getChildNodes().stream()
- .map(child -> {
- final YangInstanceIdentifier childId = id.node(child.getIdentifier());
- final Object schemaChild = schemaChild(schemaNode, child.getIdentifier());
-
- // An augment cannot change other augment, so we do not update parent node if we are streaming
- // children of AugmentationSchema (otherwise we would fail to find schema for nested augmentations):
- if (updateParentNode) {
- if (schemaNode instanceof AugmentationSchema) {
- // child nodes would not have nested augmentations, so we stop moving parentNode:
- return new Modification(childId, child, parentNode, schemaChild, false);
- } else {
- // update parent node:
- return new Modification(childId, child, schemaNode, schemaChild, true);
- }
- }
- return new Modification(childId, child, parentNode, schemaChild, updateParentNode);
- });
+ return modified;
}
/**
- * Find next schema node in hierarchy.
+ * Check if new data are empty but still to be considered as a modification, meaning it's presence has a meaning
+ * e.g. containers with presence statement.
*/
- private Object schemaChild(final Object schemaNode, final YangInstanceIdentifier.PathArgument identifier) {
- Object found = null;
-
- if (identifier instanceof YangInstanceIdentifier.AugmentationIdentifier) {
- if (schemaNode instanceof AugmentationTarget) {
- // Find matching augmentation
- found = ((AugmentationTarget) schemaNode).getAvailableAugmentations().stream()
- .filter(aug -> identifier.equals(new YangInstanceIdentifier.AugmentationIdentifier(
- aug.getChildNodes().stream()
- .map(SchemaNode::getQName)
- .collect(Collectors.toSet()))))
- .findFirst()
- .orElse(null);
-
- if (found == null) {
- // An augment cannot change other augment, but all augments only change their targets (data nodes).
- //
- // As a consequence, if nested augmentations are present,
- // AugmentationSchema might reference child schema node instances that do not include changes
- // from nested augments.
- //
- // But schemaNode, as mentioned earlier, contains all the changes introduced by augments.
- //
- // On the other hand, in case of augments which introduce leaves,
- // we need to address AugmentationSchema node directly so we can't simply do
- // found = schemaNode;
- //
- found =
- findAugmentation(parentNode, (YangInstanceIdentifier.AugmentationIdentifier) identifier);
- }
- }
- } else if (schemaNode instanceof DataNodeContainer) {
- // Special handling for list aggregator nodes. If we are at list aggregator node e.g. MapNode and
- // we are searching for schema for a list entry e.g. MapEntryNode just return the same schema
- if (schemaNode instanceof ListSchemaNode &&
- ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
- found = schemaNode;
- } else {
- found = ((DataNodeContainer) schemaNode).getDataChildByName(identifier.getNodeType());
- }
- } else if (schemaNode instanceof ChoiceSchemaNode) {
- // For choices, iterate through all the cases
- final Optional<DataSchemaNode> maybeChild = ((ChoiceSchemaNode) schemaNode).getCases().stream()
- .flatMap(cas -> cas.getChildNodes().stream())
- .filter(child -> child.getQName().equals(identifier.getNodeType()))
- .findFirst();
- if (maybeChild.isPresent()) {
- found = maybeChild.get();
- }
- // Special handling for leaf-list nodes. Basically the same as is for list mixin nodes
- } else if (schemaNode instanceof LeafListSchemaNode &&
- ((SchemaNode) schemaNode).getQName().equals(identifier.getNodeType())) {
- found = schemaNode;
- }
+ private static boolean isEmptyPresenceNode(@Nonnull final Modification modification) {
+ return modification.is(ContainerSchemaNode.class)
+ && ((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
+ && modification.getChildNodes().isEmpty()
+ && VALID_MODIFICATIONS.contains(modification.getModificationType());
+ }
- return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schemaNode, identifier);
+ /**
+ * Checks whether node is non-presence container but with changed nested data
+ */
+ private static boolean isNonPresenceOverride(@Nonnull final Modification modification) {
+ return modification.is(ContainerSchemaNode.class)// must be container
+ && !((ContainerSchemaNode) modification.getSchemaNode()).isPresenceContainer()
+ // must be non-presence
+ && modification.getChildNodes().isEmpty() // is override to empty
+ && modification.isBeforeAndAfterDifferent()// to detect that it is modification
+ &&
+ modification.getDataBefore().isPresent(); // to ensure the case when overriding previously existing
}
- @Override
- public String toString() {
- return "Modification{" +
- "id=" + id +
- '}';
+ /**
+ * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
+ */
+ private ModificationDiff recursivelyChildrenFromCandidate(@Nonnull final Modification modification) {
+ // recursively process child nodes for specific modifications
+ return modification.streamChildren()
+ .filter(child -> !child.is(LeafSchemaNode.class))
+ .map(this::recursivelyFromCandidate)
+ .reduce(ModificationDiff::merge)
+ .orElse(EMPTY_DIFF);
}
}
+
}