From 9f6b16d6e8ade6dfa40e9bbf4196d55adf8f2fec Mon Sep 17 00:00:00 2001 From: Maros Marsalek Date: Wed, 29 Jun 2016 09:14:51 +0200 Subject: 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 --- .../fd/honeycomb/v3po/translate/util/RWUtils.java | 37 ++- .../util/write/CloseableWriterRegistry.java | 65 ---- .../util/write/DelegatingWriterRegistry.java | 182 ----------- .../translate/util/write/NoopWriterCustomizer.java | 49 --- .../translate/util/write/NoopWriterRegistry.java | 24 +- .../write/ReflexiveAugmentWriterCustomizer.java | 50 --- .../util/write/ReflexiveChildWriterCustomizer.java | 59 ---- .../util/write/registry/FlatWriterRegistry.java | 334 +++++++++++++++++++++ .../write/registry/FlatWriterRegistryBuilder.java | 225 ++++++++++++++ .../util/write/registry/SubtreeWriter.java | 85 ++++++ 10 files changed, 675 insertions(+), 435 deletions(-) delete mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/CloseableWriterRegistry.java delete mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/DelegatingWriterRegistry.java delete mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/NoopWriterCustomizer.java delete mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveAugmentWriterCustomizer.java delete mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/ReflexiveChildWriterCustomizer.java create mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistry.java create mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilder.java create mode 100644 v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriter.java (limited to 'v3po/translate-utils/src/main/java/io/fd/honeycomb/v3po/translate') 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 List>> emptyChildWriterList() { - return Collections.emptyList(); - } - public static List>> emptyAugReaderList() { return Collections.emptyList(); } - public static List>> emptyAugWriterList() { - return Collections.emptyList(); - } - public static List>> singletonAugReaderList( ChildReader> item) { return Collections.>>singletonList(item); @@ -101,16 +93,6 @@ public final class RWUtils { return Collections.>>singletonList(item); } - public static List>> singletonChildWriterList( - ChildWriter> item) { - return Collections.>>singletonList(item); - } - - public static List>> singletonAugWriterList( - ChildWriter> item) { - return Collections.>>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) 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 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, DataObject> nodesBefore, - @Nonnull final Map, DataObject> nodesAfter, - @Nonnull final WriteContext ctx) throws TranslationException { - writerRegistry.update(nodesBefore, nodesAfter, ctx); - } - - @Override - public void update( - @Nonnull final InstanceIdentifier 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 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, Writer> rootWriters; - - /** - * Create new {@link DelegatingWriterRegistry} - * - * @param rootWriters List of delegate writers - */ - public DelegatingWriterRegistry(@Nonnull final List> 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 getManagedDataObjectType() { - throw new UnsupportedOperationException("Root registry has no type"); - } - - @Override - public void update(@Nonnull final InstanceIdentifier 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 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, DataObject> nodesBefore, - @Nonnull final Map, DataObject> nodesAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException { - - Multimap, 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> processedNodes = Lists.newArrayList(); - - for (Map.Entry, Writer> rootWriterEntry : rootWriters - .entrySet()) { - - final InstanceIdentifier 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, DataObject> nodesBefore, - final @Nonnull Multimap, InstanceIdentifier> rootIdToNestedIds) { - for (final InstanceIdentifier changeId : nodesBefore.keySet()) { - final InstanceIdentifier.PathArgument first = Iterables.getFirst(changeId.getPathArguments(), null); - checkNotNull(first, "Empty identifier detected"); - final InstanceIdentifier 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> processedNodes; - private final Map, DataObject> nodesBefore; - private final Map, DataObject> nodesAfter; - private final WriteContext ctx; - - ReverterImpl(final WriterRegistry delegatingWriterRegistry, - final List> processedNodes, - final Map, DataObject> nodesBefore, - final Map, 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> 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 implements RootWriterCustomizer { - - @Override - public void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataAfter, - @Nonnull final WriteContext ctx) { - - } - - @Override - public void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull final D dataAfter, - @Nonnull final WriteContext ctx) { - - } - - @Override - public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier 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,18 +31,10 @@ import org.slf4j.LoggerFactory; * Empty registry that does not perform any changes. Can be used in data layer, if we want to disable passing data to * translation layer. */ -public class NoopWriterRegistry implements WriterRegistry { +public class NoopWriterRegistry implements WriterRegistry, AutoCloseable { private static final Logger LOG = LoggerFactory.getLogger(NoopWriterRegistry.class); - @Override - public void update(@Nonnull final Map, DataObject> dataBefore, - @Nonnull final Map, 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 id, @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter, @@ -53,9 +44,20 @@ public class NoopWriterRegistry implements WriterRegistry { // NOOP } + @Override + public void update(@Nonnull final DataObjectUpdates updates, + @Nonnull final WriteContext ctx) throws TranslationException { + // NOOP + } + @Nonnull @Override public InstanceIdentifier 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 extends NoopWriterCustomizer implements - ChildWriterCustomizer { - - @Nonnull - @Override - @SuppressWarnings("unchecked") - public Optional extract(@Nonnull final InstanceIdentifier currentId, @Nonnull final DataObject parentData) { - checkArgument(parentData instanceof Augmentable, "Not augmnatable parent object: %s", parentData); - final Class 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 extends NoopWriterCustomizer implements - ChildWriterCustomizer { - - @Nonnull - @Override - @SuppressWarnings("unchecked") - public Optional extract(@Nonnull final InstanceIdentifier currentId, @Nonnull final DataObject parentData) { - final Class currentType = currentId.getTargetType(); - final Optional method = ReflectionUtils.findMethodReflex(getParentType(currentId), - "get" + currentType.getSimpleName(), Collections.>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 getParentType(final @Nonnull InstanceIdentifier 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> handledTypes; + + private final Set> writersOrderReversed; + private final Set> writersOrder; + private final Map, 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, 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> getAllHandledTypes( + @Nonnull final ImmutableMap, Writer> writers) { + final ImmutableSet.Builder> handledTypesBuilder = ImmutableSet.builder(); + for (Map.Entry, 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 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, ? 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 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 getParentDataObjectUpdate(final WriteContext ctx, + final Multimap, ? 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 parentBefore = ctx.readBefore(parentKeyedId); + final Optional parentAfter = ctx.readAfter(parentKeyedId); + return Collections.singleton( + DataObjectUpdate.create(parentKeyedId, parentBefore.orNull(), parentAfter.orNull())); + } + + private void bulkUpdate(@Nonnull final Multimap, ? extends DataObjectUpdate> updates, + @Nonnull final WriteContext ctx, + final boolean attemptRevert, + @Nonnull final Set> 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> processedNodes = new HashSet<>(); + + // Iterate over all writers and call update if there are any related updates + for (InstanceIdentifier writerType : writersOrder) { + Collection 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> 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, ? extends DataObjectUpdate> updates) { + if (!handledTypes.containsAll(updates.keySet())) { + final Sets.SetView> 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, ? 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 getManagedDataObjectType() { + throw new UnsupportedOperationException("Registry has no managed type"); + } + + // FIXME unit test + private final class ReverterImpl implements Reverter { + + private final Collection> processedNodes; + private final Multimap, ? extends DataObjectUpdate> updates; + private final Set> revertDeleteOrder; + private final WriteContext ctx; + + ReverterImpl(final Collection> processedNodes, + final Multimap, ? extends DataObjectUpdate> updates, + final Set> 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, 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, DataObjectUpdate> filterAndRevertProcessed( + final Multimap, ? extends DataObjectUpdate> updates, + final Collection> processedNodes) { + final Multimap, 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, WriterRelation> + writersRelations = new DirectedAcyclicGraph<>((sourceVertex, targetVertex) -> new WriterRelation()); + private final Map, Writer> writersMap = new HashMap<>(); + + /** + * AddWriter without any special relationship to any other type. + */ + @Override + public FlatWriterRegistryBuilder addWriter(@Nonnull final Writer 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> handledChildren, + @Nonnull final Writer 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 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> handledChildren, + @Nonnull final Writer writer, + @Nonnull final InstanceIdentifier relatedType) { + return addWriterBefore(SubtreeWriter.createForWriter(handledChildren, writer), relatedType); + } + + @Override + public FlatWriterRegistryBuilder addWriterBefore(@Nonnull final Writer writer, + @Nonnull final Collection> 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> handledChildren, + @Nonnull final Writer writer, + @Nonnull final Collection> 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 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> handledChildren, + @Nonnull final Writer writer, + @Nonnull final InstanceIdentifier relatedType) { + return addWriterAfter(SubtreeWriter.createForWriter(handledChildren, writer), relatedType); + } + + @Override + public FlatWriterRegistryBuilder addWriterAfter(@Nonnull final Writer writer, + @Nonnull final Collection> 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> handledChildren, + @Nonnull final Writer writer, + @Nonnull final Collection> 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, 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, Writer> getMappedWriters() { + final ImmutableMap.Builder, 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 implements Writer { + + private final Writer delegate; + private final Set> handledChildTypes = new HashSet<>(); + + private SubtreeWriter(final Writer delegate, Set> 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> getHandledChildTypes() { + return handledChildTypes; + } + + @Override + public void update( + @Nonnull final InstanceIdentifier 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 getManagedDataObjectType() { + return delegate.getManagedDataObjectType(); + } + + /** + * Wrap a writer as a subtree writer. + */ + static Writer createForWriter(@Nonnull final Set> handledChildren, + @Nonnull final Writer writer) { + return new SubtreeWriter<>(writer, handledChildren); + } +} -- cgit 1.2.3-korg