HONEYCOMB-94 Reimplement writer registry with better ordering options
authorMaros Marsalek <[email protected]>
Wed, 29 Jun 2016 07:14:51 +0000 (09:14 +0200)
committerMaros Marsalek <[email protected]>
Wed, 13 Jul 2016 09:24:26 +0000 (11:24 +0200)
Now the registry is flat and allows for full control of writer execution order

Change-Id: I864e1d676588ffe59b596145e0829e81b1a1ed2f
Signed-off-by: Maros Marsalek <[email protected]>
54 files changed:
v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java
v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java [new file with mode: 0644]
v3po/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java
v3po/data-impl/src/main/yang/data-impl.yang
v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java
v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java
v3po/data-impl/src/test/resources/test-diff.yang
v3po/features/pom.xml
v3po/features/src/main/features/features.xml
v3po/impl/src/main/config/default-config.xml
v3po/impl/src/main/config/initializer-config.xml
v3po/translate-api/pom.xml
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java [deleted file]
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java [new file with mode: 0644]
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java [new file with mode: 0644]
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java [new file with mode: 0644]
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistry.java
v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java [new file with mode: 0644]
v3po/translate-api/src/main/yang/translate-api.yang
v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java
v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java [deleted file]
v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java [deleted file]
v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java [new file with mode: 0644]
v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriter.java [moved from v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeRootWriter.java with 55% similarity]
v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java [new file with mode: 0644]
v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java [new file with mode: 0644]
v3po/translate-utils/pom.xml
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java [deleted file]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java [deleted file]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java [deleted file]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java [deleted file]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java [deleted file]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java [new file with mode: 0644]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java [new file with mode: 0644]
v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java [new file with mode: 0644]
v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java
v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java
v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java
v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java
v3po/translate-utils/src/main/yang/translate-utils.yang
v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java [deleted file]
v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java [new file with mode: 0644]
v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java [new file with mode: 0644]
v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java [new file with mode: 0644]
v3po/v3po2vpp/src/main/config/default-config.xml
v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java
v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/SubinterfaceAugmentationWriterFactory.java
v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/VppHoneycombWriterModule.java
v3po/v3po2vpp/src/main/yang/v3po2vpp.yang
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java [deleted file]
v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java [deleted file]
v3po/vpp-translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/v3po/util/NamingContext.java

index 91de188..77aa12a 100644 (file)
 
 package io.fd.honeycomb.v3po.data.impl;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.util.concurrent.Futures.immediateCheckedFuture;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
-import com.google.common.collect.Sets;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.CheckedFuture;
 import io.fd.honeycomb.v3po.data.DataModification;
 import io.fd.honeycomb.v3po.data.ReadableDataManager;
 import io.fd.honeycomb.v3po.translate.TranslationException;
+import io.fd.honeycomb.v3po.translate.util.RWUtils;
 import io.fd.honeycomb.v3po.translate.util.write.TransactionMappingContext;
 import io.fd.honeycomb.v3po.translate.util.write.TransactionWriteContext;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
 import io.fd.honeycomb.v3po.translate.write.WriteContext;
 import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
 import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 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.DataTreeCandidateNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -110,6 +108,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
         @Override
         protected void processCandidate(final DataTreeCandidate candidate)
             throws TranslationException {
+
             final DataTreeCandidateNode rootNode = candidate.getRootNode();
             final YangInstanceIdentifier rootPath = candidate.getRootPath();
             LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}",
@@ -119,13 +118,12 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
                     ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, rootNode);
             LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
 
-            final Map<InstanceIdentifier<?>, DataObject> nodesBefore = toBindingAware(modificationDiff.getModificationsBefore());
-            LOG.debug("ConfigDataTree.modify() extracted nodesBefore={}", nodesBefore.keySet());
-            final Map<InstanceIdentifier<?>, DataObject> nodesAfter = toBindingAware(modificationDiff.getModificationsAfter());
-            LOG.debug("ConfigDataTree.modify() extracted nodesAfter={}", nodesAfter.keySet());
+            // Distinguish between updates (create + update) and deletes
+            final WriterRegistry.DataObjectUpdates baUpdates = toBindingAware(modificationDiff.getUpdates());
+            LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates);
 
             try (final WriteContext ctx = getTransactionWriteContext()) {
-                writerRegistry.update(nodesBefore, nodesAfter, ctx);
+                writerRegistry.update(baUpdates, ctx);
 
                 final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
                     ((TransactionMappingContext) ctx.getMappingContext()).submit();
@@ -152,7 +150,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
                 LOG.error(msg, e);
                 throw new TranslationException(msg, e);
             } catch (TranslationException e) {
-                LOG.error("Error while processing data change (before={}, after={})", nodesBefore, nodesAfter, e);
+                LOG.error("Error while processing data change (updates={})", baUpdates, e);
                 throw e;
             }
         }
@@ -167,177 +165,71 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
             return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
         }
 
-        private Map<InstanceIdentifier<?>, DataObject> toBindingAware(final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> biNodes) {
+        private WriterRegistry.DataObjectUpdates toBindingAware(
+                final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> biNodes) {
             return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
         }
     }
 
     @VisibleForTesting
-    static Map<InstanceIdentifier<?>, DataObject> toBindingAware(final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> biNodes,
-                                                                 final BindingNormalizedNodeSerializer serializer) {
-        final HashMap<InstanceIdentifier<?>, DataObject> transformed = new HashMap<>(biNodes.size());
-        for (Map.Entry<YangInstanceIdentifier, NormalizedNode<?, ?>> biEntry : biNodes.entrySet()) {
-            final Map.Entry<InstanceIdentifier<?>, DataObject> baEntry = serializer.fromNormalizedNode(biEntry.getKey(), biEntry.getValue());
-            if (baEntry != null) {
-                transformed.put(baEntry.getKey(), baEntry.getValue());
+    static WriterRegistry.DataObjectUpdates toBindingAware(
+            final Map<YangInstanceIdentifier, ModificationDiff.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()) {
+            final InstanceIdentifier<?> unkeyedIid =
+                    RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey()));
+
+            ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
+            final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
+            if (dataObjectUpdate != null) {
+                if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
+                    dataObjectDeletes.put(unkeyedIid, ((DataObjectUpdate.DataObjectDelete) dataObjectUpdate));
+                } else {
+                    dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
+                }
             }
         }
-        return transformed;
+        return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes);
     }
 
-    @VisibleForTesting
-    static final class ModificationDiff {
-
-        private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap(), Collections.emptyMap());
-        private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE);
-
-        private final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsBefore;
-        private final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsAfter;
+    @Nullable
+    private static DataObjectUpdate toDataObjectUpdate(
+            final ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate,
+            final BindingNormalizedNodeSerializer serializer) {
 
-        private ModificationDiff(@Nonnull final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsBefore,
-                                 @Nonnull final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationsAfter) {
-            this.modificationsBefore = modificationsBefore;
-            this.modificationsAfter = modificationsAfter;
-        }
-
-        Map<YangInstanceIdentifier, NormalizedNode<?, ?>> getModificationsBefore() {
-            return modificationsBefore;
-        }
+        InstanceIdentifier<?> baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId());
+        checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId());
 
-        Map<YangInstanceIdentifier, NormalizedNode<?, ?>> getModificationsAfter() {
-            return modificationsAfter;
-        }
-
-        private ModificationDiff merge(final ModificationDiff other) {
-            if (this == EMPTY_DIFF) {
-                return other;
-            }
+        DataObject dataObjectBefore = getDataObject(serializer,
+                normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId());
+        DataObject dataObjectAfter =
+                getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId());
 
-            if (other == EMPTY_DIFF) {
-                return this;
-            }
-
-            return new ModificationDiff(join(modificationsBefore, other.modificationsBefore),
-                    join(modificationsAfter, other.modificationsAfter));
-        }
-
-        private static Map<YangInstanceIdentifier, NormalizedNode<?, ?>> join(
-                final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> mapOne,
-                final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> mapTwo) {
-            // Check unique modifications
-            // TODO Probably not necessary to check
-            final Sets.SetView<YangInstanceIdentifier> duplicates = Sets.intersection(mapOne.keySet(), mapTwo.keySet());
-            checkArgument(duplicates.size() == 0, "Duplicates detected: %s. In maps: %s and %s", duplicates, mapOne, mapTwo);
-            final HashMap<YangInstanceIdentifier, NormalizedNode<?, ?>> joined = new HashMap<>();
-            joined.putAll(mapOne);
-            joined.putAll(mapTwo);
-            return joined;
-        }
-
-        private static ModificationDiff createFromBefore(YangInstanceIdentifier idBefore, DataTreeCandidateNode candidate) {
-            return new ModificationDiff(
-                    Collections.singletonMap(idBefore, candidate.getDataBefore().get()),
-                    Collections.emptyMap());
-        }
-
-        private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) {
-            return new ModificationDiff(
-                    Collections.singletonMap(id, candidate.getDataBefore().get()),
-                    Collections.singletonMap(id, candidate.getDataAfter().get()));
-        }
-
-        private static ModificationDiff createFromAfter(YangInstanceIdentifier idAfter, DataTreeCandidateNode candidate) {
-            return new ModificationDiff(
-                    Collections.emptyMap(),
-                    Collections.singletonMap(idAfter, candidate.getDataAfter().get()));
-        }
-
-        /**
-         * Produce a diff from a candidate node recursively.
-         */
-        @Nonnull
-        static ModificationDiff recursivelyFromCandidate(@Nonnull final YangInstanceIdentifier yangIid,
-                                                         @Nonnull final DataTreeCandidateNode currentCandidate) {
-            switch (currentCandidate.getModificationType()) {
-                case APPEARED:
-                case DISAPPEARED:
-                case UNMODIFIED: {
-                    // (dis)appeared nodes are not important, no real data to process
-                    return ModificationDiff.EMPTY_DIFF;
-                }
-                case WRITE: {
-                    return currentCandidate.getDataBefore().isPresent()
-                            ? ModificationDiff.create(yangIid, currentCandidate)
-                            : ModificationDiff.createFromAfter(yangIid, currentCandidate);
-                    // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes
-                }
-                case DELETE:
-                    return ModificationDiff.createFromBefore(yangIid, currentCandidate);
-                case SUBTREE_MODIFIED: {
-                    // Modifications here are presented also for leaves. However that kind of granularity is not required
-                    // So if there's a modified leaf, mark current complex node also as modification
-                    java.util.Optional<Boolean> leavesModified = currentCandidate.getChildNodes().stream()
-                            .filter(ModificationDiff::isLeaf)
-                            // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug
-                            // and that messes up our modifications collection here, so we need to skip
-                            .filter(ModificationDiff::isModification)
-                            .map(child -> LEAF_MODIFICATIONS.contains(child.getModificationType()))
-                            .reduce((boolOne, boolTwo) -> boolOne || boolTwo);
-
-                    if (leavesModified.isPresent() && leavesModified.get()) {
-                        return ModificationDiff.create(yangIid, currentCandidate);
-                        // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes even if current
-                        // was modified
-                    } else {
-                        // SUBTREE MODIFIED, no modification on current, but process children recursively
-                        return currentCandidate.getChildNodes().stream()
-                                // not interested in modifications to leaves
-                                .filter(child -> !isLeaf(child))
-                                .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate))
-                                .reduce(ModificationDiff::merge)
-                                .orElse(EMPTY_DIFF);
-                    }
-                }
-                default:
-                    throw new IllegalStateException("Unknown modification type: "
-                            + currentCandidate.getModificationType() + ". Unsupported");
-            }
-        }
+        return dataObjectBefore == null && dataObjectAfter == null
+                ? null
+                : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter);
+    }
 
-        /**
-         * Check whether candidate.before and candidate.after is different. If not
-         * return false.
-         */
-        private static boolean isModification(final DataTreeCandidateNode candidateNode) {
-            if (candidateNode.getDataBefore().isPresent()) {
-                if (candidateNode.getDataAfter().isPresent()) {
-                    return !candidateNode.getDataAfter().get().equals(candidateNode.getDataBefore().get());
-                } else {
-                    return true;
-                }
+    @Nullable
+    private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer,
+                                            @Nullable final NormalizedNode<?, ?> data,
+                                            @Nonnull final YangInstanceIdentifier id) {
+        DataObject dataObject = null;
+        if (data != null) {
+            final Map.Entry<InstanceIdentifier<?>, DataObject> dataObjectEntry =
+                    serializer.fromNormalizedNode(id, data);
+            if (dataObjectEntry != null) {
+                dataObject = dataObjectEntry.getValue();
             }
-
-            // considering not a modification if data after is also null
-            return candidateNode.getDataAfter().isPresent();
-        }
-
-        /**
-         * Check whether candidate node is for a leaf type node.
-         */
-        private static boolean isLeaf(final DataTreeCandidateNode candidateNode) {
-            // orNull intentional, some candidate nodes have both data after and data before null
-            return candidateNode.getDataAfter().orNull() instanceof LeafNode<?>
-                    || candidateNode.getDataBefore().orNull() instanceof LeafNode<?>;
-        }
-
-        @Override
-        public String toString() {
-            return "ModificationDiff{"
-                    + "modificationsBefore=" + modificationsBefore
-                    + ", modificationsAfter=" + modificationsAfter
-                    + '}';
         }
+        return dataObject;
     }
+
 }
 
 
diff --git a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java
new file mode 100644 (file)
index 0000000..abc0062
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2016 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.v3po.data.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+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.LeafNode;
+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;
+
+/**
+ * Recursively collects and provides all unique and non-null modifications (modified normalized nodes).
+ */
+final class ModificationDiff {
+
+    private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap());
+    private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE);
+
+    private final Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates;
+
+    private ModificationDiff(@Nonnull Map<YangInstanceIdentifier, NormalizedNodeUpdate> updates) {
+        this.updates = updates;
+    }
+
+    /**
+     * Get processed modifications.
+     *
+     * @return mapped modifications, where key is keyed {@link YangInstanceIdentifier}.
+     */
+    Map<YangInstanceIdentifier, NormalizedNodeUpdate> getUpdates() {
+        return updates;
+    }
+
+    private ModificationDiff merge(final ModificationDiff other) {
+        if (this == EMPTY_DIFF) {
+            return other;
+        }
+
+        if (other == EMPTY_DIFF) {
+            return this;
+        }
+
+        return new ModificationDiff(join(updates, other.updates));
+    }
+
+    private static Map<YangInstanceIdentifier, NormalizedNodeUpdate> join(Map<YangInstanceIdentifier, NormalizedNodeUpdate> first,
+                                                                          Map<YangInstanceIdentifier, NormalizedNodeUpdate> second) {
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> merged = new HashMap<>();
+        merged.putAll(first);
+        merged.putAll(second);
+        return merged;
+    }
+
+    private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) {
+        return new ModificationDiff(ImmutableMap.of(id, NormalizedNodeUpdate.create(id, candidate)));
+    }
+
+    /**
+     * 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 YangInstanceIdentifier yangIid,
+                                                     @Nonnull final DataTreeCandidateNode currentCandidate) {
+        // recursively process child nodes for exact modifications
+        return recursivelyChildrenFromCandidate(yangIid, currentCandidate)
+                // also add modification on current level, if elligible
+                .merge(isModification(currentCandidate)
+                        ? ModificationDiff.create(yangIid, currentCandidate)
+                        : EMPTY_DIFF);
+    }
+
+    /**
+     * 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 DataTreeCandidateNode currentCandidate) {
+        // Mixin nodes are not considered modifications
+        if (isMixin(currentCandidate) && !isAugment(currentCandidate)) {
+            return false;
+        } else {
+            return isCurrentModified(currentCandidate);
+        }
+    }
+
+    private static Boolean isCurrentModified(final @Nonnull DataTreeCandidateNode currentCandidate) {
+        // Check if there are any modified leaves and if so, consider current node as modified
+        final Boolean directLeavesModified = currentCandidate.getChildNodes().stream()
+                .filter(ModificationDiff::isLeaf)
+                // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug
+                // and that messes up our modifications collection here, so we need to skip
+                .filter(ModificationDiff::isBeforeAndAfterDifferent)
+                .filter(child -> LEAF_MODIFICATIONS.contains(child.getModificationType()))
+                .findFirst()
+                .isPresent();
+
+        return directLeavesModified
+                // Also check choices (choices do not exist in BA world and if anything within a choice was modified,
+                // consider its parent as being modified)
+                || currentCandidate.getChildNodes().stream()
+                        .filter(ModificationDiff::isChoice)
+                        // Recursively check each choice if there was any change to it
+                        .filter(ModificationDiff::isCurrentModified)
+                        .findFirst()
+                        .isPresent();
+    }
+
+    /**
+     * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}.
+     */
+    private static ModificationDiff recursivelyChildrenFromCandidate(final @Nonnull YangInstanceIdentifier yangIid,
+                                                                     final @Nonnull DataTreeCandidateNode currentCandidate) {
+        // recursively process child nodes for specific modifications
+        return currentCandidate.getChildNodes().stream()
+                // not interested in modifications to leaves
+                .filter(child -> !isLeaf(child))
+                .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate))
+                .reduce(ModificationDiff::merge)
+                .orElse(EMPTY_DIFF);
+    }
+
+    /**
+     * Check whether candidate.before and candidate.after is different. If not return false.
+     */
+    private static boolean isBeforeAndAfterDifferent(@Nonnull final DataTreeCandidateNode candidateNode) {
+        if (candidateNode.getDataBefore().isPresent()) {
+            return !candidateNode.getDataBefore().get().equals(candidateNode.getDataAfter().orNull());
+        }
+
+        // considering not a modification if data after is also null
+        return candidateNode.getDataAfter().isPresent();
+    }
+
+    /**
+     * Check whether candidate node is for a leaf type node.
+     */
+    private static boolean isLeaf(final DataTreeCandidateNode candidateNode) {
+        // orNull intentional, some candidate nodes have both data after and data before null
+        return candidateNode.getDataAfter().orNull() instanceof LeafNode<?>
+                || candidateNode.getDataBefore().orNull() instanceof LeafNode<?>;
+    }
+
+    /**
+     * Check whether candidate node is for a Mixin type node.
+     */
+    private static boolean isMixin(final DataTreeCandidateNode candidateNode) {
+        // orNull intentional, some candidate nodes have both data after and data before null
+        return candidateNode.getDataAfter().orNull() instanceof MixinNode
+                || candidateNode.getDataBefore().orNull() instanceof MixinNode;
+    }
+
+    /**
+     * Check whether candidate node is for an Augmentation type node.
+     */
+    private static boolean isAugment(final DataTreeCandidateNode candidateNode) {
+        // orNull intentional, some candidate nodes have both data after and data before null
+        return candidateNode.getDataAfter().orNull() instanceof AugmentationNode
+                || candidateNode.getDataBefore().orNull() instanceof AugmentationNode;
+    }
+
+    /**
+     * Check whether candidate node is for a Choice type node.
+     */
+    private static boolean isChoice(final DataTreeCandidateNode candidateNode) {
+        // orNull intentional, some candidate nodes have both data after and data before null
+        return candidateNode.getDataAfter().orNull() instanceof ChoiceNode
+                || candidateNode.getDataBefore().orNull() instanceof ChoiceNode;
+    }
+
+    @Override
+    public String toString() {
+        return "ModificationDiff{updates=" + updates + '}';
+    }
+
+    /**
+     * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}.
+     */
+    static final class NormalizedNodeUpdate {
+
+        @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;
+        }
+
+        @Nullable
+        public NormalizedNode<?, ?> getDataBefore() {
+            return dataBefore;
+        }
+
+        @Nullable
+        public NormalizedNode<?, ?> getDataAfter() {
+            return dataAfter;
+        }
+
+        @Nonnull
+        public YangInstanceIdentifier getId() {
+            return id;
+        }
+
+        static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
+                                           @Nonnull final DataTreeCandidateNode candidate) {
+            return create(id, candidate.getDataBefore().orNull(), candidate.getDataAfter().orNull());
+        }
+
+        static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id,
+                                           @Nullable final NormalizedNode<?, ?> dataBefore,
+                                           @Nullable final NormalizedNode<?, ?> dataAfter) {
+            checkArgument(!(dataBefore == null && dataAfter == null), "Both before and after data are null");
+            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
+                    + '}';
+        }
+    }
+
+}
index 3e871e0..eabcdcb 100644 (file)
@@ -38,8 +38,8 @@ public class ConfigDataTreeModule extends
     public java.lang.AutoCloseable createInstance() {
         LOG.debug("ConfigDataTreeModule.createInstance()");
         return new CloseableConfigDataTree(
-                new ModifiableDataTreeDelegator(getSerializerDependency(), getDataTreeDependency(), getWriterRegistryDependency(),
-                    getContextBindingBrokerDependency()));
+                new ModifiableDataTreeDelegator(getSerializerDependency(), getDataTreeDependency(),
+                        getWriterRegistryBuilderDependency().build(), getContextBindingBrokerDependency()));
     }
 
     private static final class CloseableConfigDataTree implements ModifiableDataManager, AutoCloseable {
index 0ea76cf..9228463 100644 (file)
@@ -108,11 +108,11 @@ module data-impl {
                 }
             }
 
-            container writer-registry {
+            container writer-registry-builder {
                 uses config:service-ref {
                     refine type {
                         mandatory true;
-                        config:required-identity tapi:honeycomb-writer-registry;
+                        config:required-identity tapi:honeycomb-writer-registry-builder;
                     }
                 }
             }
index 086636d..c265366 100644 (file)
 
 package io.fd.honeycomb.v3po.data.impl;
 
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyMap;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import io.fd.honeycomb.v3po.data.DataModification;
 import io.fd.honeycomb.v3po.translate.TranslationException;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
 import io.fd.honeycomb.v3po.translate.write.WriteContext;
 import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
+import java.util.AbstractMap;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -46,18 +53,14 @@ import org.mockito.Mock;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 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.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.DataTreeCandidateNode;
-import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
 
 public class ModifiableDataTreeDelegatorTest {
 
@@ -65,112 +68,66 @@ public class ModifiableDataTreeDelegatorTest {
     private WriterRegistry writer;
     @Mock
     private BindingNormalizedNodeSerializer serializer;
-    @Mock
     private DataTree dataTree;
     @Mock
     private org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification modification;
     @Mock
     private DataBroker contextBroker;
+    @Mock
+    private org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction tx;
 
     private ModifiableDataTreeManager configDataTree;
 
+    static final InstanceIdentifier<?> DEFAULT_ID = InstanceIdentifier.create(DataObject.class);
+    static DataObject DEFAULT_DATA_OBJECT = mockDataObject("serialized", DataObject.class);
+
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         initMocks(this);
-        configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, writer, contextBroker);
-    }
-
-    @Test
-    public void testRead() throws Exception {
-        final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot
-                snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class);
-        when(dataTree.takeSnapshot()).thenReturn(snapshot);
-        when(snapshot.newModification()).thenReturn(modification);
+        dataTree = ModificationDiffTest.getDataTree();
+        when(contextBroker.newReadWriteTransaction()).thenReturn(tx);
+        when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null));
 
-        final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class);
-        final Optional node = mock(Optional.class);
-        doReturn(node).when(modification).readNode(path);
+        when(serializer.fromYangInstanceIdentifier(any(YangInstanceIdentifier.class))).thenReturn(((InstanceIdentifier) DEFAULT_ID));
+        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);
 
-        final DataModification dataTreeSnapshot = configDataTree.newModification();
-        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> future =
-                dataTreeSnapshot.read(path);
-
-        verify(dataTree, times(2)).takeSnapshot();
-        verify(modification).readNode(path);
-
-        assertTrue(future.isDone());
-        assertEquals(node, future.get());
+        configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, writer, contextBroker);
     }
 
     @Test
-    public void testNewModification() throws Exception {
-        final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot
-                snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class);
-        when(dataTree.takeSnapshot()).thenReturn(snapshot);
-        when(snapshot.newModification()).thenReturn(modification);
-
-        configDataTree.newModification();
-        // Snapshot captured twice, so that original data could be provided to translation layer without any possible
-        // modification
-        verify(dataTree, times(2)).takeSnapshot();
-        verify(snapshot, times(2)).newModification();
+    public void testRead() throws Exception {
+        final ContainerNode topContainer = ModificationDiffTest.getTopContainer("topContainer");
+        ModificationDiffTest.addNodeToTree(dataTree, topContainer, ModificationDiffTest.TOP_CONTAINER_ID);
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read =
+                configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID);
+        final CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read2 =
+                configDataTree.newModification().read(ModificationDiffTest.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);
     }
 
     @Test
     public void testCommitSuccessful() throws Exception {
-        final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot
-            snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class);
-        when(dataTree.takeSnapshot()).thenReturn(snapshot);
-        when(snapshot.newModification()).thenReturn(modification);
-        final DataTreeCandidate prepare = mock(DataTreeCandidate.class);
-        doReturn(prepare).when(dataTree).prepare(modification);
-
-        final org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction ctxTransaction = mock(
-            org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction.class);
-        doReturn(ctxTransaction).when(contextBroker).newReadWriteTransaction();
-        doReturn(Futures.immediateCheckedFuture(null)).when(ctxTransaction).submit();
-
-        final DataObject dataBefore = mockDataObject("before", Ethernet.class);
-        final DataObject dataAfter = mockDataObject("after", Ethernet.class);
-
-        // Prepare modification:
-        final DataTreeCandidateNode rootNode = mockRootNode();
-        doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
-        final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
-        doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
-        doReturn(ModificationType.WRITE).when(childNode).getModificationType();
-
-        // data before:
-        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
-        when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
-        // data after:
-        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
-        when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
-
-        // Run the test
-        doReturn(rootNode).when(prepare).getRootNode();
+        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
+
         final DataModification dataModification = configDataTree.newModification();
+        dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+        dataModification.validate();
         dataModification.commit();
 
-        // Verify all changes were processed:
-        verify(writer).update(
-                mapOf(dataBefore, Ethernet.class),
-                mapOf(dataAfter, Ethernet.class),
-                any(WriteContext.class));
-
-        // Verify modification was validated
-        verify(dataTree).validate(modification);
-        // Verify context transaction was finished
-        verify(ctxTransaction).submit();
-    }
-
-    private Map<InstanceIdentifier<?>, DataObject> mapOf(final DataObject dataBefore, final Class<Ethernet> type) {
-        return eq(Collections.singletonMap(InstanceIdentifier.create(type),dataBefore));
+        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());
     }
 
-    private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
+    private static DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
         final DataObject dataBefore = mock(classToMock, name);
         doReturn(classToMock).when(dataBefore).getImplementedInterface();
         return dataBefore;
@@ -178,171 +135,135 @@ public class ModifiableDataTreeDelegatorTest {
 
     @Test
     public void testCommitUndoSuccessful() throws Exception {
-        final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot
-            snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class);
-        when(dataTree.takeSnapshot()).thenReturn(snapshot);
-        when(snapshot.newModification()).thenReturn(modification);
-        final DataTreeCandidate prepare = mock(DataTreeCandidate.class);
-        doReturn(prepare).when(dataTree).prepare(modification);
-
-        // Prepare data changes:
-        final DataObject dataBefore = mockDataObject("before", Ethernet.class);
-        final DataObject dataAfter = mockDataObject("after", Ethernet.class);
-
-        final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter reverter = mock(
-            io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.class);
+        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
 
         // Fail on update:
+        final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
         final TranslationException failedOnUpdateException = new TranslationException("update failed");
-        doThrow(new io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter,
-                failedOnUpdateException)).when(writer).update(anyMap(), anyMap(), any(WriteContext.class));
-
-        // Prepare modification:
-        final DataTreeCandidateNode rootNode = mockRootNode();
-        doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
-        final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
-        doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
-        doReturn(ModificationType.WRITE).when(childNode).getModificationType();
-
-        // data before:
-        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
-        when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
-        // data after:
-        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
-        when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
-
-        // Run the test
+        doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException))
+                .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+
         try {
-            doReturn(rootNode).when(prepare).getRootNode();
+            // Run the test
             final DataModification dataModification = configDataTree.newModification();
+            dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+            dataModification.validate();
             dataModification.commit();
-        } catch (io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException e) {
-            verify(writer).update(anyMap(), anyMap(), any(WriteContext.class));
+            fail("WriterRegistry.BulkUpdateException was expected");
+        } catch (WriterRegistry.BulkUpdateException e) {
+            verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+            assertThat(e.getFailedIds(), hasItem(DEFAULT_ID));
             verify(reverter).revert();
             assertEquals(failedOnUpdateException, e.getCause());
-            return;
         }
-
-        fail("WriterRegistry.BulkUpdateException was expected");
     }
 
     @Test
     public void testCommitUndoFailed() throws Exception {
-        final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot
-            snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class);
-        when(dataTree.takeSnapshot()).thenReturn(snapshot);
-        when(snapshot.newModification()).thenReturn(modification);
-        final DataTreeCandidate prepare = mock(DataTreeCandidate.class);
-        doReturn(prepare).when(dataTree).prepare(modification);
-
-        // Prepare data changes:
-        final DataObject dataBefore = mockDataObject("before", Ethernet.class);
-        final DataObject dataAfter = mockDataObject("after", Ethernet.class);
-
-        final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter reverter = mock(
-            io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.class);
+        final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue");
 
         // Fail on update:
-        doThrow(new io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter,
-                new TranslationException("update failed"))).when(writer).update(anyMap(), anyMap(), any(WriteContext.class));
+        final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
+        final TranslationException failedOnUpdateException = new TranslationException("update failed");
+        doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException))
+                .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
 
         // Fail on revert:
-        final TranslationException failedOnRevertException = new TranslationException("update failed");
-        final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException revertFailedException =
-                new io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException(Collections.<InstanceIdentifier<?>>emptyList(),
-                        failedOnRevertException);
-        doThrow(revertFailedException).when(reverter).revert();
-
-        // Prepare modification:
-        final DataTreeCandidateNode rootNode = mockRootNode();
-        doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType();
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier();
-        final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class);
-        doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier();
-        doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes();
-        doReturn(ModificationType.WRITE).when(childNode).getModificationType();
-
-        // data before:
-        final ContainerNode nodeBefore = mockContainerNode(dataBefore);
-        when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore));
-        // data after:
-        final ContainerNode nodeAfter = mockContainerNode(dataAfter);
-        when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter));
-
-        // Run the test
+        final TranslationException failedOnRevertException = new TranslationException("revert failed");
+        doThrow(new WriterRegistry.Reverter.RevertFailedException(Collections.emptySet(), failedOnRevertException))
+                .when(reverter).revert();
+
         try {
-            doReturn(rootNode).when(prepare).getRootNode();
+            // Run the test
             final DataModification dataModification = configDataTree.newModification();
+            dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList);
+            dataModification.validate();
             dataModification.commit();
-        } catch (io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException e) {
-            verify(writer).update(anyMap(), anyMap(), any(WriteContext.class));
+            fail("WriterRegistry.Reverter.RevertFailedException was expected");
+        } catch (WriterRegistry.Reverter.RevertFailedException e) {
+            verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
             verify(reverter).revert();
             assertEquals(failedOnRevertException, e.getCause());
-            return;
         }
-
-        fail("RevertFailedException was expected");
     }
 
+    private abstract static class DataObject1 implements DataObject {}
+    private abstract static class DataObject2 implements DataObject {}
+    private abstract static class DataObject3 implements DataObject {}
+
     @Test
-    public void testChildrenFromNormalized() throws Exception {
-        final BindingNormalizedNodeSerializer serializer = mock(BindingNormalizedNodeSerializer.class);
-
-        final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> map = new HashMap<>();
-
-        // init child1 (will not be serialized)
-        final DataContainerChild child1 = mock(DataContainerChild.class);
-        when(child1.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
-        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child1))).thenReturn(null);
-        map.put(mock(YangInstanceIdentifier.class), child1);
-
-        // init child 2 (will be serialized)
-        final DataContainerChild child2 = mock(DataContainerChild.class);
-        when(child2.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class));
-
-        final Map.Entry entry = mock(Map.Entry.class);
-        final InstanceIdentifier<?> id = mock(InstanceIdentifier.class);
-        doReturn(id).when(entry).getKey();
-        final DataObject data = mock(DataObject.class);
-        doReturn(data).when(entry).getValue();
-        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child2))).thenReturn(entry);
-        map.put(mock(YangInstanceIdentifier.class), child2);
-
-        // run tested method
-        final Map<InstanceIdentifier<?>, DataObject> baMap =
-                ModifiableDataTreeDelegator.toBindingAware(map, serializer);
-        assertEquals(1, baMap.size());
-        assertEquals(data, baMap.get(id));
+    public void testToBindingAware() throws Exception {
+        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
+
+        final Map<YangInstanceIdentifier, ModificationDiff.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));
+
+        // create
+        final QName nn2 = QName.create("namespace", "nn1");
+        final YangInstanceIdentifier yid2 = mockYid(nn2);
+        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));
+
+        // update
+        final QName nn3 = QName.create("namespace", "nn1");
+        final YangInstanceIdentifier yid3 = mockYid(nn3);
+        final InstanceIdentifier iid3 = mockIid(yid3, DataObject3.class);
+        final NormalizedNode nn3B = mockNormalizedNode(nn3);
+        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));
+
+        final WriterRegistry.DataObjectUpdates dataObjectUpdates =
+                ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+
+        assertThat(dataObjectUpdates.getDeletes().size(), is(1));
+        assertThat(dataObjectUpdates.getDeletes().keySet(), hasItem(((InstanceIdentifier<?>) iid1)));
+        assertThat(dataObjectUpdates.getDeletes().values(), hasItem(
+                ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid1, do1B, null))));
+
+        assertThat(dataObjectUpdates.getUpdates().size(), is(2));
+        assertThat(dataObjectUpdates.getUpdates().keySet(), hasItems((InstanceIdentifier<?>) iid2, (InstanceIdentifier<?>) iid3));
+        assertThat(dataObjectUpdates.getUpdates().values(), hasItems(
+                DataObjectUpdate.create(iid2, null, do2A),
+                DataObjectUpdate.create(iid3, do3B, do3A)));
+
+        assertThat(dataObjectUpdates.getTypeIntersection().size(), is(3));
     }
 
-    private DataTreeCandidateNode mockRootNode() {
-        final DataTreeCandidate candidate = mock(DataTreeCandidate.class);
-        when(dataTree.prepare(modification)).thenReturn(candidate);
-
-        final DataTreeCandidateNode rootNode = mock(DataTreeCandidateNode.class);
-        when(candidate.getRootNode()).thenReturn(rootNode);
-
-        return rootNode;
+    private <D extends DataObject> D mockDataObject(final YangInstanceIdentifier yid1,
+                                       final InstanceIdentifier iid1,
+                                       final NormalizedNode nn1B,
+                                       final Class<D> type) {
+        final D do1B = mock(type);
+        when(serializer.fromNormalizedNode(yid1, nn1B)).thenReturn(new AbstractMap.SimpleEntry<>(iid1, do1B));
+        return do1B;
     }
 
-    private ContainerNode mockContainerNode(DataObject modification) {
-        final YangInstanceIdentifier.NodeIdentifier identifier =
-                YangInstanceIdentifier.NodeIdentifier.create(QName.create("/"));
-
-        final ContainerNode node = mock(ContainerNode.class);
-        when(node.getIdentifier()).thenReturn(identifier);
-
-        final Map.Entry entry = mock(Map.Entry.class);
-        final Class<? extends DataObject> implementedInterface =
-                (Class<? extends DataObject>) modification.getImplementedInterface();
-        final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
+    private NormalizedNode mockNormalizedNode(final QName nn1) {
+        final NormalizedNode nn1B = mock(NormalizedNode.class);
+        when(nn1B.getNodeType()).thenReturn(nn1);
+        return nn1B;
+    }
 
-        doReturn(id).when(entry).getKey();
-        doReturn(modification).when(entry).getValue();
-        doReturn(entry).when(serializer).fromNormalizedNode(any(YangInstanceIdentifier.class), eq(node));
+    private InstanceIdentifier mockIid(final YangInstanceIdentifier yid1,
+                                       final Class<? extends DataObject> type) {
+        final InstanceIdentifier iid1 = InstanceIdentifier.create(type);
+        when(serializer.fromYangInstanceIdentifier(yid1)).thenReturn(iid1);
+        return iid1;
+    }
 
-        return node;
+    private YangInstanceIdentifier mockYid(final QName nn1) {
+        final YangInstanceIdentifier yid1 = mock(YangInstanceIdentifier.class);
+        when(yid1.getLastPathArgument()).thenReturn(new YangInstanceIdentifier.NodeIdentifier(nn1));
+        return yid1;
     }
 }
index 3475973..bc7582e 100644 (file)
@@ -1,7 +1,8 @@
 package io.fd.honeycomb.v3po.data.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
 
 import java.util.Map;
 import org.junit.Test;
@@ -11,6 +12,8 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 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;
@@ -28,15 +31,23 @@ import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceIm
 
 public class ModificationDiffTest {
 
-    private static final QName TOP_CONTAINER_QNAME =
+    static final QName TOP_CONTAINER_QNAME =
             QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container");
-    private static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string");
-    private static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name");
-    private static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text");
-    private static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list");
-    private static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list");
+    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 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 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 YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME);
 
     @Test
     public void testInitialWrite() throws Exception {
@@ -47,10 +58,33 @@ public class ModificationDiffTest {
         dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
+        final ModificationDiff modificationDiff = getModificationDiff(prepare);
 
-        assertTrue(modificationDiff.getModificationsBefore().isEmpty());
-        assertAfter(topContainer, TOP_CONTAINER_ID, modificationDiff);
+        assertThat(modificationDiff.getUpdates().size(), is(1));
+        assertThat(modificationDiff.getUpdates().values().size(), is(1));
+        assertUpdate(modificationDiff.getUpdates().values().iterator().next(), TOP_CONTAINER_ID, null, topContainer);
+    }
+
+    @Test
+    public void testInitialWriteForContainerWithChoice() throws Exception {
+        final TipProducingDataTree dataTree = getDataTree();
+        final DataTreeModification dataTreeModification = getModification(dataTree);
+        final ContainerNode containerWithChoice = Builders.containerBuilder()
+                .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(WITH_CHOICE_CONTAINER_QNAME))
+                .withChild(Builders.choiceBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(CHOICE_QNAME))
+                        .withChild(ImmutableNodes.leafNode(IN_CASE1_LEAF_QNAME, "withinCase1"))
+                        .build())
+                .build();
+        final YangInstanceIdentifier WITH_CHOICE_CONTAINER_ID = YangInstanceIdentifier.of(WITH_CHOICE_CONTAINER_QNAME);
+        dataTreeModification.write(WITH_CHOICE_CONTAINER_ID, containerWithChoice);
+        final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
+
+        final Map<YangInstanceIdentifier, ModificationDiff.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) {
@@ -66,10 +100,9 @@ public class ModificationDiffTest {
         dataTreeModification.write(TOP_CONTAINER_ID, topContainer);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
+        final ModificationDiff modificationDiff = getModificationDiff(prepare);
 
-        assertTrue(modificationDiff.getModificationsBefore().isEmpty());
-        assertTrue(modificationDiff.getModificationsAfter().isEmpty());
+        assertThat(modificationDiff.getUpdates().size(), is(0));
     }
 
     private DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree,
@@ -83,60 +116,61 @@ public class ModificationDiffTest {
     @Test
     public void testUpdateWrite() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
-        final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+        final ContainerNode topContainer = getTopContainer("string1");
+        addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
 
         final DataTreeModification dataTreeModification = getModification(dataTree);
         final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
         dataTreeModification.write(TOP_CONTAINER_ID, topContainerAfter);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
+        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
-        assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff);
-        assertAfter(topContainerAfter, TOP_CONTAINER_ID, modificationDiff);
+        assertThat(updates.size(), is(1));
+        assertThat(updates.values().size(), is(1));
+        assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter);
     }
 
-    private ModifiableDataTreeDelegator.ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) {
-        return ModifiableDataTreeDelegator.ModificationDiff
-                .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
+    private ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) {
+        return ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode());
     }
 
     @Test
     public void testUpdateMerge() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
-        final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+        final ContainerNode topContainer = getTopContainer("string1");
+        addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
 
         final DataTreeModification dataTreeModification = getModification(dataTree);
         final NormalizedNode<?, ?> topContainerAfter = getTopContainer("string2");
         dataTreeModification.merge(TOP_CONTAINER_ID, topContainerAfter);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff =
-                getModificationDiff(prepare);
-
-        assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff);
-        assertAfter(topContainerAfter, TOP_CONTAINER_ID, modificationDiff);
+        final Map<YangInstanceIdentifier, ModificationDiff.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);
     }
 
     @Test
     public void testUpdateDelete() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
-        final NormalizedNode<?, ?> topContainerBefore = addTopContainer(dataTree);
+        final ContainerNode topContainer = getTopContainer("string1");
+        addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID);
 
         final DataTreeModification dataTreeModification = getModification(dataTree);
         dataTreeModification.delete(TOP_CONTAINER_ID);
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
-
-        assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff);
-        assertTrue(modificationDiff.getModificationsAfter().isEmpty());
+        final Map<YangInstanceIdentifier, ModificationDiff.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);
     }
 
     @Test
     public void testWriteAndUpdateInnerList() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
-        addTopContainer(dataTree);
 
         DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
         DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
@@ -146,15 +180,17 @@ public class ModificationDiffTest {
                         new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
 
         final MapNode mapNode = getNestedList("name1", "text");
+        final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier());
         dataTreeModification.write(listId, mapNode);
         dataTreeModification.ready();
         dataTree.validate(dataTreeModification);
         DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
 
-        ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
+        Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
 
-        assertTrue(modificationDiff.getModificationsBefore().isEmpty());
-        assertAfter(mapNode, listId, modificationDiff);
+        assertThat(updates.size(), is(1));
+        assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class),
+                listEntryId, null, mapNode.getValue().iterator().next());
 
         // Commit so that update can be tested next
         dataTree.commit(prepare);
@@ -171,9 +207,17 @@ public class ModificationDiffTest {
         dataTree.validate(dataTreeModification);
         prepare = dataTree.prepare(dataTreeModification);
 
-        modificationDiff = getModificationDiff(prepare);
-        assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff);
-        assertAfter(mapEntryNode, listItemId, modificationDiff);
+        updates = getModificationDiff(prepare).getUpdates();
+        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
@@ -192,25 +236,44 @@ public class ModificationDiffTest {
                         new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME));
 
         final MapNode mapNode = getNestedList("name1", "text");
+        final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier());
+
         dataTreeModification.write(listId, mapNode);
 
         final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
+        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+
+        assertThat(updates.size(), is(2));
+        assertThat(updates.values().size(), is(2));
+        assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class), TOP_CONTAINER_ID, null,
+                Builders.containerBuilder(topContainer).withChild(mapNode).build());
+        assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), listEntryId, null, mapNode.getValue().iterator().next());
+        // Assert that keys of the updates map are not wildcarded YID
+        assertThat(updates.keySet(), hasItems(
+                TOP_CONTAINER_ID,
+                listEntryId));
+    }
 
-        assertTrue(modificationDiff.getModificationsBefore().isEmpty());
+    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();
+    }
 
-        // TODO HONEYCOMB-94 2 after modifications should appear, for top-container and nested-list entry
-        assertAfter(Builders.containerBuilder(topContainer)
-                        .withChild(mapNode)
-                        .build(),
-                TOP_CONTAINER_ID, modificationDiff);
+    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 {
         final TipProducingDataTree dataTree = getDataTree();
-        addTopContainer(dataTree);
 
         DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
         DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
@@ -250,7 +313,8 @@ public class ModificationDiffTest {
                         .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, "name1")).build());
         dataTreeModification.merge(
                 deepListId,
-                Builders.mapBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
+                Builders.mapBuilder()
+                        .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME))
                         .build());
         dataTreeModification.merge(
                 deepListEntryId,
@@ -261,16 +325,14 @@ public class ModificationDiffTest {
         prepare = dataTree.prepare(dataTreeModification);
         dataTree.commit(prepare);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
-
-        assertTrue(modificationDiff.getModificationsBefore().isEmpty());
-        assertAfter(getDeepList("name1"), deepListId, modificationDiff);
+        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        assertThat(updates.size(), is(1));
+        assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), deepListEntryId, null, deepListEntry);
     }
 
     @Test
     public void testDeleteInnerListItem() throws Exception {
         final TipProducingDataTree dataTree = getDataTree();
-        addTopContainer(dataTree);
 
         DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
         DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
@@ -298,58 +360,37 @@ public class ModificationDiffTest {
         dataTree.validate(dataTreeModification);
         prepare = dataTree.prepare(dataTreeModification);
 
-        final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare);
-
-        assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff);
-        assertTrue(modificationDiff.getModificationsAfter().isEmpty());
+        final Map<YangInstanceIdentifier, ModificationDiff.NormalizedNodeUpdate> updates = getModificationDiff(prepare).getUpdates();
+        assertThat(updates.size(), is(1));
+        assertUpdate(getNormalizedNodeUpdateForBeforeType(updates, MapEntryNode.class), listItemId, mapNode.getValue().iterator().next(), null);
     }
 
-    private NormalizedNode<?, ?> addTopContainer(final TipProducingDataTree dataTree)
+    static void addNodeToTree(final DataTree dataTree, final NormalizedNode<?, ?> node,
+                                              final YangInstanceIdentifier id)
             throws DataValidationFailedException {
         DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot();
         DataTreeModification dataTreeModification = dataTreeSnapshot.newModification();
-        final NormalizedNode<?, ?> topContainerBefore = getTopContainer("string1");
-        dataTreeModification.write(TOP_CONTAINER_ID, topContainerBefore);
+        dataTreeModification.write(id, node);
         dataTreeModification.ready();
         dataTree.validate(dataTreeModification);
-        DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification);
+        DataTreeCandidate prepare = dataTree.prepare(dataTreeModification);
         dataTree.commit(prepare);
-        return topContainerBefore;
-    }
-
-    private void assertAfter(final NormalizedNode<?, ?> topContainer, final YangInstanceIdentifier TOP_CONTAINER_ID,
-                             final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) {
-        assertModification(topContainer, TOP_CONTAINER_ID, modificationDiff.getModificationsAfter());
-    }
-
-    private void assertModification(final NormalizedNode<?, ?> topContainer,
-                                    final YangInstanceIdentifier TOP_CONTAINER_ID,
-                                    final Map<YangInstanceIdentifier, NormalizedNode<?, ?>> modificationMap) {
-        assertEquals(1, modificationMap.keySet().size());
-        assertEquals(TOP_CONTAINER_ID, modificationMap.keySet().iterator().next());
-        assertEquals(topContainer, modificationMap.values().iterator().next());
-    }
-
-    private void assertBefore(final NormalizedNode<?, ?> topContainerBefore,
-                              final YangInstanceIdentifier TOP_CONTAINER_ID,
-                              final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) {
-        assertModification(topContainerBefore, TOP_CONTAINER_ID, modificationDiff.getModificationsBefore());
     }
 
-    private TipProducingDataTree getDataTree() throws ReactorException {
+    static TipProducingDataTree getDataTree() throws ReactorException {
         final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION);
         dataTree.setSchemaContext(getSchemaCtx());
         return dataTree;
     }
 
-    private ContainerNode getTopContainer(final String stringValue) {
+    static ContainerNode getTopContainer(final String stringValue) {
         return Builders.containerBuilder()
                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME))
                 .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue))
                 .build();
     }
 
-    private MapNode getNestedList(final String listItemName, final String text) {
+    static MapNode getNestedList(final String listItemName, final String text) {
         return Builders.mapBuilder()
                 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME))
                 .withChild(
@@ -378,9 +419,9 @@ public class ModificationDiffTest {
                 .build();
     }
 
-    private SchemaContext getSchemaCtx() throws ReactorException {
+    private static SchemaContext getSchemaCtx() throws ReactorException {
         final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild();
-        buildAction.addSource(new YangStatementSourceImpl(getClass().getResourceAsStream("/test-diff.yang")));
+        buildAction.addSource(new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang")));
         return buildAction.buildEffective();
     }
 }
\ No newline at end of file
index 7e8721f..5cccc87 100644 (file)
@@ -34,4 +34,21 @@ module test-diff {
         }
     }
 
+    container with-choice {
+
+        choice choice {
+            case case1 {
+                leaf in-case1 {
+                    type string;
+                }
+            }
+
+            case case2 {
+                leaf in-case2 {
+                    type string;
+                }
+            }
+        }
+    }
+
 }
index 43b5e83..2929e5d 100644 (file)
       <artifactId>translate-utils</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.jgrapht</groupId>
+      <artifactId>jgrapht-core</artifactId>
+      <version>0.9.2</version>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>vpp-translate-utils</artifactId>
index 89c6472..8419fbe 100644 (file)
@@ -38,6 +38,7 @@
     <bundle>mvn:io.fd.honeycomb.v3po/v3po-impl/${project.version}</bundle>
     <bundle>mvn:io.fd.honeycomb.v3po/translate-api/${project.version}</bundle>
     <bundle>mvn:io.fd.honeycomb.v3po/translate-spi/${project.version}</bundle>
+    <bundle>mvn:org.jgrapht/jgrapht-core/{{VERSION}}</bundle>
     <bundle>mvn:io.fd.honeycomb.v3po/translate-utils/${project.version}</bundle>
     <bundle>mvn:io.fd.honeycomb.v3po/vpp-translate-utils/${project.version}</bundle>
     <bundle>mvn:io.fd.honeycomb.v3po/data-api/${project.version}</bundle>
index 00c586d..cc6c7c6 100644 (file)
             <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</type>
             <name>runtime-mapping-singleton</name>
           </serializer>
-          <writer-registry>
-            <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry</type>
-            <name>write-registry</name>
-          </writer-registry>
+          <writer-registry-builder>
+            <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry-builder</type>
+            <name>write-registry-builder</name>
+          </writer-registry-builder>
           <context-binding-broker>
             <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-async-data-broker</type>
             <name>honeycomb-context-binding-data-broker</name>
             <provider>/modules/module[type='delegating-writer-registry'][name='write-registry']</provider>
           </instance>
         </service>
+        <service>
+          <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry-builder</type>
+          <instance>
+            <name>write-registry-builder</name>
+            <provider>/modules/module[type='delegating-writer-registry'][name='write-registry']</provider>
+          </instance>
+        </service>
 
         <service>
           <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:data:api">prefix:data-tree</type>
index cb98a2a..dd2ff2b 100644 (file)
@@ -34,8 +34,8 @@
         <!-- Config initialization -->
         <!-- Empty registry which does not pass data to VPP  -->
         <module>
-          <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:utils">prefix:noop-writer-registry</type>
-          <name>noop-writer-registry</name>
+          <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:utils">prefix:noop-writer-registry-builder</type>
+          <name>noop-writer-registry-builder</name>
         </module>
         <!-- Config data tree which does not pass data to translation layer (uses noop-write-registry)  -->
         <module>
             <type xmlns:prefix="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding:impl">prefix:binding-dom-mapping-service</type>
             <name>runtime-mapping-singleton</name>
           </serializer>
-          <writer-registry>
-            <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry</type>
-            <name>noop-writer-registry</name>
-          </writer-registry>
+          <writer-registry-builder>
+            <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry-builder</type>
+            <name>noop-writer-registry-builder</name>
+          </writer-registry-builder>
           <context-binding-broker>
             <type xmlns:binding="urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding-async-data-broker</type>
             <name>honeycomb-context-binding-data-broker</name>
       <services xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
         <!-- Config initialization -->
         <service>
-          <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry</type>
+          <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-registry-builder</type>
           <instance>
-            <name>noop-writer-registry</name>
-            <provider>/modules/module[type='noop-writer-registry'][name='noop-writer-registry']</provider>
+            <name>noop-writer-registry-builder</name>
+            <provider>/modules/module[type='noop-writer-registry-builder'][name='noop-writer-registry-builder']</provider>
           </instance>
         </service>
         <service>
index 8e244fa..1b08e85 100644 (file)
@@ -19,7 +19,7 @@
         <groupId>io.fd.honeycomb.common</groupId>
         <artifactId>impl-parent</artifactId>
         <version>1.0.0-SNAPSHOT</version>
-        <relativePath>../../common/api-parent</relativePath>
+        <relativePath>../../common/impl-parent</relativePath>
     </parent>
 
     <modelVersion>4.0.0</modelVersion>
diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java
deleted file mode 100644 (file)
index b38f269..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.write;
-
-import com.google.common.annotations.Beta;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * Child writer allowing its parent to pass the builder object
- *
- * @param <D> Specific DataObject derived type, that is handled by this writer
- */
-@Beta
-public interface ChildWriter<D extends DataObject> extends Writer<D> {
-
-    /**
-     * Extract data object managed by this writer from parent data and perform write.
-     *
-     * @param parentId Id of parent node
-     * @param parentDataAfter Parent data from modification to extract data object from
-     * @param ctx Write context for current modification
-     */
-    void writeChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                    @Nonnull final DataObject parentDataAfter,
-                    @Nonnull final WriteContext ctx) throws WriteFailedException;
-
-    /**
-     * Extract data object managed by this writer(if necessary) from parent data and perform delete.
-     *
-     * @param parentId Id of parent node
-     * @param parentDataBefore Parent data before modification to extract data object from
-     * @param ctx Write context for current modification
-     */
-    void deleteChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                     @Nonnull final DataObject parentDataBefore,
-                     @Nonnull final WriteContext ctx) throws WriteFailedException;
-
-    /**
-     * Extract data object managed by this writer(if necessary) from parent data and perform delete.
-     *
-     * @param parentId Id of parent node
-     * @param parentDataBefore Parent data before modification to extract data object from
-     * @param parentDataAfter Parent data from modification to extract data object from
-     * @param ctx Write context for current modification
-     */
-    void updateChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                     @Nonnull final DataObject parentDataBefore,
-                     @Nonnull final DataObject parentDataAfter,
-                     @Nonnull final WriteContext ctx) throws WriteFailedException;
-}
diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java
new file mode 100644 (file)
index 0000000..0d891ec
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.write;
+
+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.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Simple wrapper for BA id + data before and after state. Does not allow both before and after to be null.
+ */
+public class DataObjectUpdate {
+
+    @Nonnull
+    private final InstanceIdentifier<?> id;
+    @Nullable
+    private final DataObject dataBefore;
+    @Nullable
+    private final DataObject dataAfter;
+
+    private DataObjectUpdate(@Nonnull final InstanceIdentifier<?> id,
+                             @Nullable final DataObject dataBefore,
+                             @Nullable final DataObject dataAfter) {
+        this.id = checkNotNull(id);
+        this.dataAfter = dataAfter;
+        this.dataBefore = dataBefore;
+    }
+
+    public DataObject getDataBefore() {
+        return dataBefore;
+    }
+
+    public DataObject getDataAfter() {
+        return dataAfter;
+    }
+
+    public InstanceIdentifier<?> getId() {
+        return id;
+    }
+
+    public static DataObjectUpdate create(@Nonnull final InstanceIdentifier<?> id,
+                                    @Nullable final DataObject dataBefore,
+                                    @Nullable final DataObject dataAfter) {
+        checkArgument(!(dataBefore == null && dataAfter == null), "Both before and after data are null");
+        if (dataBefore != null) {
+            checkArgument(id.getTargetType().isAssignableFrom(dataBefore.getClass()));
+        }
+        if (dataAfter != null) {
+            checkArgument(id.getTargetType().isAssignableFrom(dataAfter.getClass()));
+        }
+
+        return dataAfter == null
+                ? new DataObjectDelete(id, dataBefore)
+                : new DataObjectUpdate(id, dataBefore, dataAfter);
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        final DataObjectUpdate that = (DataObjectUpdate) o;
+
+        return id.equals(that.id);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "DataObjectUpdate{" + "id=" + id
+                + ", dataBefore=" + dataBefore
+                + ", dataAfter=" + dataAfter
+                + '}';
+    }
+
+    public DataObjectUpdate reverse() {
+        return DataObjectUpdate.create(id, dataAfter, dataBefore);
+    }
+
+    public static class DataObjectDelete extends DataObjectUpdate {
+
+        private DataObjectDelete(@Nonnull final InstanceIdentifier<?> id,
+                                 @Nullable final DataObject dataBefore) {
+            super(id, dataBefore, null);
+        }
+    }
+}
diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java
new file mode 100644 (file)
index 0000000..71ecbb8
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.write;
+
+import com.google.common.annotations.Beta;
+import java.util.Collection;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Mutable registry that allows adding new writers.
+ */
+@Beta
+public interface ModifiableWriterRegistry {
+
+    /**
+     * Add a writer responsible for writing only a single complex node.
+     */
+    ModifiableWriterRegistry addWriter(@Nonnull Writer<? extends DataObject> writer);
+
+    /**
+     * Add a writer responsible for writing multiple complex nodes within a subtree its responsible for.
+     * Identifiers for subtree nodes handled by a single writer have to be relative from {@link DataObject} that
+     * represents subtree root.
+     */
+    ModifiableWriterRegistry addSubtreeWriter(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
+                                              @Nonnull Writer<? extends DataObject> writer);
+
+    /**
+     * Add a writer and make sure it will be executed before writer identifier by relatedType is executed.
+     */
+    ModifiableWriterRegistry addWriterBefore(@Nonnull Writer<? extends DataObject> writer,
+                                             @Nonnull InstanceIdentifier<?> relatedType);
+
+    ModifiableWriterRegistry addSubtreeWriterBefore(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
+                                                    @Nonnull Writer<? extends DataObject> writer,
+                                                    @Nonnull InstanceIdentifier<?> relatedType);
+
+    ModifiableWriterRegistry addWriterBefore(@Nonnull Writer<? extends DataObject> writer,
+                                             @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+
+    ModifiableWriterRegistry addSubtreeWriterBefore(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
+                                                    @Nonnull Writer<? extends DataObject> writer,
+                                                    @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+
+    /**
+     * Add a writer and make sure it will be executed after writer identifier by relatedType is executed.
+     */
+    ModifiableWriterRegistry addWriterAfter(@Nonnull Writer<? extends DataObject> writer,
+                                            @Nonnull InstanceIdentifier<?> relatedType);
+
+    ModifiableWriterRegistry addSubtreeWriterAfter(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
+                                                   @Nonnull Writer<? extends DataObject> writer,
+                                                   @Nonnull InstanceIdentifier<?> relatedType);
+
+    ModifiableWriterRegistry addWriterAfter(@Nonnull Writer<? extends DataObject> writer,
+                                            @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+
+    ModifiableWriterRegistry addSubtreeWriterAfter(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
+                                                   @Nonnull Writer<? extends DataObject> writer,
+                                                   @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+}
diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java
new file mode 100644 (file)
index 0000000..4287964
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.write;
+
+import com.google.common.annotations.Beta;
+
+@Beta
+public interface WriterFactory {
+
+    /**
+     * Initialize 1 or more writers and add them to provided registry.
+     */
+    void init(ModifiableWriterRegistry registry);
+}
index d30f06d..6473501 100644 (file)
@@ -19,47 +19,131 @@ package io.fd.honeycomb.v3po.translate.write;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.annotations.Beta;
-import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
 import io.fd.honeycomb.v3po.translate.TranslationException;
-import java.util.List;
-import java.util.Map;
+import java.util.Set;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
 /**
- * Special {@link Writer} capable of performing bulk updates
+ * Special {@link Writer} capable of performing bulk updates.
  */
 @Beta
 public interface WriterRegistry extends Writer<DataObject> {
 
     /**
-     * Performs bulk update
+     * Performs bulk update.
      *
      * @throws BulkUpdateException in case bulk update fails
-     * @throws TranslationException        in case some other error occurs while processing update request
+     * @throws TranslationException in case some other error occurs while processing update request
      */
-    void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> dataBefore,
-                @Nonnull final Map<InstanceIdentifier<?>, DataObject> dataAfter,
-                @Nonnull final WriteContext ctx) throws TranslationException;
+    void update(@Nonnull DataObjectUpdates updates,
+                @Nonnull WriteContext ctx) throws TranslationException;
+
+    /**
+     * Simple DTO containing updates for {@link WriterRegistry}. Currently only deletes and updates (create + update)
+     * are distinguished.
+     */
+    @Beta
+    final class DataObjectUpdates {
+
+        private final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates;
+        private final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes;
+
+        /**
+         * Create new instance.
+         *
+         * @param updates All updates indexed by their unkeyed {@link InstanceIdentifier}
+         * @param deletes All deletes indexed by their unkeyed {@link InstanceIdentifier}
+         */
+        public DataObjectUpdates(@Nonnull final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
+                                 @Nonnull final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes) {
+            this.deletes = deletes;
+            this.updates = updates;
+        }
+
+        public Multimap<InstanceIdentifier<?>, DataObjectUpdate> getUpdates() {
+            return updates;
+        }
+
+        public Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> getDeletes() {
+            return deletes;
+        }
+
+        public boolean isEmpty() {
+            return updates.isEmpty() && deletes.isEmpty();
+        }
+
+        @Override
+        public String toString() {
+            return "DataObjectUpdates{" + "updates=" + updates + ", deletes=" + deletes + '}';
+        }
+
+        /**
+         * Get a {@link Set} containing all update types from both updates as well as deletes.
+         */
+        public Set<InstanceIdentifier<?>> getTypeIntersection() {
+            return Sets.union(deletes.keySet(), updates.keySet());
+        }
+
+        /**
+         * Check whether there is only a single type of data object to be updated.
+         *
+         * @return true if there is only a single type of updates (update + delete)
+         */
+        public boolean containsOnlySingleType() {
+            return getTypeIntersection().size() == 1;
+        }
+
+        @Override
+        public boolean equals(final Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (other == null || getClass() != other.getClass()) {
+                return false;
+            }
+
+            final DataObjectUpdates that = (DataObjectUpdates) other;
+
+            if (!updates.equals(that.updates)) {
+                return false;
+            }
+            return deletes.equals(that.deletes);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result = updates.hashCode();
+            result = 31 * result + deletes.hashCode();
+            return result;
+        }
+
+    }
 
     /**
      * Thrown when bulk update failed.
      */
     @Beta
-    class BulkUpdateException extends WriteFailedException {
+    class BulkUpdateException extends TranslationException {
 
         private final Reverter reverter;
+        private final Set<InstanceIdentifier<?>> failedIds;
 
         /**
          * Constructs an BulkUpdateException.
-         *
-         * @param failedId instance identifier of the data object that caused bulk update to fail.
-         * @param cause    the cause of bulk update failure
+         * @param failedIds instance identifiers of the data objects that were not processed during bulk update.
+         * @param cause the cause of bulk update failure
          */
-        public BulkUpdateException(@Nonnull final InstanceIdentifier<?> failedId, @Nonnull final Reverter reverter,
-                                   final Throwable cause) {
-            super(failedId, "Bulk update failed at " + failedId, cause);
+        public BulkUpdateException(@Nonnull final Set<InstanceIdentifier<?>> failedIds,
+                                   @Nonnull final Reverter reverter,
+                                   @Nonnull final Throwable cause) {
+            super("Bulk update failed at: " + failedIds, cause);
+            this.failedIds = failedIds;
             this.reverter = checkNotNull(reverter, "reverter should not be null");
         }
 
@@ -72,10 +156,13 @@ public interface WriterRegistry extends Writer<DataObject> {
             reverter.revert();
         }
 
+        public Set<InstanceIdentifier<?>> getFailedIds() {
+            return failedIds;
+        }
     }
 
     /**
-     * Abstraction over revert mechanism in case of a bulk update failure
+     * Abstraction over revert mechanism in case of a bulk update failure.
      */
     @Beta
     interface Reverter {
@@ -95,19 +182,19 @@ public interface WriterRegistry extends Writer<DataObject> {
         class RevertFailedException extends TranslationException {
 
             // TODO change to list of VppDataModifications to make debugging easier
-            private final List<InstanceIdentifier<?>> notRevertedChanges;
+            private final Set<InstanceIdentifier<?>> notRevertedChanges;
 
             /**
-             * Constructs an RevertFailedException with the list of changes that were not reverted.
+             * Constructs a RevertFailedException with the list of changes that were not reverted.
              *
              * @param notRevertedChanges list of changes that were not reverted
              * @param cause              the cause of revert failure
              */
-            public RevertFailedException(@Nonnull final List<InstanceIdentifier<?>> notRevertedChanges,
+            public RevertFailedException(@Nonnull final Set<InstanceIdentifier<?>> notRevertedChanges,
                                          final Throwable cause) {
                 super(cause);
                 checkNotNull(notRevertedChanges, "notRevertedChanges should not be null");
-                this.notRevertedChanges = ImmutableList.copyOf(notRevertedChanges);
+                this.notRevertedChanges = ImmutableSet.copyOf(notRevertedChanges);
             }
 
             /**
@@ -116,7 +203,7 @@ public interface WriterRegistry extends Writer<DataObject> {
              * @return list of changes that were not reverted
              */
             @Nonnull
-            public List<InstanceIdentifier<?>> getNotRevertedChanges() {
+            public Set<InstanceIdentifier<?>> getNotRevertedChanges() {
                 return notRevertedChanges;
             }
         }
diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java
new file mode 100644 (file)
index 0000000..55ef66e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.write;
+
+/**
+ * Builder for writer registries.
+ */
+public interface WriterRegistryBuilder {
+
+    WriterRegistry build();
+}
index 4a8093a..414ee20 100644 (file)
@@ -24,14 +24,19 @@ module translate-api {
         config:java-class io.fd.honeycomb.v3po.translate.read.ReaderRegistry;
     }
 
-    identity honeycomb-writer {
+    identity honeycomb-writer-factory {
         base "config:service-type";
-        config:java-class io.fd.honeycomb.v3po.translate.write.Writer;
+        config:java-class io.fd.honeycomb.v3po.translate.write.WriterFactory;
     }
 
     identity honeycomb-writer-registry {
         base "config:service-type";
-        config:java-class io.fd.honeycomb.v3po.translate.write.WriterRegistry;
+        config:java-class io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry;
+    }
+
+    identity honeycomb-writer-registry-builder {
+        base "config:service-type";
+        config:java-class io.fd.honeycomb.v3po.translate.write.WriterRegistryBuilder;
     }
 
     identity honeycomb-mapping-context {
index 0212c08..5f1391c 100644 (file)
@@ -18,82 +18,33 @@ package io.fd.honeycomb.v3po.translate.impl.write;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import com.google.common.base.Optional;
-import com.google.common.collect.Lists;
-import io.fd.honeycomb.v3po.translate.impl.TraversalType;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
 import io.fd.honeycomb.v3po.translate.write.WriteContext;
 import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
 import io.fd.honeycomb.v3po.translate.write.Writer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AbstractCompositeWriter<D extends DataObject> implements Writer<D> {
+abstract class AbstractCompositeWriter<D extends DataObject> implements Writer<D> {
 
     private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeWriter.class);
 
-    private final Map<Class<? extends DataObject>, ChildWriter<? extends ChildOf<D>>> childWriters;
-    private final Map<Class<? extends DataObject>, ChildWriter<? extends Augmentation<D>>> augWriters;
     private final InstanceIdentifier<D> instanceIdentifier;
-    private TraversalType traversalType;
 
-    public AbstractCompositeWriter(final Class<D> type,
-                                   final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                                   final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                                   final TraversalType traversalType) {
-        this.traversalType = traversalType;
-        this.instanceIdentifier = InstanceIdentifier.create(type);
-        this.childWriters = RWUtils.uniqueLinkedIndex(childWriters, RWUtils.MANAGER_CLASS_FUNCTION);
-        this.augWriters = RWUtils.uniqueLinkedIndex(augWriters, RWUtils.MANAGER_CLASS_AUG_FUNCTION);
+    AbstractCompositeWriter(final InstanceIdentifier<D> type) {
+        this.instanceIdentifier = type;
     }
 
     protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
         throws WriteFailedException {
         LOG.debug("{}: Writing current: {} data: {}", this, id, data);
-
-        switch (traversalType) {
-            case PREORDER: {
-                LOG.trace("{}: Writing current attributes", this);
-                writeCurrentAttributes(id, data, ctx);
-                writeChildren(id, data, ctx);
-                break;
-            }
-            case POSTORDER: {
-                writeChildren(id, data, ctx);
-                LOG.trace("{}: Writing current attributes", this);
-                writeCurrentAttributes(id, data, ctx);
-                break;
-            }
-        }
-
+        writeCurrentAttributes(id, data, ctx);
         LOG.debug("{}: Current node written successfully", this);
     }
 
-    private void writeChildren(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
-        throws WriteFailedException {
-        for (ChildWriter<? extends ChildOf<D>> child : childWriters.values()) {
-            LOG.debug("{}: Writing child in: {}", this, child);
-            child.writeChild(id, data, ctx);
-        }
-
-        for (ChildWriter<? extends Augmentation<D>> child : augWriters.values()) {
-            LOG.debug("{}: Writing augment in: {}", this, child);
-            child.writeChild(id, data, ctx);
-        }
-    }
-
     protected void updateCurrent(final InstanceIdentifier<D> id, final D dataBefore, final D dataAfter,
                                  final WriteContext ctx) throws WriteFailedException {
         LOG.debug("{}: Updating current: {} dataBefore: {}, datAfter: {}", this, id, dataBefore, dataAfter);
@@ -103,69 +54,14 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
             // No change, ignore
             return;
         }
-
-        switch (traversalType) {
-            case PREORDER: {
-                LOG.trace("{}: Updating current attributes", this);
-                updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
-                updateChildren(id, dataBefore, dataAfter, ctx);
-                break;
-            }
-            case POSTORDER: {
-                updateChildren(id, dataBefore, dataAfter, ctx);
-                LOG.trace("{}: Updating current attributes", this);
-                updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
-                break;
-            }
-        }
-
+        updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
         LOG.debug("{}: Current node updated successfully", this);
     }
 
-    private void updateChildren(final InstanceIdentifier<D> id, final D dataBefore, final D dataAfter,
-                                final WriteContext ctx) throws WriteFailedException {
-        for (ChildWriter<? extends ChildOf<D>> child : childWriters.values()) {
-            LOG.debug("{}: Updating child in: {}", this, child);
-            child.updateChild(id, dataBefore, dataAfter, ctx);
-        }
-
-        for (ChildWriter<? extends Augmentation<D>> child : augWriters.values()) {
-            LOG.debug("{}: Updating augment in: {}", this, child);
-            child.updateChild(id, dataBefore, dataAfter, ctx);
-        }
-    }
-
     protected void deleteCurrent(final InstanceIdentifier<D> id, final D dataBefore, final WriteContext ctx)
         throws WriteFailedException {
         LOG.debug("{}: Deleting current: {} dataBefore: {}", this, id, dataBefore);
-
-        switch (traversalType) {
-            case PREORDER: {
-                deleteChildren(id, dataBefore, ctx);
-                LOG.trace("{}: Deleting current attributes", this);
-                deleteCurrentAttributes(id, dataBefore, ctx);
-                break;
-            }
-            case POSTORDER: {
-                LOG.trace("{}: Deleting current attributes", this);
-                deleteCurrentAttributes(id, dataBefore, ctx);
-                deleteChildren(id, dataBefore, ctx);
-                break;
-            }
-        }
-    }
-
-    private void deleteChildren(final InstanceIdentifier<D> id, final D dataBefore, final WriteContext ctx)
-        throws WriteFailedException {
-        for (ChildWriter<? extends Augmentation<D>> child : reverseCollection(augWriters.values())) {
-            LOG.debug("{}: Deleting augment in: {}", this, child);
-            child.deleteChild(id, dataBefore, ctx);
-        }
-
-        for (ChildWriter<? extends ChildOf<D>> child : reverseCollection(childWriters.values())) {
-            LOG.debug("{}: Deleting child in: {}", this, child);
-            child.deleteChild(id, dataBefore, ctx);
-        }
+        deleteCurrentAttributes(id, dataBefore, ctx);
     }
 
     @SuppressWarnings("unchecked")
@@ -177,28 +73,20 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
         LOG.debug("{}: Updating : {}", this, id);
         LOG.trace("{}: Updating : {}, from: {} to: {}", this, id, dataBefore, dataAfter);
 
-        if (idPointsToCurrent(id)) {
-            if(isWrite(dataBefore, dataAfter)) {
-                writeCurrent((InstanceIdentifier<D>) id, castToManaged(dataAfter), ctx);
-            } else if(isDelete(dataBefore, dataAfter)) {
-                deleteCurrent((InstanceIdentifier<D>) id, castToManaged(dataBefore), ctx);
-            } else {
-                checkArgument(dataBefore != null && dataAfter != null, "No data to process");
-                updateCurrent((InstanceIdentifier<D>) id, castToManaged(dataBefore), castToManaged(dataAfter), ctx);
-            }
+        checkArgument(idPointsToCurrent(id), "Cannot handle data: %s. Only: %s can be handled by writer: %s",
+                id, getManagedDataObjectType(), this);
+
+        if (isWrite(dataBefore, dataAfter)) {
+            writeCurrent((InstanceIdentifier<D>) id, castToManaged(dataAfter), ctx);
+        } else if (isDelete(dataBefore, dataAfter)) {
+            deleteCurrent((InstanceIdentifier<D>) id, castToManaged(dataBefore), ctx);
         } else {
-            if (isWrite(dataBefore, dataAfter)) {
-                writeSubtree(id, dataAfter, ctx);
-            } else if (isDelete(dataBefore, dataAfter)) {
-                deleteSubtree(id, dataBefore, ctx);
-            } else {
-                checkArgument(dataBefore != null && dataAfter != null, "No data to process");
-                updateSubtree(id, dataBefore, dataAfter, ctx);
-            }
+            checkArgument(dataBefore != null && dataAfter != null, "No data to process");
+            updateCurrent((InstanceIdentifier<D>) id, castToManaged(dataBefore), castToManaged(dataAfter), ctx);
         }
     }
 
-    private void checkDataType(final @Nullable DataObject dataAfter) {
+    private void checkDataType(@Nonnull final DataObject dataAfter) {
         checkArgument(getManagedDataObjectType().getTargetType().isAssignableFrom(dataAfter.getClass()));
     }
 
@@ -217,100 +105,10 @@ public abstract class AbstractCompositeWriter<D extends DataObject> implements W
         return dataAfter == null && dataBefore != null;
     }
 
-    private void writeSubtree(final InstanceIdentifier<? extends DataObject> id,
-                              final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException {
-        LOG.debug("{}: Writing subtree: {}", this, id);
-
-        final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
-        final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
-
-        if (writer != null) {
-            LOG.debug("{}: Writing subtree: {} in: {}", this, id, writer);
-            writer.update(id, null, dataAfter, ctx);
-        } else if (augWriter != null) {
-            LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
-            augWriter.update(id, null, dataAfter, ctx);
-        } else {
-            // If there's no dedicated writer, use write current
-            // But we need current data after to do so
-            final InstanceIdentifier<D> currentId = RWUtils.cutId(id, getManagedDataObjectType());
-            Optional<D> currentDataAfter = ctx.readAfter(currentId);
-            LOG.debug("{}: Dedicated subtree writer missing for: {}. Writing current.", this,
-                RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter);
-            writeCurrent(currentId, castToManaged(currentDataAfter.get()), ctx);
-        }
-    }
-
     private boolean idPointsToCurrent(final @Nonnull InstanceIdentifier<? extends DataObject> id) {
         return id.getTargetType().equals(getManagedDataObjectType().getTargetType());
     }
 
-    private void deleteSubtree(final InstanceIdentifier<? extends DataObject> id,
-                               final DataObject dataBefore, final WriteContext ctx) throws WriteFailedException {
-        LOG.debug("{}: Deleting subtree: {}", this, id);
-
-        final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
-        final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
-
-        if (writer != null) {
-            LOG.debug("{}: Deleting subtree: {} in: {}", this, id, writer);
-            writer.update(id, dataBefore, null, ctx);
-        } else if (augWriter != null) {
-            LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
-            augWriter.update(id, dataBefore, null, ctx);
-        }  else {
-            updateSubtreeFromCurrent(id, ctx);
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private void updateSubtreeFromCurrent(final InstanceIdentifier<? extends DataObject> id, final WriteContext ctx)
-        throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.cutId(id, getManagedDataObjectType());
-        Optional<D> currentDataBefore = ctx.readBefore(currentId);
-        Optional<D> currentDataAfter = ctx.readAfter(currentId);
-        LOG.debug("{}: Dedicated subtree writer missing for: {}. Updating current without subtree", this,
-            RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter);
-        updateCurrent((InstanceIdentifier<D>) id, castToManaged(currentDataBefore.orNull()),
-            castToManaged(currentDataAfter.orNull()), ctx);
-    }
-
-    private void updateSubtree(final InstanceIdentifier<? extends DataObject> id,
-                               final DataObject dataBefore,
-                               final DataObject dataAfter,
-                               final WriteContext ctx) throws WriteFailedException {
-        LOG.debug("{}: Updating subtree: {}", this, id);
-        final Writer<? extends ChildOf<D>> writer = getNextChildWriter(id);
-        final Writer<? extends Augmentation<D>> augWriter = getNextAgumentationWriter(id);
-
-        if (writer != null) {
-            LOG.debug("{}: Updating subtree: {} in: {}", this, id, writer);
-            writer.update(id, dataBefore, dataAfter, ctx);
-        } else if (augWriter != null) {
-            LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter);
-            augWriter.update(id, dataBefore, dataAfter, ctx);
-        } else {
-            updateSubtreeFromCurrent(id, ctx);
-        }
-    }
-
-    private Writer<? extends ChildOf<D>> getNextChildWriter(final InstanceIdentifier<? extends DataObject> id) {
-        final Class<? extends DataObject> next = RWUtils.getNextId(id, getManagedDataObjectType()).getType();
-        return childWriters.get(next);
-    }
-
-    private Writer<? extends Augmentation<D>> getNextAgumentationWriter(final InstanceIdentifier<? extends DataObject> id) {
-        final Class<? extends DataObject> next = RWUtils.getNextId(id, getManagedDataObjectType()).getType();
-        return augWriters.get(next);
-    }
-
-    private static <T> List<T> reverseCollection(final Collection<T> original) {
-        // TODO find a better reverse mechanism (probably a different collection for child writers is necessary)
-        final ArrayList<T> list = Lists.newArrayList(original);
-        Collections.reverse(list);
-        return list;
-    }
-
     protected abstract void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id,
                                                    @Nonnull final D data,
                                                    @Nonnull final WriteContext ctx) throws WriteFailedException;
diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java
deleted file mode 100644 (file)
index 6e0841d..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.impl.write;
-
-import com.google.common.base.Optional;
-import io.fd.honeycomb.v3po.translate.impl.TraversalType;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import java.util.List;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-public class CompositeChildWriter<D extends DataObject> extends AbstractCompositeWriter<D>
-    implements ChildWriter<D> {
-
-    private final ChildWriterCustomizer<D> customizer;
-
-    public CompositeChildWriter(@Nonnull final Class<D> type,
-                                @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                                @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                                @Nonnull final ChildWriterCustomizer<D> customizer) {
-        this(type, childWriters, augWriters, customizer, TraversalType.PREORDER);
-    }
-
-
-    public CompositeChildWriter(@Nonnull final Class<D> type,
-                                @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                                @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                                @Nonnull final ChildWriterCustomizer<D> customizer,
-                                @Nonnull final TraversalType traversalType) {
-        super(type, childWriters, augWriters, traversalType);
-        this.customizer = customizer;
-    }
-
-    public CompositeChildWriter(@Nonnull final Class<D> type,
-                                @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                                @Nonnull final ChildWriterCustomizer<D> customizer) {
-        this(type, childWriters, RWUtils.<D>emptyAugWriterList(), customizer);
-    }
-
-    public CompositeChildWriter(@Nonnull final Class<D> type,
-                                @Nonnull final ChildWriterCustomizer<D> customizer) {
-        this(type, RWUtils.<D>emptyChildWriterList(), RWUtils.<D>emptyAugWriterList(), customizer);
-    }
-
-    @Override
-    protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D data,
-                                          @Nonnull final WriteContext ctx) throws WriteFailedException {
-        customizer.writeCurrentAttributes(id, data, ctx);
-    }
-
-    @Override
-    protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                           @Nonnull WriteContext ctx) throws WriteFailedException {
-        customizer.deleteCurrentAttributes(id, dataBefore, ctx);
-    }
-
-    @Override
-    protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                           @Nonnull final D dataAfter, @Nonnull WriteContext ctx)
-        throws WriteFailedException {
-        customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
-    }
-
-    @Override
-    public void writeChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                           @Nonnull final DataObject parentData, @Nonnull WriteContext ctx)
-        throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Optional<D> currentData = customizer.extract(currentId, parentData);
-        if(currentData.isPresent()) {
-            writeCurrent(currentId, currentData.get(), ctx);
-        }
-    }
-
-    @Override
-    public void deleteChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                            @Nonnull final DataObject parentDataBefore,
-                            @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Optional<D> dataBefore = customizer.extract(currentId, parentDataBefore);
-        if(dataBefore.isPresent()) {
-            deleteCurrent(currentId, dataBefore.get(), ctx);
-        }
-    }
-
-    @Override
-    public void updateChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                            @Nonnull final DataObject parentDataBefore, @Nonnull final DataObject parentDataAfter,
-                            @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Optional<D> before = customizer.extract(currentId, parentDataBefore);
-        final Optional<D> after = customizer.extract(currentId, parentDataAfter);
-
-        if(before.isPresent()) {
-            if(after.isPresent()) {
-                updateCurrent(currentId, before.get(), after.get(), ctx);
-            } else {
-                deleteCurrent(currentId, before.get(), ctx);
-            }
-        } else if (after.isPresent()){
-            writeCurrent(currentId, after.get(), ctx);
-        }
-    }
-}
diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java
deleted file mode 100644 (file)
index fe9e8d5..0000000
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.impl.write;
-
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import io.fd.honeycomb.v3po.translate.impl.TraversalType;
-import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.Identifiable;
-import org.opendaylight.yangtools.yang.binding.Identifier;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-public class CompositeListWriter<D extends DataObject & Identifiable<K>, K extends Identifier<D>> extends
-    AbstractCompositeWriter<D>
-    implements ChildWriter<D> {
-
-    private static final Function<DataObject, Object> INDEX_FUNCTION = input -> input instanceof Identifiable<?>
-        ? ((Identifiable<?>) input).getKey()
-        : input;
-
-
-    private final ListWriterCustomizer<D, K> customizer;
-
-    public CompositeListWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                               @Nonnull final ListWriterCustomizer<D, K> customizer) {
-        this(type, childWriters, augWriters, customizer, TraversalType.PREORDER);
-    }
-
-    public CompositeListWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                               @Nonnull final ListWriterCustomizer<D, K> customizer,
-                               @Nonnull final TraversalType traversalType) {
-        super(type, childWriters, augWriters, traversalType);
-        this.customizer = customizer;
-    }
-
-    public CompositeListWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final ListWriterCustomizer<D, K> customizer) {
-        this(type, childWriters, RWUtils.<D>emptyAugWriterList(), customizer);
-    }
-
-    public CompositeListWriter(@Nonnull final Class<D> type,
-                               @Nonnull final ListWriterCustomizer<D, K> customizer) {
-        this(type, RWUtils.<D>emptyChildWriterList(), RWUtils.<D>emptyAugWriterList(), customizer);
-
-    }
-
-    @Override
-    protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D data,
-                                          @Nonnull final WriteContext ctx) throws WriteFailedException {
-        customizer.writeCurrentAttributes(id, data, ctx);
-    }
-
-    @Override
-    protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                           @Nonnull final WriteContext ctx) throws WriteFailedException {
-        customizer.deleteCurrentAttributes(id, dataBefore, ctx);
-    }
-
-    @Override
-    protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                           @Nonnull final D dataAfter, @Nonnull final WriteContext ctx)
-        throws WriteFailedException {
-        customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
-    }
-
-    @Override
-    public void writeChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                           @Nonnull final DataObject parentData,
-                           @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Optional<List<D>> currentData = customizer.extract(currentId, parentData);
-        if (currentData.isPresent()) {
-            for (D entry : currentData.get()) {
-                writeCurrent(currentId, entry, ctx);
-            }
-        }
-    }
-
-    @Override
-    public void deleteChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                            @Nonnull final DataObject parentDataBefore,
-                            @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Optional<List<D>> dataBefore = customizer.extract(currentId, parentDataBefore);
-        if (dataBefore.isPresent()) {
-            for (D entry : dataBefore.get()) {
-                deleteCurrent(currentId, entry, ctx);
-            }
-        }
-    }
-
-    private Map<Object, D> listOfIdentifiableToMap(Optional<List<D>> list) {
-        if (list.isPresent()) {
-            return Maps.uniqueIndex(list.get(), INDEX_FUNCTION);
-        } else {
-            return Collections.emptyMap();
-        }
-
-    }
-
-    @Override
-    public void updateChild(@Nonnull final InstanceIdentifier<? extends DataObject> parentId,
-                            @Nonnull final DataObject parentDataBefore, @Nonnull final DataObject parentDataAfter,
-                            @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier<D> currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType());
-        final Map<Object, D> dataBefore = listOfIdentifiableToMap(customizer.extract(currentId, parentDataBefore));
-        final Map<Object, D> dataAfter = listOfIdentifiableToMap(customizer.extract(currentId, parentDataAfter));
-
-        // The order of delete/write/update operations can have side-effects for devices like VPP
-        // TODO make it configurable
-
-        // First perform delete:
-        for (Object deletedNodeKey : Sets.difference(dataBefore.keySet(), dataAfter.keySet())) {
-            final D deleted = dataBefore.get(deletedNodeKey);
-            deleteCurrent(currentId, deleted, ctx);
-        }
-
-        // Then write/update:
-        for (Map.Entry<Object, D> after : dataAfter.entrySet()) {
-            final D before = dataBefore.get(after.getKey());
-            if(before == null) {
-                writeCurrent(currentId, after.getValue(), ctx);
-            } else {
-                updateCurrent(currentId, before, after.getValue(), ctx);
-            }
-        }
-
-    }
-
-    @Override
-    protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
-        throws WriteFailedException {
-        // Make sure the key is present
-        if(isWildcarded(id)) {
-            super.writeCurrent(getSpecificId(id, data), data, ctx);
-        } else {
-            super.writeCurrent(id, data, ctx);
-        }
-    }
-
-    @Override
-    protected void updateCurrent(final InstanceIdentifier<D> id, final D dataBefore, final D dataAfter,
-                                 final WriteContext ctx) throws WriteFailedException {
-        // Make sure the key is present
-        if(isWildcarded(id)) {
-            super.updateCurrent(getSpecificId(id, dataBefore), dataBefore, dataAfter, ctx);
-        } else {
-            super.updateCurrent(id, dataBefore, dataAfter, ctx);
-        }
-    }
-
-    @Override
-    protected void deleteCurrent(final InstanceIdentifier<D> id, final D dataBefore, final WriteContext ctx)
-        throws WriteFailedException {
-        // Make sure the key is present
-        if(isWildcarded(id)) {
-            super.deleteCurrent(getSpecificId(id, dataBefore), dataBefore, ctx);
-        } else {
-            super.deleteCurrent(id, dataBefore, ctx);
-        }
-    }
-
-    private boolean isWildcarded(final InstanceIdentifier<D> id) {
-        return id.firstIdentifierOf(getManagedDataObjectType().getTargetType()).isWildcarded();
-    }
-
-    private InstanceIdentifier<D> getSpecificId(final InstanceIdentifier<D> currentId, final D current) {
-        return RWUtils.replaceLastInId(currentId,
-            new InstanceIdentifier.IdentifiableItem<>(currentId.getTargetType(), current.getKey()));
-    }
-}
diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java
new file mode 100644 (file)
index 0000000..b61fb51
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.impl.write;
+
+import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer;
+import io.fd.honeycomb.v3po.translate.util.RWUtils;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Special writer handling updates for nodes of type list.
+ */
+public final class GenericListWriter<D extends DataObject & Identifiable<K>, K extends Identifier<D>> extends
+    AbstractCompositeWriter<D> implements Writer<D> {
+
+    private final ListWriterCustomizer<D, K> customizer;
+
+    public GenericListWriter(@Nonnull final InstanceIdentifier<D> type,
+                             @Nonnull final ListWriterCustomizer<D, K> customizer) {
+        super(type);
+        this.customizer = customizer;
+    }
+
+    @Override
+    protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D data,
+                                          @Nonnull final WriteContext ctx) throws WriteFailedException {
+        customizer.writeCurrentAttributes(id, data, ctx);
+    }
+
+    @Override
+    protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
+                                           @Nonnull final WriteContext ctx) throws WriteFailedException {
+        customizer.deleteCurrentAttributes(id, dataBefore, ctx);
+    }
+
+    @Override
+    protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
+                                           @Nonnull final D dataAfter, @Nonnull final WriteContext ctx)
+        throws WriteFailedException {
+        customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx);
+    }
+
+    @Override
+    protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
+        throws WriteFailedException {
+        // Make sure the key is present
+        if (isWildcarded(id)) {
+            super.writeCurrent(getSpecificId(id, data), data, ctx);
+        } else {
+            super.writeCurrent(id, data, ctx);
+        }
+    }
+
+    @Override
+    protected void updateCurrent(final InstanceIdentifier<D> id, final D dataBefore, final D dataAfter,
+                                 final WriteContext ctx) throws WriteFailedException {
+        // Make sure the key is present
+        if (isWildcarded(id)) {
+            super.updateCurrent(getSpecificId(id, dataBefore), dataBefore, dataAfter, ctx);
+        } else {
+            super.updateCurrent(id, dataBefore, dataAfter, ctx);
+        }
+    }
+
+    @Override
+    protected void deleteCurrent(final InstanceIdentifier<D> id, final D dataBefore, final WriteContext ctx)
+        throws WriteFailedException {
+        // Make sure the key is present
+        if (isWildcarded(id)) {
+            super.deleteCurrent(getSpecificId(id, dataBefore), dataBefore, ctx);
+        } else {
+            super.deleteCurrent(id, dataBefore, ctx);
+        }
+    }
+
+    private boolean isWildcarded(final InstanceIdentifier<D> id) {
+        return id.firstIdentifierOf(getManagedDataObjectType().getTargetType()).isWildcarded();
+    }
+
+    private InstanceIdentifier<D> getSpecificId(final InstanceIdentifier<D> currentId, final D current) {
+        return RWUtils.replaceLastInId(currentId,
+            new InstanceIdentifier.IdentifiableItem<>(currentId.getTargetType(), current.getKey()));
+    }
+}
 
 package io.fd.honeycomb.v3po.translate.impl.write;
 
-import io.fd.honeycomb.v3po.translate.impl.TraversalType;
 import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
 import io.fd.honeycomb.v3po.translate.write.WriteContext;
 import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import java.util.List;
 import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
-public class CompositeRootWriter<D extends DataObject> extends AbstractCompositeWriter<D> {
+/**
+ * Special writer handling updates for any complex nodes.
+ */
+public final class GenericWriter<D extends DataObject> extends AbstractCompositeWriter<D> {
 
     private final RootWriterCustomizer<D> customizer;
 
-    public CompositeRootWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                               @Nonnull final RootWriterCustomizer<D> customizer) {
-        this(type, childWriters, augWriters, customizer, TraversalType.PREORDER);
-    }
-
-    public CompositeRootWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final List<ChildWriter<? extends Augmentation<D>>> augWriters,
-                               @Nonnull final RootWriterCustomizer<D> customizer,
-                               @Nonnull final TraversalType traversalType) {
-        super(type, childWriters, augWriters, traversalType);
+    public GenericWriter(@Nonnull final InstanceIdentifier<D> type,
+                         @Nonnull final RootWriterCustomizer<D> customizer) {
+        super(type);
         this.customizer = customizer;
     }
 
-    public CompositeRootWriter(@Nonnull final Class<D> type,
-                               @Nonnull final List<ChildWriter<? extends ChildOf<D>>> childWriters,
-                               @Nonnull final RootWriterCustomizer<D> customizer) {
-        this(type, childWriters, RWUtils.<D>emptyAugWriterList(), customizer);
-    }
-
-    public CompositeRootWriter(@Nonnull final Class<D> type,
-                               @Nonnull final RootWriterCustomizer<D> customizer) {
-        this(type, RWUtils.<D>emptyChildWriterList(), RWUtils.<D>emptyAugWriterList(), customizer);
-    }
-
     @Override
     protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D data,
                                           @Nonnull final WriteContext ctx) throws WriteFailedException {
diff --git a/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java
new file mode 100644 (file)
index 0000000..54a7466
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.impl.write;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class GenericListWriterTest {
+
+    private static final InstanceIdentifier<IdentifiableDataObject>
+            DATA_OBJECT_INSTANCE_IDENTIFIER = InstanceIdentifier.create(IdentifiableDataObject.class);
+    @Mock
+    private ListWriterCustomizer<IdentifiableDataObject, DataObjectIdentifier> customizer;
+    @Mock
+    private WriteContext ctx;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        final GenericListWriter<IdentifiableDataObject, DataObjectIdentifier> writer =
+                new GenericListWriter<>(DATA_OBJECT_INSTANCE_IDENTIFIER, customizer);
+
+        final IdentifiableDataObject before = mock(IdentifiableDataObject.class);
+        final DataObjectIdentifier beforeKey = mock(DataObjectIdentifier.class);
+        when(before.getKey()).thenReturn(beforeKey);
+        final IdentifiableDataObject after = mock(IdentifiableDataObject.class);
+        final DataObjectIdentifier keyAfter = mock(DataObjectIdentifier.class);
+        when(after.getKey()).thenReturn(keyAfter);
+
+        assertEquals(DATA_OBJECT_INSTANCE_IDENTIFIER, writer.getManagedDataObjectType());
+
+        final InstanceIdentifier<IdentifiableDataObject> keyedIdBefore =
+                (InstanceIdentifier<IdentifiableDataObject>) InstanceIdentifier.create(Collections
+                        .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, beforeKey)));
+        final InstanceIdentifier<IdentifiableDataObject> keyedIdAfter =
+                (InstanceIdentifier<IdentifiableDataObject>) InstanceIdentifier.create(Collections
+                        .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, keyAfter)));
+
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx);
+        verify(customizer).updateCurrentAttributes(keyedIdBefore, before, after, ctx);
+
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, null, ctx);
+        verify(customizer).deleteCurrentAttributes(keyedIdBefore, before, ctx);
+
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, null, after, ctx);
+        verify(customizer).writeCurrentAttributes(keyedIdAfter, after, ctx);
+    }
+
+    private abstract static class IdentifiableDataObject implements DataObject, Identifiable<DataObjectIdentifier> {}
+    private abstract static class DataObjectIdentifier implements Identifier<IdentifiableDataObject> {}
+}
\ No newline at end of file
diff --git a/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java
new file mode 100644 (file)
index 0000000..919072b
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.impl.write;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class GenericWriterTest {
+
+    private static final InstanceIdentifier<DataObject>
+            DATA_OBJECT_INSTANCE_IDENTIFIER = InstanceIdentifier.create(DataObject.class);
+    @Mock
+    private RootWriterCustomizer<DataObject> customizer;
+    @Mock
+    private WriteContext ctx;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        final GenericWriter<DataObject> writer =
+                new GenericWriter<>(DATA_OBJECT_INSTANCE_IDENTIFIER, customizer);
+
+        final DataObject before = mock(DataObject.class);
+        final DataObject after = mock(DataObject.class);
+
+        assertEquals(DATA_OBJECT_INSTANCE_IDENTIFIER, writer.getManagedDataObjectType());
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx);
+        verify(customizer).updateCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx);
+
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, null, ctx);
+        verify(customizer).deleteCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, before, ctx);
+
+        writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, null, after, ctx);
+        verify(customizer).writeCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, after, ctx);
+    }
+}
\ No newline at end of file
index 86a11bd..e4197cf 100644 (file)
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>yang-data-codec-gson</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.jgrapht</groupId>
+            <artifactId>jgrapht-core</artifactId>
+            <version>0.9.2</version>
+        </dependency>
 
         <!-- Testing Dependencies -->
         <dependency>
index b712e15..55ae9ec 100644 (file)
@@ -23,13 +23,13 @@ import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import io.fd.honeycomb.v3po.translate.SubtreeManager;
 import io.fd.honeycomb.v3po.translate.read.ChildReader;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.ChildOf;
@@ -79,18 +79,10 @@ public final class RWUtils {
         return Collections.emptyList();
     }
 
-    public static <T> List<ChildWriter<? extends ChildOf<T>>> emptyChildWriterList() {
-        return Collections.emptyList();
-    }
-
     public static <T> List<ChildReader<? extends Augmentation<T>>> emptyAugReaderList() {
         return Collections.emptyList();
     }
 
-    public static <T> List<ChildWriter<? extends Augmentation<T>>> emptyAugWriterList() {
-        return Collections.emptyList();
-    }
-
     public static <T> List<ChildReader<? extends Augmentation<T>>> singletonAugReaderList(
         ChildReader<? extends Augmentation<T>> item) {
         return Collections.<ChildReader<? extends Augmentation<T>>>singletonList(item);
@@ -101,16 +93,6 @@ public final class RWUtils {
         return Collections.<ChildReader<? extends ChildOf<T>>>singletonList(item);
     }
 
-    public static <T> List<ChildWriter<? extends ChildOf<T>>> singletonChildWriterList(
-        ChildWriter<? extends ChildOf<T>> item) {
-        return Collections.<ChildWriter<? extends ChildOf<T>>>singletonList(item);
-    }
-
-    public static <T> List<ChildWriter<? extends Augmentation<T>>> singletonAugWriterList(
-        ChildWriter<? extends Augmentation<T>> item) {
-        return Collections.<ChildWriter<? extends Augmentation<T>>>singletonList(item);
-    }
-
     /**
      * Replace last item in ID with a provided IdentifiableItem of the same type
      */
@@ -197,4 +179,21 @@ public final class RWUtils {
         return (InstanceIdentifier<D>) InstanceIdentifier.create(Iterables.concat(
             parentId.getPathArguments(), Collections.singleton(t)));
     }
+
+    /**
+     * Transform a keyed instance identifier into a wildcarded one.
+     */
+    public static InstanceIdentifier<?> makeIidWildcarded(final InstanceIdentifier<?> id) {
+        final List<InstanceIdentifier.PathArgument> transformedPathArguments =
+                StreamSupport.stream(id.getPathArguments().spliterator(), false)
+                        .map(RWUtils::cleanPathArgumentFromKeys)
+                        .collect(Collectors.toList());
+        return InstanceIdentifier.create(transformedPathArguments);
+    }
+
+    private static InstanceIdentifier.PathArgument cleanPathArgumentFromKeys(final InstanceIdentifier.PathArgument pathArgument) {
+        return pathArgument instanceof InstanceIdentifier.IdentifiableItem<?, ?>
+                ? new InstanceIdentifier.Item<>(pathArgument.getType())
+                : pathArgument;
+    }
 }
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java
deleted file mode 100644 (file)
index cd53a4f..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.util.write;
-
-import io.fd.honeycomb.v3po.translate.TranslationException;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.Map;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * WriterRegistry wrapper providing AutoCloseable interface.
- */
-public final class CloseableWriterRegistry implements WriterRegistry, AutoCloseable {
-    private final WriterRegistry writerRegistry;
-
-    public CloseableWriterRegistry( final WriterRegistry writerRegistry) {
-        this.writerRegistry = writerRegistry;
-    }
-
-    @Override
-    public void update(
-            @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
-            @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter,
-            @Nonnull final WriteContext ctx) throws TranslationException {
-        writerRegistry.update(nodesBefore, nodesAfter, ctx);
-    }
-
-    @Override
-    public void update(
-            @Nonnull final InstanceIdentifier<? extends DataObject> id,
-            @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
-            @Nonnull final WriteContext ctx) throws WriteFailedException {
-        writerRegistry.update(id, dataBefore, dataAfter, ctx);
-    }
-
-    @Nonnull
-    @Override
-    public InstanceIdentifier<DataObject> getManagedDataObjectType() {
-        return writerRegistry.getManagedDataObjectType();
-    }
-
-    @Override
-    public void close() throws Exception {
-        // NOOP
-    }
-}
\ No newline at end of file
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java
deleted file mode 100644 (file)
index 061d3fa..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.util.write;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Multimap;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import io.fd.honeycomb.v3po.translate.write.Writer;
-import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Simple writer registry able to perform and aggregated write (ROOT write) on top of all provided writers. Also able to
- * delegate a specific write to one of the delegate writers.
- *
- * This could serve as a utility to hold & hide all available writers in upper layers.
- */
-public final class DelegatingWriterRegistry implements WriterRegistry {
-
-    private static final Logger LOG = LoggerFactory.getLogger(DelegatingWriterRegistry.class);
-
-    private final Map<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriters;
-
-    /**
-     * Create new {@link DelegatingWriterRegistry}
-     *
-     * @param rootWriters List of delegate writers
-     */
-    public DelegatingWriterRegistry(@Nonnull final List<Writer<? extends DataObject>> rootWriters) {
-        this.rootWriters = RWUtils.uniqueLinkedIndex(checkNotNull(rootWriters), RWUtils.MANAGER_CLASS_FUNCTION);
-    }
-
-    /**
-     * @throws UnsupportedOperationException This getter is not supported for writer registry since it does not manage a
-     *                                       specific node type
-     */
-    @Nonnull
-    @Override
-    public InstanceIdentifier<DataObject> getManagedDataObjectType() {
-        throw new UnsupportedOperationException("Root registry has no type");
-    }
-
-    @Override
-    public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-                       @Nullable final DataObject dataBefore,
-                       @Nullable final DataObject dataAfter,
-                       @Nonnull final WriteContext ctx) throws WriteFailedException {
-        final InstanceIdentifier.PathArgument first = checkNotNull(
-                Iterables.getFirst(id.getPathArguments(), null), "Empty id");
-        final Writer<? extends DataObject> writer = rootWriters.get(first.getType());
-        checkNotNull(writer,
-                "Unable to write %s. Missing writer. Current writers for: %s", id, rootWriters.keySet());
-        writer.update(id, dataBefore, dataAfter, ctx);
-    }
-
-    @Override
-    public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
-                       @Nonnull final Map<InstanceIdentifier<?>, DataObject> nodesAfter,
-                       @Nonnull final WriteContext ctx) throws WriteFailedException {
-
-        Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds = HashMultimap.create();
-        try {
-            checkAllWritersPresent(nodesBefore, rootIdToNestedIds);
-            checkAllWritersPresent(nodesAfter, rootIdToNestedIds);
-        } catch (IllegalArgumentException e) {
-            LOG.warn("Unable to process update", e);
-            throw e;
-        }
-
-        final List<InstanceIdentifier<?>> processedNodes = Lists.newArrayList();
-
-        for (Map.Entry<Class<? extends DataObject>, Writer<? extends DataObject>> rootWriterEntry : rootWriters
-                .entrySet()) {
-
-            final InstanceIdentifier<? extends DataObject> id = rootWriterEntry.getValue().getManagedDataObjectType();
-            // FIXME !! this is not ideal, we are not handling nested updates in expected order
-            // Root writers are invoked in order they were registered, but nested updates are not, since they are
-            // iterated here.
-            //
-            for (InstanceIdentifier<?> specificInstanceIdentifier : rootIdToNestedIds.get(id)) {
-                final DataObject dataBefore = nodesBefore.get(specificInstanceIdentifier);
-                final DataObject dataAfter = nodesAfter.get(specificInstanceIdentifier);
-
-                // No change to current writer
-                if (dataBefore == null && dataAfter == null) {
-                    continue;
-                }
-
-                try {
-                    LOG.debug("ChangesProcessor.applyChanges() processing dataBefore={}, dataAfter={}", dataBefore, dataAfter);
-                    update(specificInstanceIdentifier, dataBefore, dataAfter, ctx);
-                    processedNodes.add(id);
-                } catch (Exception e) {
-                    LOG.error("Error while processing data change of: {} (before={}, after={})", id, dataBefore, dataAfter, e);
-                    throw new BulkUpdateException(id, new ReverterImpl(this, processedNodes, nodesBefore, nodesAfter, ctx), e);
-                }
-            }
-        }
-    }
-
-    private void checkAllWritersPresent(final @Nonnull Map<InstanceIdentifier<?>, DataObject> nodesBefore,
-                                        final @Nonnull Multimap<InstanceIdentifier<?>, InstanceIdentifier<?>> rootIdToNestedIds) {
-        for (final InstanceIdentifier<?> changeId : nodesBefore.keySet()) {
-            final InstanceIdentifier.PathArgument first = Iterables.getFirst(changeId.getPathArguments(), null);
-            checkNotNull(first, "Empty identifier detected");
-            final InstanceIdentifier<? extends DataObject> rootId = InstanceIdentifier.create(first.getType());
-            checkArgument(rootWriters.keySet().contains(first.getType()),
-                    "Unable to handle change. Missing dedicated writer for: %s", first.getType());
-            rootIdToNestedIds.put(rootId, changeId);
-        }
-    }
-
-    private static final class ReverterImpl implements Reverter {
-        private final WriterRegistry delegatingWriterRegistry;
-        private final List<InstanceIdentifier<?>> processedNodes;
-        private final Map<InstanceIdentifier<?>, DataObject> nodesBefore;
-        private final Map<InstanceIdentifier<?>, DataObject> nodesAfter;
-        private final WriteContext ctx;
-
-        ReverterImpl(final WriterRegistry delegatingWriterRegistry,
-                            final List<InstanceIdentifier<?>> processedNodes,
-                            final Map<InstanceIdentifier<?>, DataObject> nodesBefore,
-                            final Map<InstanceIdentifier<?>, DataObject> nodesAfter, final WriteContext ctx) {
-            this.delegatingWriterRegistry = delegatingWriterRegistry;
-            this.processedNodes = processedNodes;
-            this.nodesBefore = nodesBefore;
-            this.nodesAfter = nodesAfter;
-            this.ctx = ctx;
-        }
-
-        @Override
-        public void revert() throws RevertFailedException {
-            final LinkedList<InstanceIdentifier<?>> notReverted = new LinkedList<>(processedNodes);
-
-            while (notReverted.size() > 0) {
-                final InstanceIdentifier<?> node = notReverted.peekLast();
-                LOG.debug("ChangesProcessor.revertChanges() processing node={}", node);
-
-                final DataObject dataBefore = nodesBefore.get(node);
-                final DataObject dataAfter = nodesAfter.get(node);
-
-                // revert a change by invoking writer with reordered arguments
-                try {
-                    delegatingWriterRegistry.update(node, dataAfter, dataBefore, ctx);
-                    notReverted.removeLast(); // change was successfully reverted
-                } catch (Exception e) {
-                    throw new RevertFailedException(notReverted, e);
-                }
-
-            }
-        }
-    }
-}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java
deleted file mode 100644 (file)
index 9f9c9f5..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.util.write;
-
-import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * Customizer not performing any changes on current level. Suitable for nodes that don't have any leaves and all of
- * its child nodes are managed by dedicated writers
- */
-public class NoopWriterCustomizer<D extends DataObject> implements RootWriterCustomizer<D> {
-
-    @Override
-    public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataAfter,
-                                       @Nonnull final WriteContext ctx) {
-
-    }
-
-    @Override
-    public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                        @Nonnull final D dataAfter,
-                                        @Nonnull final WriteContext ctx) {
-
-    }
-
-    @Override
-    public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D dataBefore,
-                                        @Nonnull final WriteContext ctx) {
-
-    }
-}
index 8668542..236ad89 100644 (file)
@@ -20,7 +20,6 @@ import io.fd.honeycomb.v3po.translate.TranslationException;
 import io.fd.honeycomb.v3po.translate.write.WriteContext;
 import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
 import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.Map;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.opendaylight.yangtools.yang.binding.DataObject;
@@ -32,18 +31,10 @@ import org.slf4j.LoggerFactory;
  * Empty registry that does not perform any changes. Can be used in data layer, if we want to disable passing data to
  * translation layer.
  */
-public class NoopWriterRegistry implements WriterRegistry {
+public class NoopWriterRegistry implements WriterRegistry, AutoCloseable {
 
     private static final Logger LOG = LoggerFactory.getLogger(NoopWriterRegistry.class);
 
-    @Override
-    public void update(@Nonnull final Map<InstanceIdentifier<?>, DataObject> dataBefore,
-                       @Nonnull final Map<InstanceIdentifier<?>, DataObject> dataAfter, @Nonnull final WriteContext ctx)
-            throws TranslationException {
-        LOG.trace("NoopWriterRegistry.update dataBefore{}, dataAfter={], ctx={}", dataBefore, dataAfter, ctx);
-        // NOOP
-    }
-
     @Override
     public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
                        @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
@@ -53,9 +44,20 @@ public class NoopWriterRegistry implements WriterRegistry {
         // NOOP
     }
 
+    @Override
+    public void update(@Nonnull final DataObjectUpdates updates,
+                       @Nonnull final WriteContext ctx) throws TranslationException {
+        // NOOP
+    }
+
     @Nonnull
     @Override
     public InstanceIdentifier<DataObject> getManagedDataObjectType() {
-        throw new UnsupportedOperationException("Root registry has no type");
+        throw new UnsupportedOperationException("Noop registry has no type");
+    }
+
+    @Override
+    public void close() throws Exception {
+        // NOOP
     }
 }
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java
deleted file mode 100644 (file)
index 6d29214..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.util.write;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
-
-import com.google.common.base.Optional;
-import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.Augmentable;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * Might be slow !
- */
-public class ReflexiveAugmentWriterCustomizer<C extends DataObject> extends NoopWriterCustomizer<C> implements
-    ChildWriterCustomizer<C> {
-
-    @Nonnull
-    @Override
-    @SuppressWarnings("unchecked")
-    public Optional<C> extract(@Nonnull final InstanceIdentifier<C> currentId, @Nonnull final DataObject parentData) {
-        checkArgument(parentData instanceof Augmentable<?>, "Not augmnatable parent object: %s", parentData);
-        final Class<C> currentType = currentId.getTargetType();
-        final Augmentation<?> augmentation = ((Augmentable) parentData).getAugmentation(currentType);
-        if(augmentation == null) {
-            return Optional.absent();
-        } else {
-            checkState(currentType.isAssignableFrom(augmentation.getClass()));
-            return Optional.of(currentType.cast(augmentation));
-        }
-    }
-}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java
deleted file mode 100644 (file)
index 79cdf62..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.util.write;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Iterables;
-import io.fd.honeycomb.v3po.translate.util.ReflectionUtils;
-import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Collections;
-import javax.annotation.Nonnull;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * Might be slow !
- */
-public class ReflexiveChildWriterCustomizer<C extends DataObject> extends NoopWriterCustomizer<C> implements
-    ChildWriterCustomizer<C> {
-
-    @Nonnull
-    @Override
-    @SuppressWarnings("unchecked")
-    public Optional<C> extract(@Nonnull final InstanceIdentifier<C> currentId, @Nonnull final DataObject parentData) {
-        final Class<C> currentType = currentId.getTargetType();
-        final Optional<Method> method = ReflectionUtils.findMethodReflex(getParentType(currentId),
-            "get" + currentType.getSimpleName(), Collections.<Class<?>>emptyList(), currentType);
-
-        Preconditions.checkArgument(method.isPresent(), "Unable to get %s from %s", currentType, parentData);
-
-        try {
-            return method.isPresent()
-                ? Optional.fromNullable((C) method.get().invoke(parentData))
-                : Optional.absent();
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            throw new IllegalArgumentException("Unable to get " + currentType + " from " + parentData, e);
-        }
-    }
-
-    private Class<? extends DataObject> getParentType(final @Nonnull InstanceIdentifier<C> currentId) {
-        return Iterables.get(currentId.getPathArguments(), Iterables.size(currentId.getPathArguments()) - 2).getType();
-    }
-}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java
new file mode 100644 (file)
index 0000000..79d8eb8
--- /dev/null
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.util.write.registry;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import io.fd.honeycomb.v3po.translate.TranslationException;
+import io.fd.honeycomb.v3po.translate.util.RWUtils;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Flat writer registry, delegating updates to writers in the order writers were submitted.
+ */
+@ThreadSafe
+final class FlatWriterRegistry implements WriterRegistry {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FlatWriterRegistry.class);
+
+    // All types handled by writers directly or as children
+    private final ImmutableSet<InstanceIdentifier<?>> handledTypes;
+
+    private final Set<InstanceIdentifier<?>> writersOrderReversed;
+    private final Set<InstanceIdentifier<?>> writersOrder;
+    private final Map<InstanceIdentifier<?>, Writer<?>> writers;
+
+    /**
+     * Create flat registry instance.
+     *
+     * @param writers immutable, ordered map of writers to use to process updates. Order of the writers has to be
+     *                one in which create and update operations should be handled. Deletes will be handled in reversed
+     *                order. All deletes are handled before handling all the updates.
+     */
+    FlatWriterRegistry(@Nonnull final ImmutableMap<InstanceIdentifier<?>, Writer<?>> writers) {
+        this.writers = writers;
+        this.writersOrderReversed = Sets.newLinkedHashSet(Lists.reverse(Lists.newArrayList(writers.keySet())));
+        this.writersOrder = writers.keySet();
+        this.handledTypes = getAllHandledTypes(writers);
+    }
+
+    private static ImmutableSet<InstanceIdentifier<?>> getAllHandledTypes(
+            @Nonnull final ImmutableMap<InstanceIdentifier<?>, Writer<?>> writers) {
+        final ImmutableSet.Builder<InstanceIdentifier<?>> handledTypesBuilder = ImmutableSet.builder();
+        for (Map.Entry<InstanceIdentifier<?>, Writer<?>> writerEntry : writers.entrySet()) {
+            final InstanceIdentifier<?> writerType = writerEntry.getKey();
+            final Writer<?> writer = writerEntry.getValue();
+            handledTypesBuilder.add(writerType);
+            if (writer instanceof SubtreeWriter) {
+                handledTypesBuilder.addAll(((SubtreeWriter<?>) writer).getHandledChildTypes());
+            }
+        }
+        return handledTypesBuilder.build();
+    }
+
+    @Override
+    public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+                       @Nullable final DataObject dataBefore,
+                       @Nullable final DataObject dataAfter,
+                       @Nonnull final WriteContext ctx) throws WriteFailedException {
+        singleUpdate(ImmutableMultimap.of(
+                RWUtils.makeIidWildcarded(id), DataObjectUpdate.create(id, dataBefore, dataAfter)), ctx);
+    }
+
+    @Override
+    public void update(@Nonnull final DataObjectUpdates updates,
+                       @Nonnull final WriteContext ctx) throws TranslationException {
+        if (updates.isEmpty()) {
+            return;
+        }
+
+        // Optimization
+        if (updates.containsOnlySingleType()) {
+            // First process delete
+            singleUpdate(updates.getDeletes(), ctx);
+            // Next is update
+            singleUpdate(updates.getUpdates(), ctx);
+        } else {
+            // First process deletes
+            bulkUpdate(updates.getDeletes(), ctx, true, writersOrderReversed);
+            // Next are updates
+            bulkUpdate(updates.getUpdates(), ctx, true, writersOrder);
+        }
+
+        LOG.debug("Update successful for types: {}", updates.getTypeIntersection());
+        LOG.trace("Update successful for: {}", updates);
+    }
+
+    private void singleUpdate(@Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+                              @Nonnull final WriteContext ctx) throws WriteFailedException {
+        if (updates.isEmpty()) {
+            return;
+        }
+
+        final InstanceIdentifier<?> singleType = updates.keySet().iterator().next();
+        LOG.debug("Performing single type update for: {}", singleType);
+        Collection<? extends DataObjectUpdate> singleTypeUpdates = updates.get(singleType);
+        Writer<?> writer = getWriter(singleType);
+
+        if (writer == null) {
+            // This node must be handled by a subtree writer, find it and call it or else fail
+            checkArgument(handledTypes.contains(singleType), "Unable to process update. Missing writers for: %s",
+                    singleType);
+            writer = getSubtreeWriterResponsible(singleType);
+            singleTypeUpdates = getParentDataObjectUpdate(ctx, updates, writer);
+        }
+
+        LOG.trace("Performing single type update with writer: {}", writer);
+        for (DataObjectUpdate singleUpdate : singleTypeUpdates) {
+            writer.update(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
+        }
+    }
+
+    private Writer<?> getSubtreeWriterResponsible(final InstanceIdentifier<?> singleType) {
+        final Writer<?> writer;// This is slow ( minor TODO-perf )
+        writer = writers.values().stream()
+                .filter(w -> w instanceof SubtreeWriter)
+                .filter(w -> ((SubtreeWriter<?>) w).getHandledChildTypes().contains(singleType))
+                .findFirst()
+                .get();
+        return writer;
+    }
+
+    private Collection<DataObjectUpdate> getParentDataObjectUpdate(final WriteContext ctx,
+                                                                   final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+                                                                   final Writer<?> writer) {
+        // Now read data for subtree reader root, but first keyed ID is needed and that ID can be cut from updates
+        InstanceIdentifier<?> firstAffectedChildId = ((SubtreeWriter<?>) writer).getHandledChildTypes().stream()
+                .filter(updates::containsKey)
+                .map(unkeyedId -> updates.get(unkeyedId))
+                .flatMap(doUpdates -> doUpdates.stream())
+                .map(DataObjectUpdate::getId)
+                .findFirst()
+                .get();
+
+        final InstanceIdentifier<?> parentKeyedId =
+                RWUtils.cutId(firstAffectedChildId, writer.getManagedDataObjectType());
+
+        final Optional<? extends DataObject> parentBefore = ctx.readBefore(parentKeyedId);
+        final Optional<? extends DataObject> parentAfter = ctx.readAfter(parentKeyedId);
+        return Collections.singleton(
+                DataObjectUpdate.create(parentKeyedId, parentBefore.orNull(), parentAfter.orNull()));
+    }
+
+    private void bulkUpdate(@Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+                            @Nonnull final WriteContext ctx,
+                            final boolean attemptRevert,
+                            @Nonnull final Set<InstanceIdentifier<?>> writersOrder) throws BulkUpdateException {
+        if (updates.isEmpty()) {
+            return;
+        }
+
+        LOG.debug("Performing bulk update with revert attempt: {} for: {}", attemptRevert, updates.keySet());
+
+        // Check that all updates can be handled
+        checkAllTypesCanBeHandled(updates);
+
+        // Capture all changes successfully processed in case revert is needed
+        final Set<InstanceIdentifier<?>> processedNodes = new HashSet<>();
+
+        // Iterate over all writers and call update if there are any related updates
+        for (InstanceIdentifier<?> writerType : writersOrder) {
+            Collection<? extends DataObjectUpdate> writersData = updates.get(writerType);
+            final Writer<?> writer = getWriter(writerType);
+
+            if (writersData.isEmpty()) {
+                // If there are no data for current writer, but it is a SubtreeWriter and there are updates to
+                // its children, still invoke it with its root data
+                if (writer instanceof SubtreeWriter<?> && isAffected(((SubtreeWriter<?>) writer), updates)) {
+                    // Provide parent data for SubtreeWriter for further processing
+                    writersData = getParentDataObjectUpdate(ctx, updates, writer);
+                } else {
+                    // Skipping unaffected writer
+                    // Alternative to this would be modification sort according to the order of writers
+                    continue;
+                }
+            }
+
+            LOG.debug("Performing update for: {}",  writerType);
+            LOG.trace("Performing update with writer: {}", writer);
+
+            for (DataObjectUpdate singleUpdate : writersData) {
+                try {
+                    writer.update(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
+                    processedNodes.add(singleUpdate.getId());
+                    LOG.trace("Update successful for type: {}", writerType);
+                    LOG.debug("Update successful for: {}", singleUpdate);
+                } catch (Exception e) {
+                    LOG.error("Error while processing data change of: {} (updates={})", writerType, writersData, e);
+
+                    final Reverter reverter = attemptRevert
+                            ? new ReverterImpl(processedNodes, updates, writersOrder, ctx)
+                            : () -> {}; // NOOP reverter
+
+                    // Find out which changes left unprocessed
+                    final Set<InstanceIdentifier<?>> unprocessedChanges = updates.values().stream()
+                            .map(DataObjectUpdate::getId)
+                            .filter(id -> !processedNodes.contains(id))
+                            .collect(Collectors.toSet());
+                    throw new BulkUpdateException(unprocessedChanges, reverter, e);
+                }
+            }
+        }
+    }
+
+    private void checkAllTypesCanBeHandled(
+            @Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates) {
+        if (!handledTypes.containsAll(updates.keySet())) {
+            final Sets.SetView<InstanceIdentifier<?>> missingWriters = Sets.difference(updates.keySet(), handledTypes);
+            LOG.warn("Unable to process update. Missing writers for: {}", missingWriters);
+            throw new IllegalArgumentException("Unable to process update. Missing writers for: " + missingWriters);
+        }
+    }
+
+    /**
+     * Check whether {@link SubtreeWriter} is affected by the updates.
+     *
+     * @return true if there are any updates to SubtreeWriter's child nodes (those marked by SubtreeWriter
+     *         as being taken care of)
+     * */
+    private static boolean isAffected(final SubtreeWriter<?> writer,
+                               final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates) {
+        return !Sets.intersection(writer.getHandledChildTypes(), updates.keySet()).isEmpty();
+    }
+
+    private Writer<?> getWriter(@Nonnull final InstanceIdentifier<?> singleType) {
+        final Writer<?> writer = writers.get(singleType);
+        checkNotNull(writer,
+                "Unable to write %s. Missing writer. Current writers for: %s", singleType, writers.keySet());
+        return writer;
+    }
+
+    @Nonnull
+    @Override
+    public InstanceIdentifier<DataObject> getManagedDataObjectType() {
+        throw new UnsupportedOperationException("Registry has no managed type");
+    }
+
+    // FIXME unit test
+    private final class ReverterImpl implements Reverter {
+
+        private final Collection<InstanceIdentifier<?>> processedNodes;
+        private final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates;
+        private final Set<InstanceIdentifier<?>> revertDeleteOrder;
+        private final WriteContext ctx;
+
+        ReverterImpl(final Collection<InstanceIdentifier<?>> processedNodes,
+                     final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+                     final Set<InstanceIdentifier<?>> writersOrderOriginal,
+                     final WriteContext ctx) {
+            this.processedNodes = processedNodes;
+            this.updates = updates;
+            // Use opposite ordering when executing revert
+            this.revertDeleteOrder =  writersOrderOriginal == FlatWriterRegistry.this.writersOrder
+                    ? FlatWriterRegistry.this.writersOrderReversed
+                    : FlatWriterRegistry.this.writersOrder;
+            this.ctx = ctx;
+        }
+
+        @Override
+        public void revert() throws RevertFailedException {
+            Multimap<InstanceIdentifier<?>, DataObjectUpdate> updatesToRevert =
+                    filterAndRevertProcessed(updates, processedNodes);
+
+            LOG.info("Attempting revert for changes: {}", updatesToRevert);
+            try {
+                // Perform reversed bulk update without revert attempt
+                bulkUpdate(updatesToRevert, ctx, true, revertDeleteOrder);
+                LOG.info("Revert successful");
+            } catch (BulkUpdateException e) {
+                LOG.error("Revert failed", e);
+                throw new RevertFailedException(e.getFailedIds(), e);
+            }
+        }
+
+        /**
+         * Create new updates map, but only keep already processed changes. Switching before and after data for each
+         * update.
+         */
+        private Multimap<InstanceIdentifier<?>, DataObjectUpdate> filterAndRevertProcessed(
+                final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+                final Collection<InstanceIdentifier<?>> processedNodes) {
+            final Multimap<InstanceIdentifier<?>, DataObjectUpdate> filtered = HashMultimap.create();
+            for (InstanceIdentifier<?> processedNode : processedNodes) {
+                final InstanceIdentifier<?> wildcardedIid = RWUtils.makeIidWildcarded(processedNode);
+                if (updates.containsKey(wildcardedIid)) {
+                    updates.get(wildcardedIid).stream()
+                            .filter(dataObjectUpdate -> processedNode.contains(dataObjectUpdate.getId()))
+                            .forEach(dataObjectUpdate -> filtered.put(processedNode, dataObjectUpdate.reverse()));
+                }
+            }
+            return filtered;
+        }
+    }
+
+}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java
new file mode 100644 (file)
index 0000000..f5d218f
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.util.write.registry;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import io.fd.honeycomb.v3po.translate.util.RWUtils;
+import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import io.fd.honeycomb.v3po.translate.write.WriterRegistryBuilder;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.jgrapht.experimental.dag.DirectedAcyclicGraph;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Builder for {@link FlatWriterRegistry} allowing users to specify inter-writer relationships.
+ */
+@NotThreadSafe
+public final class FlatWriterRegistryBuilder implements ModifiableWriterRegistry, WriterRegistryBuilder, AutoCloseable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(FlatWriterRegistryBuilder.class);
+
+    // Using directed acyclic graph to represent the ordering relationships between writers
+    private final DirectedAcyclicGraph<InstanceIdentifier<?>, WriterRelation>
+            writersRelations = new DirectedAcyclicGraph<>((sourceVertex, targetVertex) -> new WriterRelation());
+    private final Map<InstanceIdentifier<?>, Writer<?>> writersMap = new HashMap<>();
+
+    /**
+     * AddWriter without any special relationship to any other type.
+     */
+    @Override
+    public FlatWriterRegistryBuilder addWriter(@Nonnull final Writer<? extends DataObject> writer) {
+        // Make IID wildcarded just in case
+        // + the way InstanceIdentifier.create + equals work for Identifiable items is unexpected, meaning updates would
+        // not be matched to writers in registry
+        final InstanceIdentifier<?> targetType = RWUtils.makeIidWildcarded(writer.getManagedDataObjectType());
+        checkWriterNotPresentYet(targetType);
+        writersRelations.addVertex(targetType);
+        writersMap.put(targetType, writer);
+        return this;
+    }
+
+    /**
+     * AddWriter without any special relationship to any other type.
+     */
+    @Override
+    public FlatWriterRegistryBuilder addSubtreeWriter(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                                      @Nonnull final Writer<? extends DataObject> writer) {
+        addWriter(SubtreeWriter.createForWriter(handledChildren, writer));
+        return this;
+    }
+
+    private void checkWriterNotPresentYet(final InstanceIdentifier<?> targetType) {
+        Preconditions.checkArgument(!writersMap.containsKey(targetType),
+                "Writer for type: %s already present: %s", targetType, writersMap.get(targetType));
+    }
+
+    /**
+     * Add writer with relationship: to be executed before writer handling relatedType.
+     */
+    @Override
+    public FlatWriterRegistryBuilder addWriterBefore(@Nonnull final Writer<? extends DataObject> writer,
+                                                     @Nonnull final InstanceIdentifier<?> relatedType) {
+        final InstanceIdentifier<?> targetType = RWUtils.makeIidWildcarded(writer.getManagedDataObjectType());
+        final InstanceIdentifier<?> wildcardedRelatedType = RWUtils.makeIidWildcarded(relatedType);
+        checkWriterNotPresentYet(targetType);
+        writersRelations.addVertex(targetType);
+        writersRelations.addVertex(wildcardedRelatedType);
+        addEdge(targetType, wildcardedRelatedType);
+        writersMap.put(targetType, writer);
+        return this;
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addSubtreeWriterBefore(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                                            @Nonnull final Writer<? extends DataObject> writer,
+                                                            @Nonnull final InstanceIdentifier<?> relatedType) {
+        return addWriterBefore(SubtreeWriter.createForWriter(handledChildren, writer), relatedType);
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addWriterBefore(@Nonnull final Writer<? extends DataObject> writer,
+                                                     @Nonnull final Collection<InstanceIdentifier<?>> relatedTypes) {
+        final InstanceIdentifier<?> targetType = RWUtils.makeIidWildcarded(writer.getManagedDataObjectType());
+        checkWriterNotPresentYet(targetType);
+        writersRelations.addVertex(targetType);
+        relatedTypes.stream()
+                .map(RWUtils::makeIidWildcarded)
+                .forEach(writersRelations::addVertex);
+        relatedTypes.stream()
+                .map(RWUtils::makeIidWildcarded)
+                .forEach(type -> addEdge(targetType, type));
+        writersMap.put(targetType, writer);
+        return this;
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addSubtreeWriterBefore(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                                            @Nonnull final Writer<? extends DataObject> writer,
+                                                            @Nonnull final Collection<InstanceIdentifier<?>> relatedTypes) {
+        return addWriterBefore(SubtreeWriter.createForWriter(handledChildren, writer), relatedTypes);
+    }
+
+    /**
+     * Add writer with relationship: to be executed after writer handling relatedType.
+     */
+    @Override
+    public FlatWriterRegistryBuilder addWriterAfter(@Nonnull final Writer<? extends DataObject> writer,
+                                                    @Nonnull final InstanceIdentifier<?> relatedType) {
+        final InstanceIdentifier<?> targetType = RWUtils.makeIidWildcarded(writer.getManagedDataObjectType());
+        final InstanceIdentifier<?> wildcardedRelatedType = RWUtils.makeIidWildcarded(relatedType);
+        checkWriterNotPresentYet(targetType);
+        writersRelations.addVertex(targetType);
+        writersRelations.addVertex(wildcardedRelatedType);
+        // set edge to indicate before relationship, just reversed
+        addEdge(wildcardedRelatedType, targetType);
+        writersMap.put(targetType, writer);
+        return this;
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addSubtreeWriterAfter(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                                           @Nonnull final Writer<? extends DataObject> writer,
+                                                           @Nonnull final InstanceIdentifier<?> relatedType) {
+        return addWriterAfter(SubtreeWriter.createForWriter(handledChildren, writer), relatedType);
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addWriterAfter(@Nonnull final Writer<? extends DataObject> writer,
+                                                    @Nonnull final Collection<InstanceIdentifier<?>> relatedTypes) {
+        final InstanceIdentifier<?> targetType = RWUtils.makeIidWildcarded(writer.getManagedDataObjectType());
+        checkWriterNotPresentYet(targetType);
+        writersRelations.addVertex(targetType);
+        relatedTypes.stream()
+                .map(RWUtils::makeIidWildcarded)
+                .forEach(writersRelations::addVertex);
+        // set edge to indicate before relationship, just reversed
+        relatedTypes.stream()
+                .map(RWUtils::makeIidWildcarded)
+                .forEach(type -> addEdge(type, targetType));
+        writersMap.put(targetType, writer);
+        return this;
+    }
+
+    @Override
+    public FlatWriterRegistryBuilder addSubtreeWriterAfter(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                                           @Nonnull final Writer<? extends DataObject> writer,
+                                                           @Nonnull final Collection<InstanceIdentifier<?>> relatedTypes) {
+        return addWriterAfter(SubtreeWriter.createForWriter(handledChildren, writer), relatedTypes);
+    }
+
+
+    private void addEdge(final InstanceIdentifier<?> targetType,
+                         final InstanceIdentifier<?> relatedType) {
+        try {
+            writersRelations.addDagEdge(targetType, relatedType);
+        } catch (DirectedAcyclicGraph.CycleFoundException e) {
+            throw new IllegalArgumentException(String.format(
+                    "Unable to add writer with relation: %s -> %s. Loop detected", targetType, relatedType), e);
+        }
+    }
+
+    /**
+     * Create FlatWriterRegistry with writers ordered according to submitted relationships.
+     */
+    @Override
+    public FlatWriterRegistry build() {
+        final ImmutableMap<InstanceIdentifier<?>, Writer<?>> mappedWriters = getMappedWriters();
+        LOG.debug("Building writer registry with writers: {}",
+                mappedWriters.keySet().stream()
+                        .map(InstanceIdentifier::getTargetType)
+                        .map(Class::getSimpleName)
+                        .collect(Collectors.joining(", ")));
+        LOG.trace("Building writer registry with writers: {}", mappedWriters);
+        return new FlatWriterRegistry(mappedWriters);
+    }
+
+    @VisibleForTesting
+    ImmutableMap<InstanceIdentifier<?>, Writer<?>> getMappedWriters() {
+        final ImmutableMap.Builder<InstanceIdentifier<?>, Writer<?>> builder = ImmutableMap.builder();
+        // Iterate writer types according to their relationships from graph
+        writersRelations.iterator()
+                .forEachRemaining(writerType -> {
+                    // There might be types stored just for relationship sake, no real writer, ignoring those
+                    if (writersMap.containsKey(writerType)) {
+                        builder.put(writerType, writersMap.get(writerType));
+                    }
+                });
+        return builder.build();
+    }
+
+    @Override
+    public void close() throws Exception {
+        writersMap.clear();
+        writersRelations.removeAllEdges(writersRelations.edgeSet());
+        writersRelations.removeAllVertices(writersRelations.vertexSet());
+    }
+
+    // Represents edges in graph
+    private static final class WriterRelation {}
+}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java
new file mode 100644 (file)
index 0000000..e395b29
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.util.write.registry;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.Iterables;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import java.util.HashSet;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Simple writer delegate for subtree writers (writers handling also children nodes) providing a list of all the
+ * children nodes being handled.
+ */
+final class SubtreeWriter<D extends DataObject> implements Writer<D> {
+
+    private final Writer<D> delegate;
+    private final Set<InstanceIdentifier<?>> handledChildTypes = new HashSet<>();
+
+    private SubtreeWriter(final Writer<D> delegate, Set<InstanceIdentifier<?>> handledTypes) {
+        this.delegate = delegate;
+        for (InstanceIdentifier<?> handledType : handledTypes) {
+            // Iid has to start with writer's handled root type
+            checkArgument(delegate.getManagedDataObjectType().getTargetType().equals(
+                    handledType.getPathArguments().iterator().next().getType()),
+                    "Handled node from subtree has to be identified by an instance identifier starting from: %s."
+                    + "Instance identifier was: %s", getManagedDataObjectType().getTargetType(), handledType);
+            checkArgument(Iterables.size(handledType.getPathArguments()) > 1,
+                    "Handled node from subtree identifier too short: %s", handledType);
+            handledChildTypes.add(InstanceIdentifier.create(Iterables.concat(
+                    getManagedDataObjectType().getPathArguments(), Iterables.skip(handledType.getPathArguments(), 1))));
+        }
+    }
+
+    /**
+     * Return set of types also handled by this writer. All of the types are children of the type managed by this
+     * writer excluding the type of this writer.
+     */
+    Set<InstanceIdentifier<?>> getHandledChildTypes() {
+        return handledChildTypes;
+    }
+
+    @Override
+    public void update(
+            @Nonnull final InstanceIdentifier<? extends DataObject> id,
+            @Nullable final DataObject dataBefore,
+            @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx) throws WriteFailedException {
+        delegate.update(id, dataBefore, dataAfter, ctx);
+    }
+
+    @Override
+    @Nonnull
+    public InstanceIdentifier<D> getManagedDataObjectType() {
+        return delegate.getManagedDataObjectType();
+    }
+
+    /**
+     * Wrap a writer as a subtree writer.
+     */
+    static Writer<?> createForWriter(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
+                                     @Nonnull final Writer<? extends DataObject> writer) {
+        return new SubtreeWriter<>(writer, handledChildren);
+    }
+}
index 0eb5062..fccd6b1 100644 (file)
@@ -42,6 +42,8 @@ public class DelegatingReaderRegistryModule extends org.opendaylight.yang.gen.v1
         return new CloseableReaderRegistry(new DelegatingReaderRegistry(rootReadersDependency));
     }
 
+
+
     // TODO move to translate-utils
     private static final class CloseableReaderRegistry implements ReaderRegistry, AutoCloseable {
         private final DelegatingReaderRegistry delegatingReaderRegistry;
index 214ab0f..24d6c50 100644 (file)
@@ -8,6 +8,13 @@
 * Do not modify this file unless it is present under src/main directory
 */
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406;
+
+import org.opendaylight.controller.config.api.DynamicMBeanWithInstance;
+
 public class DelegatingReaderRegistryModuleFactory extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406.AbstractDelegatingReaderRegistryModuleFactory {
 
+    @Override
+    public DelegatingReaderRegistryModule handleChangedClass(final DynamicMBeanWithInstance old) throws Exception {
+        return super.handleChangedClass(old);
+    }
 }
index 0266ca9..7eadde8 100644 (file)
@@ -1,12 +1,6 @@
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Lists;
-import io.fd.honeycomb.v3po.translate.util.write.CloseableWriterRegistry;
-import io.fd.honeycomb.v3po.translate.util.write.DelegatingWriterRegistry;
-import io.fd.honeycomb.v3po.translate.write.Writer;
-import java.util.List;
-import org.opendaylight.yangtools.yang.binding.DataObject;
+import io.fd.honeycomb.v3po.translate.util.write.registry.FlatWriterRegistryBuilder;
 
 public class DelegatingWriterRegistryModule extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406.AbstractDelegatingWriterRegistryModule {
     public DelegatingWriterRegistryModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
@@ -24,16 +18,9 @@ public class DelegatingWriterRegistryModule extends org.opendaylight.yang.gen.v1
 
     @Override
     public java.lang.AutoCloseable createInstance() {
-        final List<Writer<? extends DataObject>> rootReadersDependency = Lists.transform(getRootWritersDependency(),
-            new Function<Writer, Writer<? extends DataObject>>() {
-
-                @SuppressWarnings("unchecked")
-                @Override
-                public Writer<? extends DataObject> apply(final Writer input) {
-                    return input;
-                }
-            });
-        return new CloseableWriterRegistry(new DelegatingWriterRegistry(rootReadersDependency));
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        getWriterFactoryDependency().forEach(writerFactory -> writerFactory.init(flatWriterRegistryBuilder));
+        return flatWriterRegistryBuilder;
     }
 
 }
index 16c8af3..fedd069 100644 (file)
@@ -1,7 +1,8 @@
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406;
 
-import io.fd.honeycomb.v3po.translate.util.write.*;
 import io.fd.honeycomb.v3po.translate.util.write.NoopWriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.WriterRegistryBuilder;
 
 public class NoopWriterRegistryModule extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.translate.utils.rev160406.AbstractNoopWriterRegistryModule {
     public NoopWriterRegistryModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
@@ -19,7 +20,19 @@ public class NoopWriterRegistryModule extends org.opendaylight.yang.gen.v1.urn.h
 
     @Override
     public java.lang.AutoCloseable createInstance() {
-        return new CloseableWriterRegistry(new NoopWriterRegistry());
+        return new NoopWriterRegistryBuilder();
     }
 
+    private static final class NoopWriterRegistryBuilder implements AutoCloseable, WriterRegistryBuilder {
+
+        @Override
+        public WriterRegistry build() {
+            return new NoopWriterRegistry();
+        }
+
+        @Override
+        public void close() throws Exception {
+            // Noop
+        }
+    }
 }
index 1219648..2c00bb5 100644 (file)
@@ -40,6 +40,7 @@ module translate-utils {
     identity delegating-writer-registry {
         base config:module-type;
         config:provided-service tapi:honeycomb-writer-registry;
+        config:provided-service tapi:honeycomb-writer-registry-builder;
         config:java-name-prefix DelegatingWriterRegistry;
     }
 
@@ -47,11 +48,11 @@ module translate-utils {
         case delegating-writer-registry {
             when "/config:modules/config:module/config:type = 'delegating-writer-registry'";
 
-                list root-writers {
+                list writer-factory {
                     uses config:service-ref {
                         refine type {
                             mandatory true;
-                            config:required-identity tapi:honeycomb-writer;
+                            config:required-identity tapi:honeycomb-writer-factory;
                         }
                     }
                 }
@@ -59,15 +60,15 @@ module translate-utils {
         }
     }
 
-    identity noop-writer-registry {
+    identity noop-writer-registry-builder {
         base config:module-type;
-        config:provided-service tapi:honeycomb-writer-registry;
+        config:provided-service tapi:honeycomb-writer-registry-builder;
         config:java-name-prefix NoopWriterRegistry;
     }
 
     augment "/config:modules/config:module/config:configuration" {
-        case noop-writer-registry {
-            when "/config:modules/config:module/config:type = 'noop-writer-registry'";
+        case noop-writer-registry-builder {
+            when "/config:modules/config:module/config:type = 'noop-writer-registry-builder'";
         }
     }
 
diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java
deleted file mode 100644 (file)
index f51e49d..0000000
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.impl.write.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import io.fd.honeycomb.v3po.translate.util.write.DelegatingWriterRegistry;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.write.WriteFailedException;
-import io.fd.honeycomb.v3po.translate.write.Writer;
-import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppState;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-public class DelegatingWriterRegistryTest {
-
-    private final InstanceIdentifier<Vpp> vppId;
-    private final InstanceIdentifier<VppState> vppStateId;
-    private final InstanceIdentifier<Interfaces> interfaceId;
-
-    private WriteContext ctx;
-    private Writer<Vpp> writer;
-    private Writer<VppState> vppStateWriter;
-    private Writer<Interfaces> interfacesWriter;
-
-    private DelegatingWriterRegistry registry;
-
-    public DelegatingWriterRegistryTest() {
-        vppId = InstanceIdentifier.create(Vpp.class);
-        vppStateId = InstanceIdentifier.create(VppState.class);
-        interfaceId = InstanceIdentifier.create(Interfaces.class);
-    }
-
-    @SuppressWarnings("unchecked")
-    private <D extends DataObject> Writer<D> mockWriter(Class<D> clazz) {
-        final Writer<D> mock = (Writer<D>) Mockito.mock(Writer.class);
-        doReturn(InstanceIdentifier.create(clazz)).when(mock).getManagedDataObjectType();
-        return mock;
-    }
-
-    private DataObject mockDataObject(final String name, final Class<? extends DataObject> classToMock) {
-        final DataObject dataBefore = mock(classToMock, name);
-        doReturn(classToMock).when(dataBefore).getImplementedInterface();
-        return dataBefore;
-    }
-
-    @SuppressWarnings("unchecked")
-    private static Map<InstanceIdentifier<?>, DataObject> asMap(DataObject... objects) {
-        final Map<InstanceIdentifier<?>, DataObject> map = new HashMap<>();
-        for (DataObject object : objects) {
-            final Class<? extends DataObject> implementedInterface =
-                    (Class<? extends DataObject>) object.getImplementedInterface();
-            final InstanceIdentifier<?> id = InstanceIdentifier.create(implementedInterface);
-            map.put(id, object);
-        }
-        return map;
-    }
-
-    @Before
-    public void setUp() {
-        ctx = mock(WriteContext.class);
-        writer = mockWriter(Vpp.class);
-        vppStateWriter = mockWriter(VppState.class);
-        interfacesWriter = mockWriter(Interfaces.class);
-
-        final List<Writer<? extends DataObject>> writers = new ArrayList<>();
-        writers.add(writer);
-        writers.add(vppStateWriter);
-        writers.add(interfacesWriter);
-
-        registry = new DelegatingWriterRegistry(writers);
-    }
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testGetManagedDataObjectType() {
-        registry.getManagedDataObjectType();
-    }
-
-    @Test
-    public void testBulkUpdateRevert() throws Exception {
-        // Prepare data changes:
-        final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class);
-        final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class);
-
-        final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class);
-        final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class);
-
-        // Fail on update
-        Mockito.doThrow(new WriteFailedException(InstanceIdentifier.create(Vpp.class), "vpp failed"))
-            .when(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx);
-
-        // Run the test
-        try {
-            registry.update(asMap(dataBefore1, dataBefore2), asMap(dataAfter1, dataAfter2), ctx);
-        } catch (WriterRegistry.BulkUpdateException e) {
-            // Check second update failed
-            assertEquals(vppStateId, e.getFailedId());
-            verify(writer).update(vppId, dataBefore1, dataAfter1, ctx);
-            verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx);
-
-            // Try to revert changes
-            e.revertChanges();
-
-            // Check revert was successful
-            verify(writer).update(vppId, dataAfter1, dataBefore1, ctx);
-            verify(vppStateWriter, never()).update(vppStateId, dataAfter2, dataBefore2, ctx);
-
-            return;
-        }
-        fail("BulkUpdateException expected");
-    }
-
-    @Test
-    public void testBulkUpdateRevertFail() throws Exception {
-        // Prepare data changes:
-        final DataObject dataBefore1 = mockDataObject("Vpp before", Vpp.class);
-        final DataObject dataAfter1 = mockDataObject("Vpp after", Vpp.class);
-
-        final DataObject dataBefore2 = mockDataObject("VppState before", VppState.class);
-        final DataObject dataAfter2 = mockDataObject("VppState after", VppState.class);
-
-        final DataObject dataBefore3 = mockDataObject("Interfaces before", Interfaces.class);
-        final DataObject dataAfter3 = mockDataObject("Interfaces after", Interfaces.class);
-
-        // Fail on the third update
-        doThrow(new WriteFailedException(InstanceIdentifier.create(Vpp.class), "vpp failed")).when(interfacesWriter)
-                .update(interfaceId, dataBefore3, dataAfter3, ctx);
-
-        // Fail on the second revert
-        doThrow(new WriteFailedException(InstanceIdentifier.create(Vpp.class), "vpp failed")).when(writer)
-                .update(vppId, dataAfter1, dataBefore1, ctx);
-
-        // Run the test
-        try {
-            registry.update(asMap(dataBefore1, dataBefore2, dataBefore3), asMap(dataAfter1, dataAfter2, dataAfter3), ctx);
-        } catch (WriterRegistry.BulkUpdateException e) {
-            // Check third update failed
-            assertEquals(interfaceId, e.getFailedId());
-            verify(writer).update(vppId, dataBefore1, dataAfter1, ctx);
-            verify(vppStateWriter).update(vppStateId, dataBefore2, dataAfter2, ctx);
-            verify(interfacesWriter).update(interfaceId, dataBefore3, dataAfter3, ctx);
-
-            // Try to revert changes
-            try {
-                e.revertChanges();
-            } catch (WriterRegistry.Reverter.RevertFailedException e2) {
-                // Check second revert failed
-                assertEquals(Collections.singletonList(vppId), e2.getNotRevertedChanges());
-                verify(writer).update(vppId, dataAfter1, dataBefore1, ctx);
-                verify(vppStateWriter).update(vppStateId, dataAfter2, dataBefore2, ctx);
-                verify(interfacesWriter, never()).update(interfaceId, dataAfter3, dataBefore3, ctx);
-                return;
-            }
-            fail("WriterRegistry.Revert.RevertFailedException expected");
-        }
-        fail("BulkUpdateException expected");
-    }
-}
\ No newline at end of file
diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java
new file mode 100644 (file)
index 0000000..ec407b6
--- /dev/null
@@ -0,0 +1,156 @@
+package io.fd.honeycomb.v3po.translate.util.write.registry;
+
+import static org.hamcrest.CoreMatchers.anyOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class FlatWriterRegistryBuilderTest {
+
+
+    @Test
+    public void testRelationsBefore() throws Exception {
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        /*
+            1   ->  2   ->  3
+                        ->  4
+         */
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject3.class));
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject4.class));
+        flatWriterRegistryBuilder.addWriterBefore(mockWriter(DataObject2.class),
+                Lists.newArrayList(DataObject3.IID, DataObject4.IID));
+        flatWriterRegistryBuilder.addWriterBefore(mockWriter(DataObject1.class), DataObject2.IID);
+        final ImmutableMap<InstanceIdentifier<?>, Writer<?>> mappedWriters =
+                flatWriterRegistryBuilder.getMappedWriters();
+
+        final ArrayList<InstanceIdentifier<?>> typesInList = Lists.newArrayList(mappedWriters.keySet());
+        assertEquals(DataObject1.IID, typesInList.get(0));
+        assertEquals(DataObject2.IID, typesInList.get(1));
+        assertThat(typesInList.get(2), anyOf(equalTo(DataObject3.IID), equalTo(DataObject4.IID)));
+        assertThat(typesInList.get(3), anyOf(equalTo(DataObject3.IID), equalTo(DataObject4.IID)));
+    }
+
+    @Test
+    public void testRelationsAfter() throws Exception {
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        /*
+            1   ->  2   ->  3
+                        ->  4
+         */
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject1.class));
+        flatWriterRegistryBuilder.addWriterAfter(mockWriter(DataObject2.class), DataObject1.IID);
+        flatWriterRegistryBuilder.addWriterAfter(mockWriter(DataObject3.class), DataObject2.IID);
+        flatWriterRegistryBuilder.addWriterAfter(mockWriter(DataObject4.class), DataObject2.IID);
+        final ImmutableMap<InstanceIdentifier<?>, Writer<?>> mappedWriters =
+                flatWriterRegistryBuilder.getMappedWriters();
+
+        final List<InstanceIdentifier<?>> typesInList = Lists.newArrayList(mappedWriters.keySet());
+        assertEquals(DataObject1.IID, typesInList.get(0));
+        assertEquals(DataObject2.IID, typesInList.get(1));
+        assertThat(typesInList.get(2), anyOf(equalTo(DataObject3.IID), equalTo(DataObject4.IID)));
+        assertThat(typesInList.get(3), anyOf(equalTo(DataObject3.IID), equalTo(DataObject4.IID)));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRelationsLoop() throws Exception {
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        /*
+            1   ->  2   ->  1
+         */
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject1.class));
+        flatWriterRegistryBuilder.addWriterAfter(mockWriter(DataObject2.class), DataObject1.IID);
+        flatWriterRegistryBuilder.addWriterAfter(mockWriter(DataObject1.class), DataObject2.IID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAddWriterTwice() throws Exception {
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject1.class));
+        flatWriterRegistryBuilder.addWriter(mockWriter(DataObject1.class));
+    }
+
+    @Test
+    public void testAddSubtreeWriter() throws Exception {
+        final FlatWriterRegistryBuilder flatWriterRegistryBuilder = new FlatWriterRegistryBuilder();
+        flatWriterRegistryBuilder.addSubtreeWriter(
+                Sets.newHashSet(DataObject4.DataObject5.IID,
+                                DataObject4.DataObject5.IID),
+                mockWriter(DataObject4.class));
+        final ImmutableMap<InstanceIdentifier<?>, Writer<?>> mappedWriters =
+                flatWriterRegistryBuilder.getMappedWriters();
+        final ArrayList<InstanceIdentifier<?>> typesInList = Lists.newArrayList(mappedWriters.keySet());
+
+        assertEquals(DataObject4.IID, typesInList.get(0));
+        assertEquals(1, typesInList.size());
+    }
+
+    @Test
+    public void testCreateSubtreeWriter() throws Exception {
+        final Writer<?> forWriter = SubtreeWriter.createForWriter(Sets.newHashSet(
+                DataObject4.DataObject5.IID,
+                DataObject4.DataObject5.DataObject51.IID,
+                DataObject4.DataObject6.IID),
+                mockWriter(DataObject4.class));
+        assertThat(forWriter, instanceOf(SubtreeWriter.class));
+        assertThat(((SubtreeWriter<?>) forWriter).getHandledChildTypes().size(), is(3));
+        assertThat(((SubtreeWriter<?>) forWriter).getHandledChildTypes(), hasItems(DataObject4.DataObject5.IID,
+                DataObject4.DataObject6.IID, DataObject4.DataObject5.DataObject51.IID));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testCreateInvalidSubtreeWriter() throws Exception {
+        SubtreeWriter.createForWriter(Sets.newHashSet(
+                InstanceIdentifier.create(DataObject3.class).child(DataObject3.DataObject31.class)),
+                mockWriter(DataObject4.class));
+    }
+
+    @SuppressWarnings("unchecked")
+    private Writer<? extends DataObject> mockWriter(final Class<? extends DataObject> doClass)
+            throws NoSuchFieldException, IllegalAccessException {
+        final Writer mock = mock(Writer.class);
+        when(mock.getManagedDataObjectType()).thenReturn((InstanceIdentifier<?>) doClass.getDeclaredField("IID").get(null));
+        return mock;
+    }
+
+    private abstract static class DataObject1 implements DataObject {
+        static InstanceIdentifier<DataObject1> IID = InstanceIdentifier.create(DataObject1.class);
+    }
+    private abstract static class DataObject2 implements DataObject {
+        static InstanceIdentifier<DataObject2> IID = InstanceIdentifier.create(DataObject2.class);
+    }
+    private abstract static class DataObject3 implements DataObject {
+        static InstanceIdentifier<DataObject3> IID = InstanceIdentifier.create(DataObject3.class);
+        private abstract static class DataObject31 implements DataObject, ChildOf<DataObject3> {
+            static InstanceIdentifier<DataObject31> IID = DataObject3.IID.child(DataObject31.class);
+        }
+    }
+    private abstract static class DataObject4 implements DataObject {
+        static InstanceIdentifier<DataObject4> IID = InstanceIdentifier.create(DataObject4.class);
+        private abstract static class DataObject5 implements DataObject, ChildOf<DataObject4> {
+            static InstanceIdentifier<DataObject5> IID = DataObject4.IID.child(DataObject5.class);
+            private abstract static class DataObject51 implements DataObject, ChildOf<DataObject5> {
+                static InstanceIdentifier<DataObject51> IID = DataObject5.IID.child(DataObject51.class);
+            }
+        }
+        private abstract static class DataObject6 implements DataObject, ChildOf<DataObject4> {
+            static InstanceIdentifier<DataObject6> IID = DataObject4.IID.child(DataObject6.class);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java
new file mode 100644 (file)
index 0000000..7fb5779
--- /dev/null
@@ -0,0 +1,295 @@
+package io.fd.honeycomb.v3po.translate.util.write.registry;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Multimap;
+import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate;
+import io.fd.honeycomb.v3po.translate.write.WriteContext;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import io.fd.honeycomb.v3po.translate.write.WriterRegistry;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class FlatWriterRegistryTest {
+
+    @Mock
+    private Writer<DataObject1> writer1;
+    @Mock
+    private Writer<DataObject2> writer2;
+    @Mock
+    private Writer<DataObject3> writer3;
+    @Mock
+    private WriteContext ctx;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(writer1.getManagedDataObjectType()).thenReturn(DataObject1.IID);
+        when(writer2.getManagedDataObjectType()).thenReturn(DataObject2.IID);
+        when(writer3.getManagedDataObjectType()).thenReturn(DataObject3.IID);
+    }
+
+    @Test
+    public void testSingleUpdate() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1));
+
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 before = mock(DataObject1.class);
+        final DataObject1 after = mock(DataObject1.class);
+        flatWriterRegistry.update(iid, before, after, ctx);
+
+        verify(writer1).update(iid, before, after, ctx);
+    }
+
+    @Test
+    public void testMultipleUpdatesForSingleWriter() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final InstanceIdentifier<DataObject1> iid2 = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 dataObject = mock(DataObject1.class);
+        updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
+        updates.put(DataObject1.IID, DataObjectUpdate.create(iid2, dataObject, dataObject));
+        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+
+        verify(writer1).update(iid, dataObject, dataObject, ctx);
+        verify(writer1).update(iid2, dataObject, dataObject, ctx);
+        // Invoked when registry is being created
+        verifyNoMoreInteractions(writer1);
+        verifyZeroInteractions(writer2);
+    }
+
+    @Test
+    public void testMultipleUpdatesForMultipleWriters() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 dataObject = mock(DataObject1.class);
+        updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
+        final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
+        final DataObject2 dataObject2 = mock(DataObject2.class);
+        updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
+        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+
+        final InOrder inOrder = inOrder(writer1, writer2);
+        inOrder.verify(writer1).update(iid, dataObject, dataObject, ctx);
+        inOrder.verify(writer2).update(iid2, dataObject2, dataObject2, ctx);
+
+        verifyNoMoreInteractions(writer1);
+        verifyNoMoreInteractions(writer2);
+    }
+
+    @Test
+    public void testMultipleDeletesForMultipleWriters() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create();
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 dataObject = mock(DataObject1.class);
+        deletes.put(DataObject1.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null)));
+        final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
+        final DataObject2 dataObject2 = mock(DataObject2.class);
+        deletes.put(DataObject2.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid2, dataObject2, null)));
+        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(ImmutableMultimap.of(), deletes), ctx);
+
+        final InOrder inOrder = inOrder(writer1, writer2);
+        // Reversed order of invocation, first writer2 and then writer1
+        inOrder.verify(writer2).update(iid2, dataObject2, null, ctx);
+        inOrder.verify(writer1).update(iid, dataObject, null, ctx);
+
+        verifyNoMoreInteractions(writer1);
+        verifyNoMoreInteractions(writer2);
+    }
+
+    @Test
+    public void testMultipleUpdatesAndDeletesForMultipleWriters() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create();
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 dataObject = mock(DataObject1.class);
+        // Writer 1 delete
+        deletes.put(DataObject1.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null)));
+        // Writer 1 update
+        updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
+        final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
+        final DataObject2 dataObject2 = mock(DataObject2.class);
+        // Writer 2 delete
+        deletes.put(DataObject2.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid2, dataObject2, null)));
+        // Writer 2 update
+        updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
+        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, deletes), ctx);
+
+        final InOrder inOrder = inOrder(writer1, writer2);
+        // Reversed order of invocation, first writer2 and then writer1 for deletes
+        inOrder.verify(writer2).update(iid2, dataObject2, null, ctx);
+        inOrder.verify(writer1).update(iid, dataObject, null, ctx);
+        // Then also updates are processed
+        inOrder.verify(writer1).update(iid, dataObject, dataObject, ctx);
+        inOrder.verify(writer2).update(iid2, dataObject2, dataObject2, ctx);
+
+        verifyNoMoreInteractions(writer1);
+        verifyNoMoreInteractions(writer2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMultipleUpdatesOneMissing() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        addUpdate(updates, DataObject1.class);
+        addUpdate(updates, DataObject2.class);
+        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+    }
+
+    @Test
+    public void testMultipleUpdatesOneFailing() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+        // Writer1 always fails
+        doThrow(new RuntimeException()).when(writer1)
+                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        addUpdate(updates, DataObject1.class);
+        addUpdate(updates, DataObject2.class);
+
+        try {
+            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            fail("Bulk update should have failed on writer1");
+        } catch (WriterRegistry.BulkUpdateException e) {
+            assertThat(e.getFailedIds().size(), is(2));
+            assertThat(e.getFailedIds(), hasItem(InstanceIdentifier.create(DataObject2.class)));
+            assertThat(e.getFailedIds(), hasItem(InstanceIdentifier.create(DataObject1.class)));
+        }
+    }
+
+    @Test
+    public void testMultipleUpdatesOneFailingThenRevertWithSuccess() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(
+                        ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2, DataObject3.IID, writer3));
+
+        // Writer1 always fails
+        doThrow(new RuntimeException()).when(writer3)
+                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        addUpdate(updates, DataObject1.class);
+        addUpdate(updates, DataObject3.class);
+        final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
+        final DataObject2 before2 = mock(DataObject2.class);
+        final DataObject2 after2 = mock(DataObject2.class);
+        updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, before2, after2));
+
+        try {
+            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            fail("Bulk update should have failed on writer1");
+        } catch (WriterRegistry.BulkUpdateException e) {
+            assertThat(e.getFailedIds().size(), is(1));
+
+            final InOrder inOrder = inOrder(writer1, writer2, writer3);
+            inOrder.verify(writer1)
+                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+            inOrder.verify(writer2)
+                .update(iid2, before2, after2, ctx);
+            inOrder.verify(writer3)
+                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+
+            e.revertChanges();
+            // Revert changes. Successful updates are iterated in reverse
+            inOrder.verify(writer2)
+                    .update(iid2, after2, before2, ctx);
+            inOrder.verify(writer1)
+                    .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+            verifyNoMoreInteractions(writer3);
+        }
+    }
+
+    @Test
+    public void testMultipleUpdatesOneFailingThenRevertWithFail() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(
+                        ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2, DataObject3.IID, writer3));
+
+        // Writer1 always fails
+        doThrow(new RuntimeException()).when(writer3)
+                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+
+        final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+        addUpdate(updates, DataObject1.class);
+        addUpdate(updates, DataObject2.class);
+        addUpdate(updates, DataObject3.class);
+
+        try {
+            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            fail("Bulk update should have failed on writer1");
+        } catch (WriterRegistry.BulkUpdateException e) {
+            // Writer1 always fails from now
+            doThrow(new RuntimeException()).when(writer1)
+                    .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+            try {
+                e.revertChanges();
+            } catch (WriterRegistry.Reverter.RevertFailedException e1) {
+                assertThat(e1.getNotRevertedChanges().size(), is(1));
+                assertThat(e1.getNotRevertedChanges(), hasItem(InstanceIdentifier.create(DataObject1.class)));
+            }
+        }
+    }
+
+    private <D extends DataObject> void addUpdate(final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
+                           final Class<D> type) throws Exception {
+        final InstanceIdentifier<D> iid = (InstanceIdentifier<D>) type.getDeclaredField("IID").get(null);
+        updates.put(iid, DataObjectUpdate.create(iid, mock(type), mock(type)));
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testSingleUpdateMissingWriter() throws Exception {
+        final FlatWriterRegistry flatWriterRegistry =
+                new FlatWriterRegistry(ImmutableMap.of());
+
+        final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+        final DataObject1 before = mock(DataObject1.class);
+        final DataObject1 after = mock(DataObject1.class);
+        flatWriterRegistry.update(iid, before, after, ctx);
+    }
+
+    private abstract static class DataObject1 implements DataObject {
+        static final InstanceIdentifier<DataObject1> IID = InstanceIdentifier.create(DataObject1.class);
+    }
+    private abstract static class DataObject2 implements DataObject {
+        static final InstanceIdentifier<DataObject2> IID = InstanceIdentifier.create(DataObject2.class);
+    }
+    private abstract static class DataObject3 implements DataObject {
+        static final InstanceIdentifier<DataObject3> IID = InstanceIdentifier.create(DataObject3.class);
+    }
+}
\ No newline at end of file
diff --git a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java
new file mode 100644 (file)
index 0000000..b7dcadc
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016 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.v3po.translate.util.write.registry;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Sets;
+import io.fd.honeycomb.v3po.translate.write.Writer;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class SubtreeWriterTest {
+
+    @Mock
+    Writer<DataObject1> writer;
+    @Mock
+    Writer<DataObject1.DataObject11> writer11;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(writer.getManagedDataObjectType()).thenReturn(DataObject1.IID);
+        when(writer11.getManagedDataObjectType()).thenReturn(DataObject1.DataObject11.IID);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubtreeWriterCreationFail() throws Exception {
+        // The subtree node identified by IID.c(DataObject.class) is not a child of writer.getManagedDataObjectType
+        SubtreeWriter.createForWriter(Collections.singleton(InstanceIdentifier.create(DataObject.class)), writer);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubtreeWriterCreationFailInvalidIid() throws Exception {
+        // The subtree node identified by IID.c(DataObject.class) is not a child of writer.getManagedDataObjectType
+        SubtreeWriter.createForWriter(Collections.singleton(DataObject1.IID), writer);
+    }
+
+    @Test
+    public void testSubtreeWriterCreation() throws Exception {
+        final SubtreeWriter<?> forWriter = (SubtreeWriter<?>) SubtreeWriter.createForWriter(Sets.newHashSet(
+                DataObject1.DataObject11.IID,
+                DataObject1.DataObject11.DataObject111.IID,
+                DataObject1.DataObject12.IID),
+                writer);
+
+        assertEquals(writer.getManagedDataObjectType(), forWriter.getManagedDataObjectType());
+        assertEquals(3, forWriter.getHandledChildTypes().size());
+    }
+
+    @Test
+    public void testSubtreeWriterHandledTypes() throws Exception {
+        final SubtreeWriter<?> forWriter = (SubtreeWriter<?>) SubtreeWriter.createForWriter(Sets.newHashSet(
+                DataObject1.DataObject11.DataObject111.IID),
+                writer);
+
+        assertEquals(writer.getManagedDataObjectType(), forWriter.getManagedDataObjectType());
+        assertEquals(1, forWriter.getHandledChildTypes().size());
+        assertThat(forWriter.getHandledChildTypes(), hasItem(DataObject1.DataObject11.DataObject111.IID));
+    }
+
+    private abstract static class DataObject1 implements DataObject {
+        static InstanceIdentifier<DataObject1> IID = InstanceIdentifier.create(DataObject1.class);
+        private abstract static class DataObject11 implements DataObject, ChildOf<DataObject1> {
+            static InstanceIdentifier<DataObject11> IID = DataObject1.IID.child(DataObject11.class);
+            private abstract static class DataObject111 implements DataObject, ChildOf<DataObject11> {
+                static InstanceIdentifier<DataObject111> IID = DataObject11.IID.child(DataObject111.class);
+            }
+        }
+        private abstract static class DataObject12 implements DataObject, ChildOf<DataObject1> {
+            static InstanceIdentifier<DataObject12> IID = DataObject1.IID.child(DataObject12.class);
+        }
+    }
+}
\ No newline at end of file
index 1ee1775..6b487a1 100644 (file)
                 <module>
                     <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:utils">prefix:delegating-writer-registry</type>
                     <name>write-registry</name>
-                    <root-writers>
-                        <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer</type>
+                    <writer-factory>
+                        <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-factory</type>
                         <name>vpp-honeycomb-writer</name>
-                    </root-writers>
-                    <root-writers>
-                        <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer</type>
+                    </writer-factory>
+                    <writer-factory>
+                        <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-factory</type>
                         <name>interfaces-honeycomb-writer</name>
-                    </root-writers>
+                    </writer-factory>
                 </module>
             </modules>
 
                     </instance>
                 </service>
                 <service>
-                    <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer</type>
+                    <type xmlns:prefix="urn:honeycomb:params:xml:ns:yang:translate:api">prefix:honeycomb-writer-factory</type>
                     <instance>
                         <name>vpp-honeycomb-writer</name>
                         <provider>/modules/module[type='vpp-honeycomb-writer'][name='vpp-honeycomb-writer']
index d87370a..8cc740a 100644 (file)
@@ -1,21 +1,24 @@
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import io.fd.honeycomb.v3po.translate.impl.TraversalType;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.util.write.CloseableWriter;
-import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.write.ReflexiveAugmentWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.v3po.interfaces.*;
+import com.google.common.collect.Sets;
+import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter;
+import io.fd.honeycomb.v3po.translate.impl.write.GenericWriter;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.EthernetCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.InterfaceCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.L2Customizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.RoutingCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.TapCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.VhostUserCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.VxlanCustomizer;
+import io.fd.honeycomb.v3po.translate.v3po.interfaces.VxlanGpeCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4AddressCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4Customizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4NeighbourCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv6Customizer;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
+import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.WriterFactory;
+import java.util.Set;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.Interface1;
@@ -24,15 +27,18 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev14061
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Neighbor;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceAugmentation;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.*;
-import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
-
-import java.util.ArrayList;
-import java.util.List;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Routing;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Tap;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.VhostUser;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Vxlan;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.VxlanGpe;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.jvpp.future.FutureJVpp;
 
 public class InterfacesHoneycombWriterModule extends
-    org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractInterfacesHoneycombWriterModule {
+        org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractInterfacesHoneycombWriterModule {
     public InterfacesHoneycombWriterModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
                                            org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
         super(identifier, dependencyResolver);
@@ -52,91 +58,98 @@ public class InterfacesHoneycombWriterModule extends
 
     @Override
     public java.lang.AutoCloseable createInstance() {
-
-        final List<ChildWriter<? extends Augmentation<Interface>>> ifcAugmentations = Lists.newArrayList();
-        ifcAugmentations.add(getVppIfcAugmentationWriter());
-        ifcAugmentations.add(getInterface1AugmentationWriter());
-        ifcAugmentations.add(
-            SubinterfaceAugmentationWriterFactory.createInstance(getVppJvppIfcDependency(), getInterfaceContextDependency(),
-                getBridgeDomainContextDependency()));
-
-        final ChildWriter<Interface> interfaceWriter = new CompositeListWriter<>(Interface.class,
-            RWUtils.emptyChildWriterList(),
-            ifcAugmentations,
-            new InterfaceCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()),
-            // It's important that this customizer is handled in a postorder way, because you first have to handle child nodes
-            // e.g. Vxlan before setting other interface or vppInterfaceAugmentation leaves
-            TraversalType.POSTORDER);
-
-        final List<ChildWriter<? extends ChildOf<Interfaces>>> childWriters = new ArrayList<>();
-        childWriters.add(interfaceWriter);
-
-        // FIXME if we just return the root writer and cfg subsystem takes care to set it into reader registry,
-        // we loose the ordering information for root writers
-        // Or can we rely to the order in which readers are configured ?
-        return new CloseableWriter<>(new CompositeRootWriter<>(Interfaces.class,
-            childWriters, new NoopWriterCustomizer<>()));
+        return new InterfacesWriterFactory(getVppJvppIfcDependency(),
+                                           getBridgeDomainContextDependency(),
+                                           getInterfaceContextDependency());
     }
 
-    private ChildWriter<? extends Augmentation<Interface>> getInterface1AugmentationWriter() {
-
-        final ChildWriter<Neighbor> neighborWriter = new CompositeListWriter<>(Neighbor.class,
-                new Ipv4NeighbourCustomizer(getVppJvppIfcDependency(),getInterfaceContextDependency()));
-
-        final ChildWriter<Address> addressWriter = new CompositeListWriter<>(Address.class,
-            new Ipv4AddressCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
 
-        final ChildWriter<Ipv4> ipv4Writer = new CompositeChildWriter<>(Ipv4.class,
-                ImmutableList.of(neighborWriter,addressWriter),
-                new Ipv4Customizer(getVppJvppIfcDependency(),getInterfaceContextDependency()));
-        final ChildWriter<Ipv6> ipv6Writer = new CompositeChildWriter<>(Ipv6.class,
-            new Ipv6Customizer(getVppJvppIfcDependency()));
+    private static class InterfacesWriterFactory implements WriterFactory, AutoCloseable {
+
+        private final FutureJVpp jvpp;
+        private final NamingContext bdContext;
+        private final NamingContext ifcContext;
+
+        InterfacesWriterFactory(final FutureJVpp vppJvppIfcDependency,
+                                       final NamingContext bridgeDomainContextDependency,
+                                       final NamingContext interfaceContextDependency) {
+            this.jvpp = vppJvppIfcDependency;
+            this.bdContext = bridgeDomainContextDependency;
+            this.ifcContext = interfaceContextDependency;
+        }
+
+        @Override
+        public void close() throws Exception {
+            // unregister is not supported in ModifiableWriterRegistry (not really needed though)
+        }
+
+        @Override
+        public void init(final ModifiableWriterRegistry registry) {
+            // Interfaces
+            //  Interface =
+            final InstanceIdentifier<Interface> ifcId = InstanceIdentifier.create(Interfaces.class).child(Interface.class);
+            registry.addWriter(new GenericListWriter<>(ifcId, new InterfaceCustomizer(jvpp, ifcContext)));
+            //   VppInterfaceAugmentation
+            addVppInterfaceAgmentationWriters(ifcId, registry);
+            //   Interface1 (ietf-ip augmentation)
+            addInterface1AugmentationWriters(ifcId, registry);
+            //   SubinterfaceAugmentation TODO make dedicated module for subIfc writer factory
+            new SubinterfaceAugmentationWriterFactory(ifcId, jvpp, ifcContext, bdContext).init(registry);
+        }
+
+        private void addInterface1AugmentationWriters(final InstanceIdentifier<Interface> ifcId,
+                                                      final ModifiableWriterRegistry registry) {
+            final InstanceIdentifier<Interface1> ifc1AugId = ifcId.augmentation(Interface1.class);
+            // Ipv6(after interface) TODO unfinished customizer =
+            registry.addWriterAfter(new GenericWriter<>(ifc1AugId.child(Ipv6.class), new Ipv6Customizer(jvpp)),
+                    ifcId);
+            // Ipv4(after interface)
+            final InstanceIdentifier<Ipv4> ipv4Id = ifc1AugId.child(Ipv4.class);
+            registry.addWriterAfter(new GenericWriter<>(ipv4Id, new Ipv4Customizer(jvpp, ifcContext)),
+                    ifcId);
+            //  Address(after Ipv4) =
+            final InstanceIdentifier<Address> ipv4AddressId = ipv4Id.child(Address.class);
+            registry.addWriterAfter(new GenericListWriter<>(ipv4AddressId, new Ipv4AddressCustomizer(jvpp, ifcContext)),
+                    ipv4Id);
+            //  Neighbor(after ipv4Address)
+            registry.addWriterAfter(new GenericListWriter<>(ipv4Id.child(Neighbor.class), new Ipv4NeighbourCustomizer(jvpp, ifcContext)),
+                    ipv4AddressId);
+        }
+
+        private void addVppInterfaceAgmentationWriters(final InstanceIdentifier<Interface> ifcId,
+                                                       final ModifiableWriterRegistry registry) {
+            final InstanceIdentifier<VppInterfaceAugmentation> vppIfcAugId = ifcId.augmentation(VppInterfaceAugmentation.class);
+            // VhostUser(Needs to be executed before Interface customizer) =
+            final InstanceIdentifier<VhostUser> vhostId = vppIfcAugId.child(VhostUser.class);
+            registry.addWriterBefore(new GenericWriter<>(vhostId, new VhostUserCustomizer(jvpp, ifcContext)),
+                    ifcId);
+            // Vxlan(Needs to be executed before Interface customizer) =
+            final InstanceIdentifier<Vxlan> vxlanId = vppIfcAugId.child(Vxlan.class);
+            registry.addWriterBefore(new GenericWriter<>(vxlanId, new VxlanCustomizer(jvpp, ifcContext)),
+                    ifcId);
+            // VxlanGpe(Needs to be executed before Interface customizer) =
+            final InstanceIdentifier<VxlanGpe> vxlanGpeId = vppIfcAugId.child(VxlanGpe.class);
+            registry.addWriterBefore(new GenericWriter<>(vxlanGpeId, new VxlanGpeCustomizer(jvpp, ifcContext)),
+                    ifcId);
+            // Tap(Needs to be executed before Interface customizer) =
+            final InstanceIdentifier<Tap> tapId = vppIfcAugId.child(Tap.class);
+            registry.addWriterBefore(new GenericWriter<>(tapId, new TapCustomizer(jvpp, ifcContext)),
+                    ifcId);
+
+            final Set<InstanceIdentifier<?>> specificIfcTypes = Sets.newHashSet(vhostId, vxlanGpeId, vxlanGpeId, tapId);
+
+            // Ethernet(No dependency, customizer not finished TODO) =
+            registry.addWriter(new GenericWriter<>(vppIfcAugId.child(Ethernet.class), new EthernetCustomizer(jvpp)));
+            // Routing(Execute only after specific interface customizers) =
+            registry.addWriterAfter(
+                    new GenericWriter<>(vppIfcAugId.child(Routing.class), new RoutingCustomizer(jvpp, ifcContext)),
+                    specificIfcTypes);
+            // Routing(Execute only after specific interface customizers) =
+            registry.addWriterAfter(
+                    new GenericWriter<>(vppIfcAugId.child(L2.class), new L2Customizer(jvpp, ifcContext, bdContext)),
+                    specificIfcTypes);
+        }
 
-        final List<ChildWriter<? extends ChildOf<Interface1>>> interface1ChildWriters = Lists.newArrayList();
-        interface1ChildWriters.add(ipv4Writer);
-        interface1ChildWriters.add(ipv6Writer);
-
-        return new CompositeChildWriter<>(Interface1.class,
-            interface1ChildWriters, new ReflexiveAugmentWriterCustomizer<>());
     }
 
-    private ChildWriter<VppInterfaceAugmentation> getVppIfcAugmentationWriter() {
-
-        final ChildWriter<Ethernet> ethernetWriter = new CompositeChildWriter<>(Ethernet.class,
-            new EthernetCustomizer(getVppJvppIfcDependency()));
-
-        final ChildWriter<Routing> routingWriter = new CompositeChildWriter<>(Routing.class,
-            new RoutingCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
-
-        final ChildWriter<Vxlan> vxlanWriter = new CompositeChildWriter<>(Vxlan.class,
-            new VxlanCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
-
-        final ChildWriter<VxlanGpe> vxlanGpeWriter = new CompositeChildWriter<>(VxlanGpe.class,
-            new VxlanGpeCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
-
-        final ChildWriter<VhostUser> vhostUserWriter = new CompositeChildWriter<>(VhostUser.class,
-            new VhostUserCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
-
-        final ChildWriter<Tap> tapWriter = new CompositeChildWriter<>(Tap.class,
-            new TapCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()));
-
-        final ChildWriter<L2> l2Writer = new CompositeChildWriter<>(L2.class,
-            new L2Customizer(getVppJvppIfcDependency(), getInterfaceContextDependency(),
-                getBridgeDomainContextDependency())
-        );
-
-        final List<ChildWriter<? extends ChildOf<VppInterfaceAugmentation>>> vppIfcChildWriters = Lists.newArrayList();
-        vppIfcChildWriters.add(vhostUserWriter);
-        vppIfcChildWriters.add(vxlanWriter);
-        vppIfcChildWriters.add(vxlanGpeWriter);
-        vppIfcChildWriters.add(tapWriter);
-        vppIfcChildWriters.add(ethernetWriter);
-        vppIfcChildWriters.add(l2Writer);
-        vppIfcChildWriters.add(routingWriter);
-
-        return new CompositeChildWriter<>(VppInterfaceAugmentation.class,
-            vppIfcChildWriters,
-            RWUtils.emptyAugWriterList(),
-            new ReflexiveAugmentWriterCustomizer<>());
-    }
 }
index f24dbf7..589ba92 100644 (file)
 
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406;
 
-import static io.fd.honeycomb.v3po.translate.util.RWUtils.singletonChildWriterList;
-
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.util.write.ReflexiveAugmentWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer;
+import com.google.common.collect.Sets;
+import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter;
+import io.fd.honeycomb.v3po.translate.impl.write.GenericWriter;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.RewriteCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.SubInterfaceCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.SubInterfaceL2Customizer;
 import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.SubInterfaceIpv4AddressCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
-import java.util.ArrayList;
-import java.util.List;
-import javax.annotation.Nonnull;
+import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.WriterFactory;
+import org.opendaylight.yang.gen.v1.urn.ieee.params.xml.ns.yang.dot1q.types.rev150626.dot1q.tag.or.any.Dot1qTag;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.SubinterfaceAugmentation;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.SubInterfaces;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.sub.interfaces.SubInterface;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.sub.interfaces.SubInterfaceKey;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.L2;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.match.attributes.match.type.vlan.tagged.VlanTagged;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.Match;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.Tags;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.l2.Rewrite;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.tags.Tag;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.ip4.attributes.Ipv4;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.ip4.attributes.ipv4.Address;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.tag.rewrite.PushTags;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.openvpp.jvpp.future.FutureJVpp;
 
-final class SubinterfaceAugmentationWriterFactory {
-
-    private SubinterfaceAugmentationWriterFactory() {
-    }
-
-    private static ChildWriter<Ipv4> getIp4Writer(
-        @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext) {
+final class SubinterfaceAugmentationWriterFactory implements WriterFactory {
 
-        final ChildWriter<Address> addressWriter = new CompositeListWriter<>(
-            Address.class,
-            new SubInterfaceIpv4AddressCustomizer(futureJvpp, interfaceContext));
+    private final InstanceIdentifier<Interface> ifcId;
+    private final FutureJVpp jvpp;
+    private final NamingContext ifcContext;
+    private final NamingContext bdContext;
 
-        return new CompositeChildWriter<>(
-            Ipv4.class,
-            RWUtils.singletonChildWriterList(addressWriter),
-            new ReflexiveChildWriterCustomizer<>());
+    public SubinterfaceAugmentationWriterFactory(
+            final InstanceIdentifier<Interface> ifcId, final FutureJVpp jvpp,
+            final NamingContext ifcContext, final NamingContext bdContext) {
+        this.ifcId = ifcId;
+        this.jvpp = jvpp;
+        this.ifcContext = ifcContext;
+        this.bdContext = bdContext;
     }
 
-    private static ChildWriter<L2> getL2Writer(
-        @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext,
-        @Nonnull final NamingContext bridgeDomainContext) {
-
-        final ChildWriter<? extends ChildOf<L2>> rewriteWriter =
-            new CompositeChildWriter<>(Rewrite.class, new RewriteCustomizer(futureJvpp, interfaceContext));
-
-        return new CompositeChildWriter<>(
-            L2.class,
-            singletonChildWriterList(rewriteWriter),
-            new SubInterfaceL2Customizer(futureJvpp, interfaceContext, bridgeDomainContext)
-        );
-    }
-
-    static ChildWriter<SubinterfaceAugmentation> createInstance(
-        @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext,
-        @Nonnull final NamingContext bridgeDomainContext) {
-        final List<ChildWriter<? extends ChildOf<SubInterface>>> childWriters = new ArrayList<>();
-
-        // TODO L2 is ChildOf<SubInterfaceBaseAttributes>, but SubInterface extends SubInterfaceBaseAttributes
-        // If we use containers inside groupings, we need to cast and lose static type checking.
-        // Can we get rid of the cast?
-        childWriters.add((ChildWriter) getL2Writer(futureJvpp, interfaceContext, bridgeDomainContext));
-        childWriters.add((ChildWriter) getIp4Writer(futureJvpp, interfaceContext));
-
-        final CompositeListWriter<SubInterface, SubInterfaceKey> subInterfaceWriter = new CompositeListWriter<>(
-            SubInterface.class,
-            childWriters,
-            new SubInterfaceCustomizer(futureJvpp, interfaceContext));
-
-        final ChildWriter<SubInterfaces> subInterfacesWriter = new CompositeChildWriter<>(
-            SubInterfaces.class,
-            singletonChildWriterList(subInterfaceWriter),
-            new ReflexiveChildWriterCustomizer<>());
+    @Override
+    public void init(final ModifiableWriterRegistry registry) {
+        final InstanceIdentifier<SubinterfaceAugmentation> subIfcAugId =
+                ifcId.augmentation(SubinterfaceAugmentation.class);
+        // Subinterfaces
+        //  Subinterface(Handle only after all interface related stuff gets processed) =
+        final InstanceIdentifier<SubInterface> subIfcId = subIfcAugId.child(SubInterfaces.class).child(SubInterface.class);
+        registry.addSubtreeWriterAfter(
+                // TODO this customizer covers quite a lot of complex child nodes (maybe refactor ?)
+                Sets.newHashSet(
+                        InstanceIdentifier.create(SubInterface.class).child(Tags.class),
+                        InstanceIdentifier.create(SubInterface.class).child(Tags.class).child(Tag.class),
+                        InstanceIdentifier.create(SubInterface.class).child(Tags.class).child(Tag.class).child(
+                                Dot1qTag.class),
+                        InstanceIdentifier.create(SubInterface.class).child(Match.class),
+                        InstanceIdentifier.create(SubInterface.class).child(Match.class).child(VlanTagged.class)),
+                new GenericListWriter<>(subIfcId, new SubInterfaceCustomizer(jvpp, ifcContext)),
+                ifcId);
+        //   L2 =
+        final InstanceIdentifier<org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.L2>
+                l2Id = subIfcId.child(
+                org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.L2.class);
+        registry.addWriterAfter(new GenericWriter<>(l2Id, new SubInterfaceL2Customizer(jvpp, ifcContext, bdContext)),
+                subIfcId);
+        //    Rewrite(also handles pushTags + pushTags/dot1qtag) =
+        final InstanceIdentifier<Rewrite> rewriteId = l2Id.child(Rewrite.class);
+        registry.addSubtreeWriterAfter(
+                Sets.newHashSet(
+                        InstanceIdentifier.create(Rewrite.class).child(PushTags.class),
+                        InstanceIdentifier.create(Rewrite.class).child(PushTags.class)
+                                .child(org.opendaylight.yang.gen.v1.urn.ieee.params.xml.ns.yang.dot1q.types.rev150626.dot1q.tag.Dot1qTag.class)),
+                new GenericWriter<>(rewriteId, new RewriteCustomizer(jvpp, ifcContext)),
+                l2Id);
+        //   Ipv4(handled after L2 and L2/rewrite is done) =
+        final InstanceIdentifier<Address> ipv4SubifcAddressId = subIfcId.child(Ipv4.class).child(Address.class);
+        registry.addWriterAfter(new GenericListWriter<>(ipv4SubifcAddressId,
+                new SubInterfaceIpv4AddressCustomizer(jvpp, ifcContext)),
+                rewriteId);
 
-        return new CompositeChildWriter<>(
-            SubinterfaceAugmentation.class,
-            singletonChildWriterList(subInterfacesWriter),
-            RWUtils.emptyAugWriterList(),
-            new ReflexiveAugmentWriterCustomizer<>());
     }
 }
index 6bf3da1..d9a542b 100644 (file)
@@ -1,25 +1,18 @@
 package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406;
 
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.util.write.CloseableWriter;
-import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer;
+import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter;
+import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
 import io.fd.honeycomb.v3po.translate.v3po.vpp.BridgeDomainCustomizer;
 import io.fd.honeycomb.v3po.translate.v3po.vpp.L2FibEntryCustomizer;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
-import java.util.ArrayList;
-import java.util.List;
+import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry;
+import io.fd.honeycomb.v3po.translate.write.WriterFactory;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.L2FibTable;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.l2.fib.table.L2FibEntry;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.l2.fib.table.L2FibEntryKey;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.openvpp.jvpp.future.FutureJVpp;
 
 public class VppHoneycombWriterModule extends
     org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractVppHoneycombWriterModule {
@@ -42,38 +35,45 @@ public class VppHoneycombWriterModule extends
 
     @Override
     public java.lang.AutoCloseable createInstance() {
-        final CompositeListWriter<BridgeDomain, BridgeDomainKey> bridgeDomainWriter = new CompositeListWriter<>(
-            BridgeDomain.class,
-            RWUtils.singletonChildWriterList(l2FibTableWriter()),
-            new BridgeDomainCustomizer(getVppJvppWriterDependency(), getBridgeDomainContextVppDependency()));
-
-        final ChildWriter<BridgeDomains> bridgeDomainsWriter = new CompositeChildWriter<>(
-            BridgeDomains.class,
-            RWUtils.singletonChildWriterList(bridgeDomainWriter),
-            new ReflexiveChildWriterCustomizer<>());
-
-        final List<ChildWriter<? extends ChildOf<Vpp>>> childWriters = new ArrayList<>();
-        childWriters.add(bridgeDomainsWriter);
-
-        return new CloseableWriter<>(new CompositeRootWriter<>(
-            Vpp.class,
-            childWriters,
-            new NoopWriterCustomizer<>()));
+        return new VppHoneycombWriterFactory(
+                getVppJvppWriterDependency(),
+                getBridgeDomainContextVppDependency(),
+                getInterfaceContextVppDependency());
     }
 
-    private ChildWriter l2FibTableWriter() {
-        final CompositeListWriter<L2FibEntry, L2FibEntryKey> l2FibEntryWriter =
-            new CompositeListWriter<>(L2FibEntry.class,
-                new L2FibEntryCustomizer(getVppJvppWriterDependency(),
-                    getBridgeDomainContextVppDependency(), getInterfaceContextVppDependency()));
+    private static final class VppHoneycombWriterFactory implements WriterFactory, AutoCloseable {
 
-        final ChildWriter<L2FibTable> l2FibTableWriter = new CompositeChildWriter<>(
-            L2FibTable.class,
-            RWUtils.singletonChildWriterList(l2FibEntryWriter),
-            new ReflexiveChildWriterCustomizer<>());
+        private final FutureJVpp jvpp;
+        private final NamingContext bdContext;
+        private final NamingContext ifcContext;
 
-        return l2FibTableWriter;
-    }
+        VppHoneycombWriterFactory(final FutureJVpp vppJvppWriterDependency,
+                                  final NamingContext bridgeDomainContextVppDependency,
+                                  final NamingContext interfaceContextVppDependency) {
+            this.jvpp = vppJvppWriterDependency;
+            this.bdContext = bridgeDomainContextVppDependency;
+            this.ifcContext = interfaceContextVppDependency;
+        }
 
+        @Override
+        public void close() throws Exception {
+            // unregister is not supported in ModifiableWriterRegistry (not really needed though)
+        }
 
+        @Override
+        public void init(final ModifiableWriterRegistry registry) {
+            // Vpp has no handlers
+            //  BridgeDomains has no handlers
+            //   BridgeDomain =
+            final InstanceIdentifier<BridgeDomain> bdId =
+                    InstanceIdentifier.create(Vpp.class).child(BridgeDomains.class).child(BridgeDomain.class);
+            registry.addWriter(new GenericListWriter<>(bdId, new BridgeDomainCustomizer(jvpp, bdContext)));
+            //    L2FibTable has no handlers
+            //     L2FibEntry(handled after BridgeDomain) =
+            final InstanceIdentifier<L2FibEntry> l2FibEntryId = bdId.child(L2FibTable.class).child(L2FibEntry.class);
+            registry.addWriterAfter(
+                    new GenericListWriter<>(l2FibEntryId, new L2FibEntryCustomizer(jvpp, bdContext, ifcContext)),
+                    bdId);
+        }
+    }
 }
index cf89cbd..8a55272 100644 (file)
@@ -149,7 +149,7 @@ module v3po2vpp {
 
     identity vpp-honeycomb-writer {
         base config:module-type;
-        config:provided-service tapi:honeycomb-writer;
+        config:provided-service tapi:honeycomb-writer-factory;
     }
 
     augment "/config:modules/config:module/config:configuration" {
@@ -187,7 +187,7 @@ module v3po2vpp {
 
     identity interfaces-honeycomb-writer {
         base config:module-type;
-        config:provided-service tapi:honeycomb-writer;
+        config:provided-service tapi:honeycomb-writer-factory;
     }
 
     augment "/config:modules/config:module/config:configuration" {
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java
deleted file mode 100644 (file)
index e713c62..0000000
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.v3po.vpp;
-
-import static io.fd.honeycomb.v3po.translate.v3po.test.ContextTestUtils.getMapping;
-import static io.fd.honeycomb.v3po.translate.v3po.test.ContextTestUtils.getMappingIid;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import com.google.common.base.Optional;
-import com.google.common.collect.Iterators;
-import com.google.common.collect.Lists;
-import io.fd.honeycomb.v3po.translate.MappingContext;
-import io.fd.honeycomb.v3po.translate.ModificationCache;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter;
-import io.fd.honeycomb.v3po.translate.util.write.DelegatingWriterRegistry;
-import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
-import io.fd.honeycomb.v3po.translate.write.WriteContext;
-import io.fd.honeycomb.v3po.translate.write.Writer;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-import java.util.concurrent.ExecutionException;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.Mappings;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomainsBuilder;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-import org.openvpp.jvpp.VppInvocationException;
-import org.openvpp.jvpp.dto.BridgeDomainAddDel;
-import org.openvpp.jvpp.dto.BridgeDomainAddDelReply;
-import org.openvpp.jvpp.future.FutureJVpp;
-
-public class VppTest {
-
-    private static final byte ADD_OR_UPDATE_BD = 1;
-    private static final byte ZERO = 0;
-    private static final String BD_NAME = "bdn1";
-    private static final String BD_CONTEXT_NAME = "test-instance";
-
-    @Mock
-    private FutureJVpp api;
-    @Mock
-    private WriteContext ctx;
-    @Mock
-    private MappingContext mappingContext;
-
-    private DelegatingWriterRegistry rootRegistry;
-    private CompositeRootWriter<Vpp> vppWriter;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        NamingContext bdContext = new NamingContext("generatedBdName", BD_CONTEXT_NAME);
-        final ModificationCache toBeReturned = new ModificationCache();
-        doReturn(toBeReturned).when(ctx).getModificationCache();
-        doReturn(mappingContext).when(ctx).getMappingContext();
-
-        vppWriter = VppUtils.getVppWriter(api, bdContext);
-        rootRegistry = new DelegatingWriterRegistry(
-            Collections.<Writer<? extends DataObject>>singletonList(vppWriter));
-    }
-
-    private BridgeDomains getBridgeDomains(String... name) {
-        final List<BridgeDomain> bdmns = Lists.newArrayList();
-        for (String s : name) {
-            bdmns.add(new BridgeDomainBuilder()
-                .setName(s)
-                .setArpTermination(false)
-                .setFlood(true)
-                .setForward(false)
-                .setLearn(true)
-                .build());
-        }
-        return new BridgeDomainsBuilder()
-            .setBridgeDomain(bdmns)
-            .build();
-    }
-
-    private void whenBridgeDomainAddDelThenSuccess()
-        throws ExecutionException, VppInvocationException, InterruptedException {
-        final CompletionStage<BridgeDomainAddDelReply> replyCs = mock(CompletionStage.class);
-        final CompletableFuture<BridgeDomainAddDelReply> replyFuture = mock(CompletableFuture.class);
-        when(replyCs.toCompletableFuture()).thenReturn(replyFuture);
-        final BridgeDomainAddDelReply reply = new BridgeDomainAddDelReply();
-        when(replyFuture.get()).thenReturn(reply);
-        when(api.bridgeDomainAddDel(any(BridgeDomainAddDel.class))).thenReturn(replyCs);
-    }
-
-    private void verifyBridgeDomainAddDel(final BridgeDomain bd, final int bdId) throws VppInvocationException {
-        final byte arpTerm = BridgeDomainTestUtils.booleanToByte(bd.isArpTermination());
-        final byte flood = BridgeDomainTestUtils.booleanToByte(bd.isFlood());
-        final byte forward = BridgeDomainTestUtils.booleanToByte(bd.isForward());
-        final byte learn = BridgeDomainTestUtils.booleanToByte(bd.isLearn());
-        final byte uuf = BridgeDomainTestUtils.booleanToByte(bd.isUnknownUnicastFlood());
-
-        // TODO adding equals methods for jvpp DTOs would make ArgumentCaptor usage obsolete
-        ArgumentCaptor<BridgeDomainAddDel> argumentCaptor = ArgumentCaptor.forClass(BridgeDomainAddDel.class);
-        verify(api).bridgeDomainAddDel(argumentCaptor.capture());
-        final BridgeDomainAddDel actual = argumentCaptor.getValue();
-        assertEquals(arpTerm, actual.arpTerm);
-        assertEquals(flood, actual.flood);
-        assertEquals(forward, actual.forward);
-        assertEquals(learn, actual.learn);
-        assertEquals(uuf, actual.uuFlood);
-        assertEquals(ADD_OR_UPDATE_BD, actual.isAdd);
-        assertEquals(bdId, actual.bdId);
-    }
-
-    private void verifyBridgeDomainDeleteWasInvoked(final int bdId) throws VppInvocationException {
-        ArgumentCaptor<BridgeDomainAddDel> argumentCaptor = ArgumentCaptor.forClass(BridgeDomainAddDel.class);
-        verify(api).bridgeDomainAddDel(argumentCaptor.capture());
-        final BridgeDomainAddDel actual = argumentCaptor.getValue();
-        assertEquals(bdId, actual.bdId);
-        assertEquals(ZERO, actual.arpTerm);
-        assertEquals(ZERO, actual.flood);
-        assertEquals(ZERO, actual.forward);
-        assertEquals(ZERO, actual.learn);
-        assertEquals(ZERO, actual.uuFlood);
-        assertEquals(ZERO, actual.isAdd);
-    }
-
-    @Test
-    public void writeVppUsingRootRegistry() throws Exception {
-        final int bdId = 1;
-        final BridgeDomains bdn1 = getBridgeDomains(BD_NAME);
-        whenBridgeDomainAddDelThenSuccess();
-
-        // Returning no Mappings for "test-instance" makes bdContext.containsName() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class));
-        // Make bdContext.containsIndex() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME));
-
-        rootRegistry.update(
-            InstanceIdentifier.create(Vpp.class),
-            null,
-            new VppBuilder().setBridgeDomains(bdn1).build(),
-            ctx);
-
-        verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId);
-    }
-
-    @Test
-    public void writeVppUsingVppWriter() throws Exception {
-        final int bdId = 1;
-        final BridgeDomains bdn1 = getBridgeDomains(BD_NAME);
-        whenBridgeDomainAddDelThenSuccess();
-
-        // Returning no Mappings for "test-instance" makes bdContext.containsName() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class));
-        // Make bdContext.containsIndex() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME));
-
-        vppWriter.update(InstanceIdentifier.create(Vpp.class),
-            null,
-            new VppBuilder().setBridgeDomains(bdn1).build(),
-            ctx);
-
-        verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId);
-        verify(mappingContext).put(getMappingIid(BD_NAME, BD_CONTEXT_NAME), getMapping(BD_NAME, 1).get());
-    }
-
-    @Test
-    public void writeVppFromRoot() throws Exception {
-        final BridgeDomains bdn1 = getBridgeDomains(BD_NAME);
-        final int bdId = 1;
-        final Vpp vpp = new VppBuilder().setBridgeDomains(bdn1).build();
-        whenBridgeDomainAddDelThenSuccess();
-
-        // Returning no Mappings for "test-instance" makes bdContext.containsName() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class));
-        // Make bdContext.containsIndex() return false
-        doReturn(Optional.absent()).when(mappingContext)
-            .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME));
-
-        rootRegistry.update(Collections.emptyMap(),
-            Collections.<InstanceIdentifier<?>, DataObject>singletonMap(InstanceIdentifier.create(Vpp.class),
-                vpp), ctx);
-
-        verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId);
-    }
-
-    @Test
-    public void deleteVpp() throws Exception {
-        final BridgeDomains bdn1 = getBridgeDomains(BD_NAME);
-        final int bdId = 1;
-        whenBridgeDomainAddDelThenSuccess();
-        doReturn(getMapping(BD_NAME, bdId)).when(mappingContext).read(getMappingIid(BD_NAME, BD_CONTEXT_NAME));
-
-        rootRegistry.update(
-            InstanceIdentifier.create(Vpp.class),
-            new VppBuilder().setBridgeDomains(bdn1).build(),
-            null,
-            ctx);
-
-        verifyBridgeDomainDeleteWasInvoked(bdId);
-    }
-
-    @Test
-    public void updateVppNoActualChange() throws Exception {
-        rootRegistry.update(
-            InstanceIdentifier.create(Vpp.class),
-            new VppBuilder().setBridgeDomains(getBridgeDomains(BD_NAME)).build(),
-            new VppBuilder().setBridgeDomains(getBridgeDomains(BD_NAME)).build(),
-            ctx);
-
-        verifyZeroInteractions(api);
-    }
-
-    @Test
-    public void writeUpdate() throws Exception {
-        final int bdn1Id = 1;
-        doReturn(getMapping(BD_NAME, bdn1Id)).when(mappingContext).read(getMappingIid(BD_NAME, BD_CONTEXT_NAME));
-
-        final BridgeDomains domainsBefore = getBridgeDomains(BD_NAME);
-        final BridgeDomain bdn1Before = domainsBefore.getBridgeDomain().get(0);
-
-        final BridgeDomain bdn1After = new BridgeDomainBuilder(bdn1Before).setFlood(!bdn1Before.isFlood()).build();
-        final BridgeDomains domainsAfter = new BridgeDomainsBuilder()
-            .setBridgeDomain(Collections.singletonList(bdn1After))
-            .build();
-
-        whenBridgeDomainAddDelThenSuccess();
-
-        rootRegistry.update(
-            InstanceIdentifier.create(Vpp.class),
-            new VppBuilder().setBridgeDomains(domainsBefore).build(),
-            new VppBuilder().setBridgeDomains(domainsAfter).build(),
-            ctx);
-
-        // bdn1 is created with negated flood value
-        verifyBridgeDomainAddDel(bdn1After, bdn1Id);
-    }
-
-    // TODO test unkeyed list
-    // TODO test update of a child without dedicated writer
-}
\ No newline at end of file
diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java
deleted file mode 100644 (file)
index 4b3eb5a..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 2016 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.v3po.translate.v3po.vpp;
-
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter;
-import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter;
-import io.fd.honeycomb.v3po.translate.util.RWUtils;
-import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer;
-import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext;
-import io.fd.honeycomb.v3po.translate.write.ChildWriter;
-import java.util.ArrayList;
-import java.util.List;
-import javax.annotation.Nonnull;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey;
-import org.opendaylight.yangtools.yang.binding.ChildOf;
-import org.openvpp.jvpp.future.FutureJVpp;
-
-final class VppUtils {
-
-    public VppUtils() {}
-
-    /**
-     * Create root Vpp writer with all its children wired
-     */
-    static CompositeRootWriter<Vpp> getVppWriter(@Nonnull final FutureJVpp vppApi, final NamingContext bdContext) {
-
-        final CompositeListWriter<BridgeDomain, BridgeDomainKey> bridgeDomainWriter = new CompositeListWriter<>(
-            BridgeDomain.class,
-            new BridgeDomainCustomizer(vppApi, bdContext));
-
-        final ChildWriter<BridgeDomains> bridgeDomainsReader = new CompositeChildWriter<>(
-            BridgeDomains.class,
-            RWUtils.singletonChildWriterList(bridgeDomainWriter),
-            new ReflexiveChildWriterCustomizer<BridgeDomains>());
-
-        final List<ChildWriter<? extends ChildOf<Vpp>>> childWriters = new ArrayList<>();
-        childWriters.add(bridgeDomainsReader);
-
-        return new CompositeRootWriter<>(
-            Vpp.class,
-            childWriters,
-            new NoopWriterCustomizer<Vpp>());
-    }
-}
index dc77106..f6e0e6d 100644 (file)
@@ -75,10 +75,6 @@ public final class NamingContext implements AutoCloseable {
     public synchronized String getName(final int index, @Nonnull final MappingContext mappingContext) {
         if (!containsName(index, mappingContext)) {
             final String artificialName = getArtificialName(index);
-            LOG.info("Assigning artificial name: {} for index: {}", artificialName, index);
-            for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
-                LOG.error("{}", stackTraceElement.toString());
-            }
             addName(index, artificialName, mappingContext);
         }