summaryrefslogtreecommitdiffstats
path: root/infra/data-impl
diff options
context:
space:
mode:
authorJan Srnicek <jsrnicek@cisco.com>2017-05-09 15:28:14 +0200
committerMarek Gradzki <mgradzki@cisco.com>2017-05-09 15:39:43 +0000
commitbe05d84deebf8bd030bb6564d5cd49094f6da961 (patch)
tree37c61729fffbdcf48014196882424fa1b72ddd78 /infra/data-impl
parent215cb683406b2cc12e869706ef9d0ae854ab53fb (diff)
HONEYCOMB-350 - APPEAR/DISAPPEAR modification handling
Allows these types of modifications to check in depth, to see if some of their children nodes were not modified Change-Id: Ice2f988732c2d9ecad8e960c4f10d01863fb0cfd Signed-off-by: Jan Srnicek <jsrnicek@cisco.com>
Diffstat (limited to 'infra/data-impl')
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java42
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java42
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java42
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java83
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java68
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java40
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java40
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java44
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java14
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java243
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModificationDiff.java493
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java101
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java105
-rw-r--r--infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java30
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java38
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java367
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java120
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java120
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java173
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java234
-rw-r--r--infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java68
-rw-r--r--infra/data-impl/src/test/resources/test-diff.yang72
22 files changed, 2026 insertions, 553 deletions
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java
new file mode 100644
index 000000000..1a9397eb4
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/AugmentationRewriteDeleteProducer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class AugmentationRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AugmentationRewriteDeleteProducer.class);
+
+ AugmentationRewriteDeleteProducer(
+ @Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+ super(baseRewriteProducer);
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ LOG.debug("Processing {} as augmentation", topLevelIdentifier);
+ return super.normalizedUpdates(topLevelIdentifier, entry);
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java
new file mode 100644
index 000000000..37fdef0e4
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/CaseRewriteDeleteProducer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class CaseRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CaseRewriteDeleteProducer.class);
+
+ CaseRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+ super(baseRewriteProducer);
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // just delegates to lower level, there is nothing to detect on case level
+ LOG.debug("Processing {} as case", topLevelIdentifier);
+ return super.normalizedUpdates(topLevelIdentifier, entry);
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java
new file mode 100644
index 000000000..4c249e084
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ChoiceRewriteDeleteProducer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ChoiceRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ChoiceRewriteDeleteProducer.class);
+
+ ChoiceRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+ super(baseRewriteProducer);
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // just delegates to lower level, there is nothing to detect on choice level
+ LOG.debug("Processing {} as choice", topLevelIdentifier);
+ return super.normalizedUpdates(topLevelIdentifier, entry);
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java
new file mode 100644
index 000000000..7a2b31eea
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ContainerRewriteDeleteProducer.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ContainerRewriteDeleteProducer extends DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ContainerRewriteDeleteProducer.class);
+
+ private final SchemaContext ctx;
+
+ ContainerRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer,
+ @Nonnull final SchemaContext ctx) {
+ super(baseRewriteProducer);
+ this.ctx = ctx;
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ final ContainerSchemaNode containerSchemaNode =
+ (ContainerSchemaNode) SchemaContextUtil
+ .findDataSchemaNode(ctx, getSchemaPath(topLevelIdentifier, entry));
+
+ if (containerSchemaNode.isPresenceContainer()) {
+ LOG.debug("Processing {} as presence container", topLevelIdentifier);
+ // if presence container - create delete right away
+ return ((ContainerNode) entry.getValue()).getValue().stream()
+ .map(containerNode -> new NormalizedNodeUpdate(
+ YangInstanceIdentifier.builder(topLevelIdentifier)
+ .node(containerNode.getIdentifier()).build(), containerNode, null))
+ .collect(Collectors.toList());
+ } else {
+ LOG.debug("Processing {} as non-presence container", topLevelIdentifier);
+ // if non-presence - goes deep with base logic
+ return super.normalizedUpdates(topLevelIdentifier, entry);
+ }
+ }
+
+ private static SchemaPath getSchemaPath(final YangInstanceIdentifier topLevelIdentifier,
+ final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ return SchemaPath.create(extractSchemaPathQNames(topLevelIdentifier, entry), true);
+ }
+
+ private static List<QName> extractSchemaPathQNames(final YangInstanceIdentifier topLevelIdentifier,
+ final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // must be filtered out of augmentation and keyed NodeIdentifiers
+ return Stream.concat(topLevelIdentifier.getPathArguments().stream(), Stream.of(entry.getKey()))
+ .filter(pathArgument -> pathArgument instanceof YangInstanceIdentifier.NodeIdentifier)
+ .map(YangInstanceIdentifier.PathArgument::getNodeType)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java
new file mode 100644
index 000000000..e3b3c74dc
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/DelegatingRewriteDeleteProducer.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.AbstractMap;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
+
+/**
+ * Basic implementation for nodes that are not considered modifications but their children could be
+ */
+abstract class DelegatingRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer;
+
+ DelegatingRewriteDeleteProducer(@Nonnull final NormalizedNodeRewriteDeleteRegistry baseRewriteProducer) {
+ this.baseRewriteProducer = baseRewriteProducer;
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // just delegates to lower level
+ checkState(entry.getValue() instanceof DataContainerNode, "Unable to extract children");
+ checkState(entry.getValue() instanceof DataContainerChild, "Unable to extract identifier");
+ final Collection<DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> value =
+ DataContainerNode.class.cast(entry.getValue()).getValue();
+ return value.stream()
+ .map(DataContainerChild.class::cast)
+ .flatMap(node -> baseRewriteProducer
+ .normalizedUpdates(childYangId(topLevelIdentifier, entry), childMapEntry(node)).stream())
+ .collect(Collectors.toList());
+ }
+
+ private static AbstractMap.SimpleEntry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> childMapEntry(
+ final DataContainerChild node) {
+ return new HashMap.SimpleEntry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>>(
+ node.getIdentifier(), node);
+ }
+
+ private static YangInstanceIdentifier childYangId(final @Nonnull YangInstanceIdentifier topLevelIdentifier,
+ final @Nonnull Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ return YangInstanceIdentifier.builder(topLevelIdentifier)
+ .node(entry.getKey()).build();
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java
new file mode 100644
index 000000000..ca7899694
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafListRewriteDeleteProducer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class LeafListRewriteDeleteProducer implements RewriteDeleteProducer{
+
+ private static final Logger LOG = LoggerFactory.getLogger(LeafListRewriteDeleteProducer.class);
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // if leaf-list,just builds delete right away
+ LOG.debug("Processing {} as leaf-list", topLevelIdentifier);
+ return Collections.singletonList(new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+ .node(entry.getKey()).build(), entry.getValue(), null));
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java
new file mode 100644
index 000000000..ddf0d64f7
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/LeafRewriteDeleteProducer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class LeafRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(LeafRewriteDeleteProducer.class);
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // if leaf,just builds delete right away
+ LOG.debug("Processing {} as leaf", topLevelIdentifier);
+ return Collections.singletonList(new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+ .node(entry.getKey()).build(), entry.getValue(), null));
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java
new file mode 100644
index 000000000..793cab8fa
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ListRewriteDeleteProducer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+class ListRewriteDeleteProducer implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ListRewriteDeleteProducer.class);
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ // identifier has different format : parent_node/list_node/list_node_with_key
+ LOG.debug("Processing {} as list", topLevelIdentifier);
+ return ((MapNode) entry.getValue()).getValue().stream()
+ .map(mapEntryNode -> new NormalizedNodeUpdate(YangInstanceIdentifier.builder(topLevelIdentifier)
+ .node(mapEntryNode.getNodeType())
+ .node(mapEntryNode.getIdentifier()).build(), mapEntryNode, null))
+ .collect(Collectors.toList());
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java
index f4989aa37..ccc40576a 100644
--- a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java
@@ -119,8 +119,8 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
rootPath, rootNode, rootNode.getDataBefore(), rootNode.getDataAfter());
- final ModificationDiff modificationDiff =
- ModificationDiff.recursivelyFromCandidateRoot(rootNode, schema);
+ final ModificationDiff modificationDiff = new ModificationDiff.ModificationDiffBuilder()
+ .setCtx(schema).build(rootNode);
LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
// Distinguish between updates (create + update) and deletes
@@ -194,25 +194,25 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
}
private WriterRegistry.DataObjectUpdates toBindingAware(
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes) {
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
}
}
@VisibleForTesting
static WriterRegistry.DataObjectUpdates toBindingAware(
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes,
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
final BindingNormalizedNodeSerializer serializer) {
final Multimap<InstanceIdentifier<?>, DataObjectUpdate> dataObjectUpdates = HashMultimap.create();
final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> dataObjectDeletes =
HashMultimap.create();
- for (Map.Entry<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
+ for (Map.Entry<YangInstanceIdentifier, NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
final InstanceIdentifier<?> unkeyedIid =
RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey()));
- ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
+ NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
if (dataObjectUpdate != null) {
if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
@@ -227,7 +227,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
@Nullable
private static DataObjectUpdate toDataObjectUpdate(
- final ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate,
+ final NormalizedNodeUpdate normalizedNodeUpdate,
final BindingNormalizedNodeSerializer serializer) {
InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java
new file mode 100644
index 000000000..88823c53d
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/Modification.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+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.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.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
+
+/**
+ * Intermediate representation of a modification + its schema.
+ */
+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;
+ }
+
+ Modification(final YangInstanceIdentifier id,
+ final DataTreeCandidateNode dataCandidate,
+ final Object parentNode,
+ final Object schemaNode) {
+ this(id, dataCandidate, parentNode, schemaNode, true);
+ }
+
+ Modification(final YangInstanceIdentifier id,
+ final DataTreeCandidateNode dataCandidate,
+ final Object schemaNode) {
+ this(id, dataCandidate, schemaNode, schemaNode);
+ }
+
+ List<Modification> getChildNodes() {
+ return streamChildren().collect(Collectors.toList());
+ }
+
+ YangInstanceIdentifier getId() {
+ return id;
+ }
+
+ 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());
+ }
+
+ 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;
+ }
+
+ boolean isBeforeAndAfterDifferent() {
+ if (dataCandidate.getDataBefore().isPresent()) {
+ return !dataCandidate.getDataBefore().get().equals(dataCandidate.getDataAfter().orNull());
+ }
+
+ // 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;
+ }
+ }
+
+ 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);
+ });
+ }
+
+ /**
+ * Find next schema node in hierarchy.
+ */
+ 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;
+ }
+
+ return checkNotNull(found, "Unable to find child node in: %s identifiable by: %s", schemaNode, identifier);
+ }
+
+ @Override
+ public String toString() {
+ return "Modification{" +
+ "id=" + id +
+ '}';
+ }
+}
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);
}
}
+
}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java
new file mode 100644
index 000000000..c7832ac0e
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeRewriteDeleteRegistry.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Basic logic for creating {@link NormalizedNodeUpdate} for changed that delete by rewrite
+ */
+final class NormalizedNodeRewriteDeleteRegistry implements RewriteDeleteProducer {
+
+ private static final Logger LOG = LoggerFactory.getLogger(NormalizedNodeRewriteDeleteRegistry.class);
+
+ private final RewriteDeleteProducer leafDeleteProducer;
+ private final RewriteDeleteProducer leafListDeleteProducer;
+ private final RewriteDeleteProducer listDeleteProducer;
+ private final RewriteDeleteProducer containerDeleteProducer;
+ private final RewriteDeleteProducer choiceDeleteProducer;
+ private final RewriteDeleteProducer caseDeleteProducer;
+ private final RewriteDeleteProducer augmentationDeleteProducer;
+
+ NormalizedNodeRewriteDeleteRegistry(@Nonnull final SchemaContext ctx) {
+ leafDeleteProducer = new LeafRewriteDeleteProducer();
+ leafListDeleteProducer = new LeafListRewriteDeleteProducer();
+ listDeleteProducer = new ListRewriteDeleteProducer();
+ containerDeleteProducer = new ContainerRewriteDeleteProducer(this, ctx);
+ choiceDeleteProducer = new ChoiceRewriteDeleteProducer(this);
+ caseDeleteProducer = new CaseRewriteDeleteProducer(this);
+ augmentationDeleteProducer = new AugmentationRewriteDeleteProducer(this);
+ }
+
+ @Override
+ public Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry) {
+ if (entry.getValue() instanceof LeafNode) {
+ LOG.debug("Processing leaf node {}", topLevelIdentifier);
+ return leafDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof LeafSetNode) {
+ LOG.debug("Processing leaf-list node {}", topLevelIdentifier);
+ return leafListDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof MapNode) {
+ LOG.debug("Processing list {}", topLevelIdentifier);
+ return listDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof ContainerNode) {
+ LOG.debug("Processing container {}", topLevelIdentifier);
+ return containerDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof ChoiceNode) {
+ LOG.debug("Processing choice {}", topLevelIdentifier);
+ return choiceDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof ChoiceCaseNode) {
+ LOG.debug("Processing case {}", topLevelIdentifier);
+ return caseDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ if (entry.getValue() instanceof AugmentationNode) {
+ LOG.debug("Processing augmentation {}", topLevelIdentifier);
+ return augmentationDeleteProducer.normalizedUpdates(topLevelIdentifier, entry);
+ }
+
+ return Collections.emptyList();
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java
new file mode 100644
index 000000000..bfc8a1e2c
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/NormalizedNodeUpdate.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+/**
+ * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
+ */
+final class NormalizedNodeUpdate {
+
+ @Nonnull
+ private final YangInstanceIdentifier id;
+ @Nullable
+ private final NormalizedNode<?, ?> dataBefore;
+ @Nullable
+ private final NormalizedNode<?, ?> dataAfter;
+
+ NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id,
+ @Nullable final NormalizedNode<?, ?> dataBefore,
+ @Nullable final NormalizedNode<?, ?> dataAfter) {
+ this.id = checkNotNull(id);
+ this.dataAfter = dataAfter;
+ this.dataBefore = dataBefore;
+ }
+
+ @Nullable
+ public NormalizedNode<?, ?> getDataBefore() {
+ return dataBefore;
+ }
+
+ @Nullable
+ public NormalizedNode<?, ?> getDataAfter() {
+ return dataAfter;
+ }
+
+ @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
+ + '}';
+ }
+}
diff --git a/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java
new file mode 100644
index 000000000..e2c5f4ae0
--- /dev/null
+++ b/infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/RewriteDeleteProducer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+
+import java.util.Collection;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+
+interface RewriteDeleteProducer {
+
+ Collection<NormalizedNodeUpdate> normalizedUpdates(@Nonnull final YangInstanceIdentifier topLevelIdentifier,
+ @Nonnull final Map.Entry<YangInstanceIdentifier.PathArgument, DataContainerChild<? extends YangInstanceIdentifier.PathArgument, ?>> entry);
+} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java
index 26a936f23..432833be3 100644
--- a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java
@@ -64,7 +64,7 @@ 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.tree.DataTree;
-public class ModifiableDataTreeDelegatorTest {
+public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
@Mock
private WriterRegistry writer;
@@ -90,7 +90,7 @@ public class ModifiableDataTreeDelegatorTest {
@Before
public void setUp() throws Exception {
initMocks(this);
- dataTree = ModificationDiffTest.getDataTree();
+ dataTree = getDataTree();
when(contextBroker.newReadWriteTransaction()).thenReturn(tx);
when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null));
@@ -98,39 +98,39 @@ public class ModifiableDataTreeDelegatorTest {
final Map.Entry<InstanceIdentifier<?>, DataObject> parsed = new AbstractMap.SimpleEntry<>(DEFAULT_ID, DEFAULT_DATA_OBJECT);
when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), any(NormalizedNode.class))).thenReturn(parsed);
- configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, ModificationDiffTest.getSchemaCtx(), writer, contextBroker);
+ configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, getSchemaCtx(), writer, contextBroker);
}
@Test
public void testRead() throws Exception {
- final ContainerNode topContainer = ModificationDiffTest.getTopContainer("topContainer");
- ModificationDiffTest.addNodeToTree(dataTree, topContainer, ModificationDiffTest.TOP_CONTAINER_ID);
+ final ContainerNode topContainer = getTopContainer("topContainer");
+ addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read =
- configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID);
+ configDataTree.read(TOP_CONTAINER_ID);
final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read2 =
- configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID);
+ configDataTree.newModification().read(TOP_CONTAINER_ID);
final Optional<NormalizedNode<?, ?>> normalizedNodeOptional = read.get();
final Optional<NormalizedNode<?, ?>> normalizedNodeOptional2 = read2.get();
assertEquals(normalizedNodeOptional, normalizedNodeOptional2);
assertTrue(normalizedNodeOptional.isPresent());
assertEquals(topContainer, normalizedNodeOptional.get());
- assertEquals(dataTree.takeSnapshot().readNode(ModificationDiffTest.TOP_CONTAINER_ID), normalizedNodeOptional);
+ assertEquals(dataTree.takeSnapshot().readNode(TOP_CONTAINER_ID), normalizedNodeOptional);
}
@Test
public void testCommitSuccessful() throws Exception {
- final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+ final MapNode nestedList = getNestedList("listEntry", "listValue");
final DataModification dataModification = configDataTree.newModification();
- dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.write(NESTED_LIST_ID, nestedList);
dataModification.validate();
dataModification.commit();
final Multimap<InstanceIdentifier<?>, DataObjectUpdate> map = HashMultimap.create();
map.put(DEFAULT_ID, DataObjectUpdate.create(DEFAULT_ID, DEFAULT_DATA_OBJECT, DEFAULT_DATA_OBJECT));
verify(writer).update(eq(new WriterRegistry.DataObjectUpdates(map, ImmutableMultimap.of())), any(WriteContext.class));
- assertEquals(nestedList, dataTree.takeSnapshot().readNode(ModificationDiffTest.NESTED_LIST_ID).get());
+ assertEquals(nestedList, dataTree.takeSnapshot().readNode(NESTED_LIST_ID).get());
}
private static DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
@@ -141,7 +141,7 @@ public class ModifiableDataTreeDelegatorTest {
@Test
public void testCommitUndoSuccessful() throws Exception {
- final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+ final MapNode nestedList = getNestedList("listEntry", "listValue");
// Fail on update:
final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
@@ -152,7 +152,7 @@ public class ModifiableDataTreeDelegatorTest {
try {
// Run the test
final DataModification dataModification = configDataTree.newModification();
- dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.write(NESTED_LIST_ID, nestedList);
dataModification.validate();
dataModification.commit();
fail("WriterRegistry.RevertSuccessException was expected");
@@ -165,7 +165,7 @@ public class ModifiableDataTreeDelegatorTest {
@Test
public void testCommitUndoFailed() throws Exception {
- final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+ final MapNode nestedList = getNestedList("listEntry", "listValue");
// Fail on update:
final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
@@ -182,7 +182,7 @@ public class ModifiableDataTreeDelegatorTest {
try {
// Run the test
final DataModification dataModification = configDataTree.newModification();
- dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+ dataModification.write(NESTED_LIST_ID, nestedList);
dataModification.validate();
dataModification.commit();
fail("WriterRegistry.Reverter.RevertFailedException was expected");
@@ -201,14 +201,14 @@ public class ModifiableDataTreeDelegatorTest {
public void testToBindingAware() throws Exception {
when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes = new HashMap<>();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes = new HashMap<>();
// delete
final QName nn1 = QName.create("namespace", "nn1");
final YangInstanceIdentifier yid1 = mockYid(nn1);
final InstanceIdentifier iid1 = mockIid(yid1, DataObject1.class);
final NormalizedNode nn1B = mockNormalizedNode(nn1);
final DataObject1 do1B = mockDataObject(yid1, iid1, nn1B, DataObject1.class);
- biNodes.put(yid1, ModificationDiff.NormalizedNodeUpdate.create(yid1, nn1B, null));
+ biNodes.put(yid1, NormalizedNodeUpdate.create(yid1, nn1B, null));
// create
final QName nn2 = QName.create("namespace", "nn1");
@@ -216,7 +216,7 @@ public class ModifiableDataTreeDelegatorTest {
final InstanceIdentifier iid2 = mockIid(yid2, DataObject2.class);;
final NormalizedNode nn2A = mockNormalizedNode(nn2);
final DataObject2 do2A = mockDataObject(yid2, iid2, nn2A, DataObject2.class);
- biNodes.put(yid2, ModificationDiff.NormalizedNodeUpdate.create(yid2, null, nn2A));
+ biNodes.put(yid2, NormalizedNodeUpdate.create(yid2, null, nn2A));
// update
final QName nn3 = QName.create("namespace", "nn1");
@@ -226,7 +226,7 @@ public class ModifiableDataTreeDelegatorTest {
final DataObject3 do3B = mockDataObject(yid3, iid3, nn3B, DataObject3.class);
final NormalizedNode nn3A = mockNormalizedNode(nn3);
final DataObject3 do3A = mockDataObject(yid3, iid3, nn3A, DataObject3.class);;
- biNodes.put(yid3, ModificationDiff.NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
+ biNodes.put(yid3, NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
final WriterRegistry.DataObjectUpdates dataObjectUpdates =
ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java
new file mode 100644
index 000000000..c7d78faf6
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationBaseTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
+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.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+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.ListNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
+import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+
+abstract class ModificationBaseTest extends ModificationMetadata {
+
+ void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
+ final YangInstanceIdentifier id)
+ throws DataValidationFailedException {
+ DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
+ dataTreeModification.write(id, node);
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
+ dataTree.commit(prepare);
+ }
+
+ protected TipProducingDataTree getDataTree() throws ReactorException {
+ final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
+ dataTree.setSchemaContext(getSchemaCtx());
+ return dataTree;
+ }
+
+ ContainerNode getTopContainer(final String stringValue) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
+ .build();
+ }
+
+ ContainerNode getTopContainerWithLeafList(final String... stringValue) {
+ final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
+ for (final String value : stringValue) {
+ leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_LEAF_LIST_QNAME, value))
+ .withValue(value)
+ .build());
+ }
+
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(FOR_LEAF_LIST_QNAME))
+ .withChild(leafSetBuilder
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LEAF_LIST_QNAME))
+ .build())
+ .build())
+ .build();
+ }
+
+ MapNode getNestedList(final String listItemName, final String text) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
+ NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+ .build()
+ )
+ .build();
+ }
+
+ MapNode getDeepList(final String listItemName) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+ .withChild(
+ Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME,
+ NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .build()
+ )
+ .build();
+ }
+
+ SchemaContext getSchemaCtx() throws ReactorException {
+ final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
+ buildAction.addSource(
+ new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
+ return buildAction.buildEffective();
+ }
+
+ DataTreeModification getModification(final TipProducingDataTree dataTree) {
+ final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
+ return dataTreeSnapshot.newModification();
+ }
+
+
+ DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
+ final DataTreeModification dataTreeModification)
+ throws DataValidationFailedException {
+ dataTreeModification.ready();
+ dataTree.validate(dataTreeModification);
+ return dataTree.prepare(dataTreeModification);
+ }
+
+ ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) throws ReactorException {
+ return new ModificationDiff.ModificationDiffBuilder()
+ .setCtx(getSchemaCtx())
+ .build(prepare.getRootNode());
+ }
+
+ NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType(
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates,
+ final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+ return updates.values().stream()
+ .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass()))
+ .findFirst().get();
+ }
+
+ NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType(
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates,
+ final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
+ return updates.values().stream()
+ .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass()))
+ .findFirst().get();
+ }
+
+ void assertUpdate(final NormalizedNodeUpdate update,
+ final YangInstanceIdentifier idExpected,
+ final NormalizedNode<?, ?> beforeExpected,
+ final NormalizedNode<?, ?> afterExpected) {
+ assertThat(update.getId(), is(idExpected));
+ assertThat(update.getDataBefore(), is(beforeExpected));
+ assertThat(update.getDataAfter(), is(afterExpected));
+ }
+
+
+ ContainerNode getNestedContainerWithLeafList(final String... stringValue) {
+ final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
+ for (final String value : stringValue) {
+ leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_CONTAINER_LEAF_LIST, value))
+ .withValue(value)
+ .build());
+ }
+
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(leafSetBuilder
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_LEAF_LIST))
+ .build())
+ .build();
+ }
+
+ ContainerNode getNestedContainerWithChoice(final String caseLeafValue) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.choiceBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CHOICE))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(UNDER_NESTED_CASE))
+ .withValue(caseLeafValue)
+ .build()).build())
+ .build();
+ }
+
+ MapEntryNode getNestedListEntry(final String listItemName, final String text) {
+ return Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
+ NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
+ .build();
+ }
+
+ MapEntryNode getNestedListInContainerEntry(final String listItemName) {
+ return Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_IN_CONTAINER_QNAME,
+ IN_CONTAINER_NAME_LEAF_QNAME, listItemName))
+ .withChild(ImmutableNodes.leafNode(IN_CONTAINER_NAME_LEAF_QNAME, listItemName))
+ .build();
+ }
+
+ MapNode getNestedList(MapEntryNode... entries) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
+ .withValue(Arrays.asList(entries))
+ .build();
+ }
+
+ MapNode getNestedListInContainer(MapEntryNode... entries) {
+ return Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_IN_CONTAINER_QNAME))
+ .withValue(Arrays.asList(entries))
+ .build();
+ }
+
+ ContainerNode getNestedContWithLeafUnderAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LEAF))
+ .withValue(value).build()).build();
+ }
+
+ ContainerNode getNestedContWithListUnderAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LIST))
+ .withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(AUG_LIST, AUG_LIST_KEY,
+ value))
+ .build())
+ .build()).build();
+ }
+
+ ContainerNode getNestedContWithContUnderAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER_LEAF))
+ .withValue(value)
+ .build())
+ .build()).build();
+ }
+
+ ContainerNode getNestedContWithLeafListUnderAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.leafSetBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_LEAFLIST))
+ .withChildValue(value)
+ .build()).build();
+ }
+
+ ContainerNode getNestedContWithContainerUnderNestedAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_CONTAINER))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_CONTAINER_LEAF))
+ .withValue(value)
+ .build()).build()).build()).build();
+ }
+
+ ContainerNode getNestedContWithLeafListUnderNestedAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+ .withChild(Builders.leafSetBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LEAF_LIST))
+ .withChildValue(value)
+ .build()).build()).build();
+ }
+
+ ContainerNode getNestedContWithLeafUnderNestedAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LEAF))
+ .withValue(value)
+ .build()).build()).build();
+ }
+
+ ContainerNode getNestedContWithListUnderNestedAug(String value) {
+ return Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(AUG_CONTAINER))
+ .withChild(Builders.mapBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_AUG_LIST))
+ .withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(
+ new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_AUG_LIST,
+ NESTED_AUG_LIST_KEY, value)).build())
+ .build()).build()).build();
+ }
+
+ TipProducingDataTree prepareStateBeforeWithTopContainer(final NormalizedNode<?, ?> topContainerData)
+ throws ReactorException, DataValidationFailedException {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModificationOriginal = getModification(dataTree);
+ // non presence, but with valid child list
+ dataTreeModificationOriginal.write(TOP_CONTAINER_ID, topContainerData);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModificationOriginal);
+ dataTree.commit(prepare);
+ return dataTree;
+ }
+
+ DataTreeCandidateTip prepareStateAfterEmpty(final TipProducingDataTree dataTree)
+ throws DataValidationFailedException {
+ final NormalizedNode<?, ?> topContainerModified = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .build();
+ final DataTreeModification dataTreeModificationModified = getModification(dataTree);
+ dataTreeModificationModified.write(TOP_CONTAINER_ID, topContainerModified);
+ return prepareModification(dataTree, dataTreeModificationModified);
+ }
+
+ void assertCollectionContainsOnlyDeletes(final ModificationDiff modificationDiff) {
+ assertTrue(modificationDiff.getUpdates().entrySet().stream().map(Map.Entry::getValue)
+ .map(NormalizedNodeUpdate::getDataAfter)
+ .filter(Objects::nonNull).collect(Collectors.toList()).isEmpty());
+ }
+
+ void assertNodeModificationPresent(final ModificationDiff modificationDiff, final Set<QName> expected) {
+ assertTrue(modificationDiff.getUpdates().values().stream()
+ .allMatch(update -> expected.contains(update.getId().getLastPathArgument().getNodeType())));
+ }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java
new file mode 100644
index 000000000..c2b959809
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffAugRewriteDeleteTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffAugRewriteDeleteTest extends ModificationBaseTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffAugRewriteDeleteTest.class);
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerAugWithLeaf()
+ throws ReactorException, DataValidationFailedException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithLeafUnderAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LEAF));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerAugWithList()
+ throws ReactorException, DataValidationFailedException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithListUnderAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LIST));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerAugWithNonPresenceContainer()
+ throws ReactorException, DataValidationFailedException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithContUnderAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_CONTAINER_LEAF));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerAugWithLeafList()
+ throws ReactorException, DataValidationFailedException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithLeafListUnderAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(AUG_LEAFLIST));
+ }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java
new file mode 100644
index 000000000..9c92b4f85
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffNestedAugRewriteDeleteTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffNestedAugRewriteDeleteTest extends ModificationBaseTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffNestedAugRewriteDeleteTest.class);
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerNestedAugWithContainer()
+ throws DataValidationFailedException, ReactorException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithContainerUnderNestedAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_CONTAINER_LEAF));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerNestedAugWithLeafList()
+ throws DataValidationFailedException, ReactorException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithLeafListUnderNestedAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LEAF_LIST));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerNestedAugWithList()
+ throws DataValidationFailedException, ReactorException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithListUnderNestedAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LIST));
+ }
+
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerNestedAugWithLeaf()
+ throws DataValidationFailedException, ReactorException {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedContWithLeafUnderNestedAug("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_AUG_LEAF));
+ }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java
new file mode 100644
index 000000000..a1ede9691
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffRewriteDeleteTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ModificationDiffRewriteDeleteTest extends ModificationBaseTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffRewriteDeleteTest.class);
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification removes data by overriding by empty list
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerPreviousDataOverrideByEmpty() throws Exception {
+ final MapEntryNode alreadyPresent = getNestedListEntry("value", "txt");
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedList(alreadyPresent))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+ }
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification removes data by overriding by empty list. This case tests
+ * when there are multiple nested non-presence containers
+ */
+ @Test
+ public void testWriteNonPresenceMultipleNonEmptyContainerPreviousDataOverrideByEmpty() throws Exception {
+ final MapEntryNode alreadyPresent = getNestedListInContainerEntry("key");
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ // another non-presence container with list entry
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(getNestedListInContainer(alreadyPresent))
+ .build())
+ // direct list entry
+ .withChild(getNestedList(alreadyPresent))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect two new entries
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(2));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff,
+ ImmutableSet.of(NESTED_LIST_IN_CONTAINER_QNAME, NESTED_LIST_QNAME));
+ }
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification removes data by overriding. Tests case with leaf
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerLeaf() throws Exception {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ // another non-presence container with leaf
+ .withChild(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_QNAME))
+ .withChild(Builders.leafBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_CONTAINER_VAL))
+ .withValue("val").build())
+ .build())
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_CONTAINER_VAL));
+ }
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification removes data by overriding. Tests case with leaf-list
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerLeafList() throws Exception {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ // another non-presence container with leaf
+ .withChild(getNestedContainerWithLeafList())
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_CONTAINER_LEAF_LIST));
+ }
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification removes data by overriding. Tests case with choice/case
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerWithChoice() throws Exception {
+ final TipProducingDataTree dataTree = prepareStateBeforeWithTopContainer(Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ // another non-presence container with leaf
+ .withChild(getNestedContainerWithChoice("val"))
+ .build());
+ // now we have state with some data
+
+ // just empty non presence container
+ final DataTreeCandidateTip prepareModified = prepareStateAfterEmpty(dataTree);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ // is delete
+ assertCollectionContainsOnlyDeletes(modificationDiff);
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(UNDER_NESTED_CASE));
+ }
+}
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java
index 664b57378..cd980dd1e 100644
--- a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationDiffTest.java
@@ -23,59 +23,26 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
import java.util.Map;
import org.junit.Test;
-import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-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.tree.DataTree;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
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.ListNodeBuilder;
-import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
-import org.opendaylight.yangtools.yang.model.api.SchemaContext;
-import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
-import org.opendaylight.yangtools.yang.parser.stmt.reactor.CrossSourceStatementReactor;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangInferencePipeline;
-import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class ModificationDiffTest {
+public class ModificationDiffTest extends ModificationBaseTest {
- static final QName TOP_CONTAINER_QNAME =
- QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container");
- static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
- static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
- static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
- static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
- static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
- static final QName EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "empty");
- static final QName IN_EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "in-empty");
-
- static final QName FOR_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "for-leaf-list");
- static final QName NESTED_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-leaf-list");
-
-
- static final QName WITH_CHOICE_CONTAINER_QNAME =
- QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "with-choice");
- static final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice");
- static final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1");
- static final QName IN_CASE2_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case2");
-
- static final QName PRESENCE_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "presence");
-
- static final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
- static final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+ private static final Logger LOG = LoggerFactory.getLogger(ModificationDiffTest.class);
@Test
public void testInitialWrite() throws Exception {
@@ -150,17 +117,14 @@ public class ModificationDiffTest {
dataTreeModification.write(WITH_CHOICE_CONTAINER_ID, containerWithChoice);
final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class),
WITH_CHOICE_CONTAINER_ID, null, containerWithChoice);
}
- private DataTreeModification getModification(final TipProducingDataTree dataTree) {
- final DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
- return dataTreeSnapshot.newModification();
- }
+
@Test
public void testWriteNonPresenceEmptyContainer() throws Exception {
@@ -175,6 +139,65 @@ public class ModificationDiffTest {
assertThat(modificationDiff.getUpdates().size(), is(0));
}
+ /**
+ * Covers case when both non-presence and nested is not present
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainer() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModification = getModification(dataTree);
+ // non presence ,but with valid child list
+ final NormalizedNode<?, ?> topContainer = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedList("value","txt"))
+ .build();
+
+ dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepare);
+
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+ }
+
+ /**
+ * Covers case when non-presence container was already filled with some data,
+ * and modification just ads some other nested data
+ */
+ @Test
+ public void testWriteNonPresenceNonEmptyContainerPreviousData() throws Exception {
+ final TipProducingDataTree dataTree = getDataTree();
+ final DataTreeModification dataTreeModificationOriginal = getModification(dataTree);
+ // non presence, but with valid child list
+ final MapEntryNode alreadyPresent = getNestedListEntry("value", "txt");
+ final NormalizedNode<?, ?> topContainerOriginal = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedList(alreadyPresent))
+ .build();
+
+ dataTreeModificationOriginal.write(TOP_CONTAINER_ID, topContainerOriginal);
+ final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModificationOriginal);
+ dataTree.commit(prepare);
+ // now we have state with some data
+
+ final MapEntryNode newEntry = getNestedListEntry("value2", "txt2");
+ final NormalizedNode<?, ?> topContainerModified = Builders.containerBuilder()
+ .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
+ .withChild(getNestedList(alreadyPresent, newEntry))
+ .build();
+ final DataTreeModification dataTreeModificationModified = getModification(dataTree);
+ dataTreeModificationModified.write(TOP_CONTAINER_ID, topContainerModified);
+ final DataTreeCandidateTip prepareModified = prepareModification(dataTree, dataTreeModificationModified);
+
+ final ModificationDiff modificationDiff = getModificationDiff(prepareModified);
+ // should detect one new entry
+ LOG.debug(modificationDiff.toString());
+ assertThat(modificationDiff.getUpdates().size(), is(1));
+ assertNodeModificationPresent(modificationDiff, ImmutableSet.of(NESTED_LIST_QNAME));
+ }
+
+
@Test
public void testWriteNonPresenceEmptyNestedContainer() throws Exception {
final TipProducingDataTree dataTree = getDataTree();
@@ -195,14 +218,6 @@ public class ModificationDiffTest {
assertThat(modificationDiff.getUpdates().size(), is(1));
}
- private DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
- final DataTreeModification dataTreeModification)
- throws DataValidationFailedException {
- dataTreeModification.ready();
- dataTree.validate(dataTreeModification);
- return dataTree.prepare(dataTreeModification);
- }
-
@Test
public void testUpdateWrite() throws Exception {
final TipProducingDataTree dataTree = getDataTree();
@@ -214,16 +229,13 @@ public class ModificationDiffTest {
dataTreeModification.write(TOP_CONTAINER_ID, topContainerAfter);
final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertThat(updates.values().size(), is(1));
assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
}
- private ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) throws ReactorException {
- return ModificationDiff.recursivelyFromCandidateRoot(prepare.getRootNode(), getSchemaCtx());
- }
@Test
public void testUpdateMerge() throws Exception {
@@ -236,7 +248,7 @@ public class ModificationDiffTest {
dataTreeModification.merge(TOP_CONTAINER_ID, topContainerAfter);
final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertThat(updates.values().size(), is(1));
assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
@@ -252,7 +264,7 @@ public class ModificationDiffTest {
dataTreeModification.delete(TOP_CONTAINER_ID);
final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertThat(updates.values().size(), is(1));
assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, null);
@@ -276,7 +288,7 @@ public class ModificationDiffTest {
dataTree.validate(dataTreeModification);
DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
- Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class),
@@ -301,14 +313,6 @@ public class ModificationDiffTest {
assertThat(updates.size(), is(1 /*Actual list entry*/));
}
//
- private void assertUpdate(final ModificationDiff.NormalizedNodeUpdate update,
- final YangInstanceIdentifier idExpected,
- final NormalizedNode<?, ?> beforeExpected,
- final NormalizedNode<?, ?> afterExpected) {
- assertThat(update.getId(), is(idExpected));
- assertThat(update.getDataBefore(), is(beforeExpected));
- assertThat(update.getDataAfter(), is(afterExpected));
- }
@Test
public void testWriteTopContainerAndInnerList() throws Exception {
@@ -332,7 +336,7 @@ public class ModificationDiffTest {
final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(2));
assertThat(updates.values().size(), is(2));
@@ -345,21 +349,7 @@ public class ModificationDiffTest {
listEntryId));
}
- private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType(
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
- final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
- return updates.values().stream()
- .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass()))
- .findFirst().get();
- }
- private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType(
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates,
- final Class<? extends NormalizedNode<?, ?>> containerNodeClass) {
- return updates.values().stream()
- .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass()))
- .findFirst().get();
- }
@Test
public void testWriteDeepList() throws Exception {
@@ -415,7 +405,7 @@ public class ModificationDiffTest {
prepare = dataTree.prepare(dataTreeModification);
dataTree.commit(prepare);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), deepListEntryId, null, deepListEntry);
}
@@ -450,88 +440,10 @@ public class ModificationDiffTest {
dataTree.validate(dataTreeModification);
prepare = dataTree.prepare(dataTreeModification);
- final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+ final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
assertThat(updates.size(), is(1));
assertUpdate(getNormalizedNodeUpdateForBeforeType(updates, MapEntryNode.class), listItemId, mapNode.getValue().iterator().next(), null);
}
- static void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
- final YangInstanceIdentifier id)
- throws DataValidationFailedException {
- DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
- DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
- dataTreeModification.write(id, node);
- dataTreeModification.ready();
- dataTree.validate(dataTreeModification);
- DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
- dataTree.commit(prepare);
- }
-
- static TipProducingDataTree getDataTree() throws ReactorException {
- final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
- dataTree.setSchemaContext(getSchemaCtx());
- return dataTree;
- }
-
- static ContainerNode getTopContainer(final String stringValue) {
- return Builders.containerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
- .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
- .build();
- }
-
- static ContainerNode getTopContainerWithLeafList(final String... stringValue) {
- final ListNodeBuilder<String, LeafSetEntryNode<String>> leafSetBuilder = Builders.leafSetBuilder();
- for (final String value : stringValue) {
- leafSetBuilder.withChild(Builders.<String>leafSetEntryBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeWithValue<>(NESTED_LEAF_LIST_QNAME, value))
- .withValue(value)
- .build());
- }
- return Builders.containerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
- .withChild(Builders.containerBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(FOR_LEAF_LIST_QNAME))
- .withChild(leafSetBuilder
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LEAF_LIST_QNAME))
- .build())
- .build())
- .build();
- }
-
- static MapNode getNestedList(final String listItemName, final String text) {
- return Builders.mapBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
- .withChild(
- Builders.mapEntryBuilder()
- .withNodeIdentifier(
- new YangInstanceIdentifier.NodeIdentifierWithPredicates(NESTED_LIST_QNAME,
- NAME_LEAF_QNAME, listItemName))
- .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
- .withChild(ImmutableNodes.leafNode(TEXT_LEAF_QNAME, text))
- .build()
- )
- .build();
- }
-
- private MapNode getDeepList(final String listItemName) {
- return Builders.mapBuilder()
- .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
- .withChild(
- Builders.mapEntryBuilder()
- .withNodeIdentifier(
- new YangInstanceIdentifier.NodeIdentifierWithPredicates(DEEP_LIST_QNAME,
- NAME_LEAF_QNAME, listItemName))
- .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, listItemName))
- .build()
- )
- .build();
- }
-
- static SchemaContext getSchemaCtx() throws ReactorException {
- final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
- buildAction.addSource(new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
- return buildAction.buildEffective();
- }
} \ No newline at end of file
diff --git a/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java
new file mode 100644
index 000000000..27d16307a
--- /dev/null
+++ b/infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModificationMetadata.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.data.impl;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+
+abstract class ModificationMetadata {
+
+ private static final String YANG_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:test:diff";
+ final QName TOP_CONTAINER_QNAME = QName.create(YANG_NAMESPACE, "2015-01-05", "top-container");
+ final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
+ final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
+ final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
+ final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
+ final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
+ final QName EMPTY_QNAME = QName.create(TOP_CONTAINER_QNAME, "empty");
+
+ final QName FOR_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "for-leaf-list");
+ final QName NESTED_LEAF_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-leaf-list");
+
+ final QName WITH_CHOICE_CONTAINER_QNAME = QName.create(YANG_NAMESPACE, "2015-01-05", "with-choice");
+ final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice");
+ final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1");
+
+ final QName PRESENCE_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "presence");
+
+ final QName NESTED_CONTAINER_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-container");
+ final QName NESTED_LIST_IN_CONTAINER_QNAME = QName.create(NESTED_CONTAINER_QNAME, "nested-list-in-container");
+ final QName IN_CONTAINER_NAME_LEAF_QNAME = QName.create(NESTED_LIST_IN_CONTAINER_QNAME, "name");
+ final QName NESTED_CONTAINER_VAL = QName.create(NESTED_CONTAINER_QNAME, "nested-container-val");
+ final QName NESTED_CONTAINER_LEAF_LIST = QName.create(NESTED_CONTAINER_QNAME, "nested-container-leaf-list");
+
+ final QName NESTED_CHOICE = QName.create(NESTED_CONTAINER_QNAME,"nested-choice");
+ final QName NESTED_CASE=QName.create(NESTED_CHOICE,"nested-case");
+ final QName UNDER_NESTED_CASE=QName.create(NESTED_CASE,"under-nested-case");
+
+ final QName AUG_LEAF = QName.create(NESTED_CONTAINER_QNAME,"under-aug-leaf");
+ final QName AUG_LEAFLIST = QName.create(NESTED_CONTAINER_QNAME,"under-aug-leaflist");
+ final QName AUG_CONTAINER = QName.create(NESTED_CONTAINER_QNAME,"under-aug-container");
+ final QName AUG_CONTAINER_LEAF = QName.create(NESTED_CONTAINER_QNAME,"under-aug-cont-leaf");
+ final QName AUG_LIST = QName.create(NESTED_CONTAINER_QNAME,"under-aug-list");
+ final QName AUG_LIST_KEY = QName.create(NESTED_CONTAINER_QNAME,"under-aug-list-key");
+
+ final QName NESTED_AUG_CONTAINER = QName.create(AUG_CONTAINER, "nested-under-aug-container");
+ final QName NESTED_AUG_CONTAINER_LEAF = QName.create(AUG_CONTAINER, "nested-under-aug-container-leaf");
+ final QName NESTED_AUG_LEAF = QName.create(AUG_CONTAINER, "nested-under-aug-leaf");
+ final QName NESTED_AUG_LIST = QName.create(AUG_CONTAINER, "nested-under-aug-list");
+ final QName NESTED_AUG_LIST_KEY = QName.create(AUG_CONTAINER, "nested-under-aug-list-key");
+ final QName NESTED_AUG_LEAF_LIST = QName.create(AUG_CONTAINER, "nested-under-aug-leaf-list");
+
+ final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
+ final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
+}
diff --git a/infra/data-impl/src/test/resources/test-diff.yang b/infra/data-impl/src/test/resources/test-diff.yang
index b7a0c7e7f..f86ad32a7 100644
--- a/infra/data-impl/src/test/resources/test-diff.yang
+++ b/infra/data-impl/src/test/resources/test-diff.yang
@@ -28,6 +28,32 @@ module test-diff {
}
}
+ container nested-container {
+ list nested-list-in-container {
+ key "name";
+
+ leaf name {
+ type string;
+ }
+ }
+
+ leaf nested-container-val {
+ type string;
+ }
+
+ leaf-list nested-container-leaf-list {
+ type string;
+ }
+
+ choice nested-choice {
+ case nested-case{
+ leaf under-nested-case{
+ type string;
+ }
+ }
+ }
+ }
+
list nested-list {
key "name";
@@ -50,6 +76,52 @@ module test-diff {
}
}
+ augment /top-container/nested-container {
+ container under-aug-container{
+ leaf under-aug-cont-leaf{
+ type string;
+ }
+ }
+
+ leaf under-aug-leaf {
+ type string;
+ }
+
+ list under-aug-list {
+ key under-aug-list-key;
+ leaf under-aug-list-key{
+ type string;
+ }
+ }
+
+ leaf-list under-aug-leaflist{
+ type string;
+ }
+ }
+
+ augment /top-container/nested-container/under-aug-container {
+ container nested-under-aug-container {
+ leaf nested-under-aug-container-leaf {
+ type string;
+ }
+ }
+
+ leaf nested-under-aug-leaf{
+ type string;
+ }
+
+ list nested-under-aug-list{
+ key nested-under-aug-list-key;
+ leaf nested-under-aug-list-key{
+ type string;
+ }
+ }
+
+ leaf-list nested-under-aug-leaf-list {
+ type string;
+ }
+ }
+
container with-choice {
choice choice {