diff options
author | 2016-06-29 09:14:51 +0200 | |
---|---|---|
committer | 2016-07-13 11:24:26 +0200 | |
commit | fb41618f6e78d5f1a20bbf37c45282c5fc15387d (patch) | |
tree | 6ac9da72b8a53c01f1ef2f97562a70b838c510f5 /v3po/translate-utils/src | |
parent | d222ccedd09b5ee76cdcb367ae96c91f49e5f287 (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/src')
19 files changed, 1257 insertions, 649 deletions
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 |