summaryrefslogtreecommitdiffstats
path: root/v3po/translate-utils
diff options
context:
space:
mode:
authorMaros Marsalek <mmarsale@cisco.com>2016-06-29 09:14:51 +0200
committerMaros Marsalek <mmarsale@cisco.com>2016-07-13 11:24:26 +0200
commitfb41618f6e78d5f1a20bbf37c45282c5fc15387d (patch)
tree6ac9da72b8a53c01f1ef2f97562a70b838c510f5 /v3po/translate-utils
parentd222ccedd09b5ee76cdcb367ae96c91f49e5f287 (diff)
HONEYCOMB-94 Reimplement writer registry with better ordering options
Now the registry is flat and allows for full control of writer execution order Change-Id: I864e1d676588ffe59b596145e0829e81b1a1ed2f Signed-off-by: Maros Marsalek <mmarsale@cisco.com>
Diffstat (limited to 'v3po/translate-utils')
-rw-r--r--v3po/translate-utils/pom.xml5
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java37
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java65
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java182
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java49
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java24
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java50
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java59
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java334
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java225
-rw-r--r--v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java85
-rw-r--r--v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java2
-rw-r--r--v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java7
-rw-r--r--v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java21
-rw-r--r--v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java17
-rw-r--r--v3po/translate-utils/src/main/yang/translate-utils.yang13
-rw-r--r--v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java189
-rw-r--r--v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java156
-rw-r--r--v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java295
-rw-r--r--v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java96
20 files changed, 1262 insertions, 649 deletions
diff --git a/v3po/translate-utils/pom.xml b/v3po/translate-utils/pom.xml
index 86a11bdba..e4197cf0e 100644
--- a/v3po/translate-utils/pom.xml
+++ b/v3po/translate-utils/pom.xml
@@ -70,6 +70,11 @@
<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>
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java
index b712e159c..55ae9ecf0 100644
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/RWUtils.java
@@ -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
index cd53a4f8b..000000000
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java
+++ /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
index 061d3fa4a..000000000
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java
+++ /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
index 9f9c9f53e..000000000
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java
+++ /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) {
-
- }
-}
diff --git a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java
index 866854212..236ad8917 100644
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterRegistry.java
@@ -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,19 +31,11 @@ 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,
@Nonnull final WriteContext ctx) throws WriteFailedException {
@@ -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
index 6d29214b3..000000000
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java
+++ /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
index 79cdf62c3..000000000
--- a/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java
+++ /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
index 000000000..79d8eb88d
--- /dev/null
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java
@@ -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
index 000000000..f5d218f55
--- /dev/null
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java
@@ -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
index 000000000..e395b29da
--- /dev/null
+++ b/v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java
@@ -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);
+ }
+}
diff --git a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java
index 0eb5062d5..fccd6b1c2 100644
--- a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java
+++ b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModule.java
@@ -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;
diff --git a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java
index 214ab0f90..24d6c50b8 100644
--- a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java
+++ b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingReaderRegistryModuleFactory.java
@@ -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);
+ }
}
diff --git a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java
index 0266ca900..7eadde80e 100644
--- a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java
+++ b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/DelegatingWriterRegistryModule.java
@@ -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;
}
}
diff --git a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java
index 16c8af359..fedd069f1 100644
--- a/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java
+++ b/v3po/translate-utils/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/translate/utils/rev160406/NoopWriterRegistryModule.java
@@ -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
+ }
+ }
}
diff --git a/v3po/translate-utils/src/main/yang/translate-utils.yang b/v3po/translate-utils/src/main/yang/translate-utils.yang
index 1219648c3..2c00bb548 100644
--- a/v3po/translate-utils/src/main/yang/translate-utils.yang
+++ b/v3po/translate-utils/src/main/yang/translate-utils.yang
@@ -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
index f51e49db5..000000000
--- a/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java
+++ /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
index 000000000..ec407b685
--- /dev/null
+++ b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java
@@ -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
index 000000000..7fb5779f3
--- /dev/null
+++ b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java
@@ -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
index 000000000..b7dcadc73
--- /dev/null
+++ b/v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java
@@ -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