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 --- .../data/impl/ModifiableDataTreeDelegator.java | 228 ++++--------- .../honeycomb/v3po/data/impl/ModificationDiff.java | 278 ++++++++++++++++ .../data/impl/rev160411/ConfigDataTreeModule.java | 4 +- v3po/data-impl/src/main/yang/data-impl.yang | 4 +- .../data/impl/ModifiableDataTreeDelegatorTest.java | 367 ++++++++------------- .../v3po/data/impl/ModificationDiffTest.java | 215 +++++++----- v3po/data-impl/src/test/resources/test-diff.yang | 17 + v3po/features/pom.xml | 5 + v3po/features/src/main/features/features.xml | 1 + v3po/impl/src/main/config/default-config.xml | 15 +- v3po/impl/src/main/config/initializer-config.xml | 18 +- v3po/translate-api/pom.xml | 2 +- .../v3po/translate/write/ChildWriter.java | 66 ---- .../v3po/translate/write/DataObjectUpdate.java | 114 +++++++ .../translate/write/ModifiableWriterRegistry.java | 78 +++++ .../v3po/translate/write/WriterFactory.java | 28 ++ .../v3po/translate/write/WriterRegistry.java | 131 ++++++-- .../translate/write/WriterRegistryBuilder.java | 25 ++ .../translate-api/src/main/yang/translate-api.yang | 11 +- .../impl/write/AbstractCompositeWriter.java | 234 +------------ .../translate/impl/write/CompositeChildWriter.java | 125 ------- .../translate/impl/write/CompositeListWriter.java | 203 ------------ .../translate/impl/write/CompositeRootWriter.java | 84 ----- .../translate/impl/write/GenericListWriter.java | 104 ++++++ .../v3po/translate/impl/write/GenericWriter.java | 60 ++++ .../impl/write/GenericListWriterTest.java | 83 +++++ .../translate/impl/write/GenericWriterTest.java | 64 ++++ v3po/translate-utils/pom.xml | 5 + .../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 +++++ .../rev160406/DelegatingReaderRegistryModule.java | 2 + .../DelegatingReaderRegistryModuleFactory.java | 7 + .../rev160406/DelegatingWriterRegistryModule.java | 21 +- .../utils/rev160406/NoopWriterRegistryModule.java | 17 +- .../src/main/yang/translate-utils.yang | 13 +- .../write/util/DelegatingWriterRegistryTest.java | 189 ----------- .../registry/FlatWriterRegistryBuilderTest.java | 156 +++++++++ .../write/registry/FlatWriterRegistryTest.java | 295 +++++++++++++++++ .../util/write/registry/SubtreeWriterTest.java | 96 ++++++ v3po/v3po2vpp/src/main/config/default-config.xml | 14 +- .../rev160406/InterfacesHoneycombWriterModule.java | 213 ++++++------ .../SubinterfaceAugmentationWriterFactory.java | 130 ++++---- .../rev160406/VppHoneycombWriterModule.java | 82 ++--- v3po/v3po2vpp/src/main/yang/v3po2vpp.yang | 4 +- .../honeycomb/v3po/translate/v3po/vpp/VppTest.java | 271 --------------- .../v3po/translate/v3po/vpp/VppUtils.java | 63 ---- .../v3po/translate/v3po/util/NamingContext.java | 4 - 55 files changed, 2835 insertions(+), 2421 deletions(-) create mode 100644 v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java delete mode 100644 v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java create mode 100644 v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java create mode 100644 v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java create mode 100644 v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java create mode 100644 v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java delete mode 100644 v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java delete mode 100644 v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java delete mode 100644 v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeRootWriter.java create mode 100644 v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java create mode 100644 v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriter.java create mode 100644 v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java create mode 100644 v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java 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 delete mode 100644 v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/util/DelegatingWriterRegistryTest.java create mode 100644 v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryBuilderTest.java create mode 100644 v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/FlatWriterRegistryTest.java create mode 100644 v3po/translate-utils/src/test/java/io/fd/honeycomb/v3po/translate/util/write/registry/SubtreeWriterTest.java delete mode 100644 v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java delete mode 100644 v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java diff --git a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java index 91de1885e..77aa12aba 100644 --- a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java +++ b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegator.java @@ -16,38 +16,36 @@ package io.fd.honeycomb.v3po.data.impl; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.util.concurrent.Futures.immediateCheckedFuture; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; -import com.google.common.collect.Sets; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import com.google.common.util.concurrent.CheckedFuture; import io.fd.honeycomb.v3po.data.DataModification; import io.fd.honeycomb.v3po.data.ReadableDataManager; import io.fd.honeycomb.v3po.translate.TranslationException; +import io.fd.honeycomb.v3po.translate.util.RWUtils; import io.fd.honeycomb.v3po.translate.util.write.TransactionMappingContext; import io.fd.honeycomb.v3po.translate.util.write.TransactionWriteContext; +import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate; import io.fd.honeycomb.v3po.translate.write.WriteContext; import io.fd.honeycomb.v3po.translate.write.WriterRegistry; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; import java.util.Map; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException; import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction; import org.opendaylight.yangtools.binding.data.codec.api.BindingNormalizedNodeSerializer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; -import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,6 +108,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager @Override protected void processCandidate(final DataTreeCandidate candidate) throws TranslationException { + final DataTreeCandidateNode rootNode = candidate.getRootNode(); final YangInstanceIdentifier rootPath = candidate.getRootPath(); LOG.trace("ConfigDataTree.modify() rootPath={}, rootNode={}, dataBefore={}, dataAfter={}", @@ -119,13 +118,12 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, rootNode); LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff); - final Map, DataObject> nodesBefore = toBindingAware(modificationDiff.getModificationsBefore()); - LOG.debug("ConfigDataTree.modify() extracted nodesBefore={}", nodesBefore.keySet()); - final Map, DataObject> nodesAfter = toBindingAware(modificationDiff.getModificationsAfter()); - LOG.debug("ConfigDataTree.modify() extracted nodesAfter={}", nodesAfter.keySet()); + // Distinguish between updates (create + update) and deletes + final WriterRegistry.DataObjectUpdates baUpdates = toBindingAware(modificationDiff.getUpdates()); + LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates); try (final WriteContext ctx = getTransactionWriteContext()) { - writerRegistry.update(nodesBefore, nodesAfter, ctx); + writerRegistry.update(baUpdates, ctx); final CheckedFuture contextUpdateResult = ((TransactionMappingContext) ctx.getMappingContext()).submit(); @@ -152,7 +150,7 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager LOG.error(msg, e); throw new TranslationException(msg, e); } catch (TranslationException e) { - LOG.error("Error while processing data change (before={}, after={})", nodesBefore, nodesAfter, e); + LOG.error("Error while processing data change (updates={})", baUpdates, e); throw e; } } @@ -167,177 +165,71 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext); } - private Map, DataObject> toBindingAware(final Map> biNodes) { + private WriterRegistry.DataObjectUpdates toBindingAware( + final Map biNodes) { return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer); } } @VisibleForTesting - static Map, DataObject> toBindingAware(final Map> biNodes, - final BindingNormalizedNodeSerializer serializer) { - final HashMap, DataObject> transformed = new HashMap<>(biNodes.size()); - for (Map.Entry> biEntry : biNodes.entrySet()) { - final Map.Entry, DataObject> baEntry = serializer.fromNormalizedNode(biEntry.getKey(), biEntry.getValue()); - if (baEntry != null) { - transformed.put(baEntry.getKey(), baEntry.getValue()); + static WriterRegistry.DataObjectUpdates toBindingAware( + final Map biNodes, + final BindingNormalizedNodeSerializer serializer) { + + final Multimap, DataObjectUpdate> dataObjectUpdates = HashMultimap.create(); + final Multimap, DataObjectUpdate.DataObjectDelete> dataObjectDeletes = + HashMultimap.create(); + + for (Map.Entry biEntry : biNodes.entrySet()) { + final InstanceIdentifier unkeyedIid = + RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey())); + + ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue(); + final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer); + if (dataObjectUpdate != null) { + if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) { + dataObjectDeletes.put(unkeyedIid, ((DataObjectUpdate.DataObjectDelete) dataObjectUpdate)); + } else { + dataObjectUpdates.put(unkeyedIid, dataObjectUpdate); + } } } - return transformed; + return new WriterRegistry.DataObjectUpdates(dataObjectUpdates, dataObjectDeletes); } - @VisibleForTesting - static final class ModificationDiff { - - private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap(), Collections.emptyMap()); - private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE); - - private final Map> modificationsBefore; - private final Map> modificationsAfter; + @Nullable + private static DataObjectUpdate toDataObjectUpdate( + final ModificationDiff.NormalizedNodeUpdate normalizedNodeUpdate, + final BindingNormalizedNodeSerializer serializer) { - private ModificationDiff(@Nonnull final Map> modificationsBefore, - @Nonnull final Map> modificationsAfter) { - this.modificationsBefore = modificationsBefore; - this.modificationsAfter = modificationsAfter; - } - - Map> getModificationsBefore() { - return modificationsBefore; - } + InstanceIdentifier baId = serializer.fromYangInstanceIdentifier(normalizedNodeUpdate.getId()); + checkNotNull(baId, "Unable to transform instance identifier: %s into BA", normalizedNodeUpdate.getId()); - Map> getModificationsAfter() { - return modificationsAfter; - } - - private ModificationDiff merge(final ModificationDiff other) { - if (this == EMPTY_DIFF) { - return other; - } + DataObject dataObjectBefore = getDataObject(serializer, + normalizedNodeUpdate.getDataBefore(), normalizedNodeUpdate.getId()); + DataObject dataObjectAfter = + getDataObject(serializer, normalizedNodeUpdate.getDataAfter(), normalizedNodeUpdate.getId()); - if (other == EMPTY_DIFF) { - return this; - } - - return new ModificationDiff(join(modificationsBefore, other.modificationsBefore), - join(modificationsAfter, other.modificationsAfter)); - } - - private static Map> join( - final Map> mapOne, - final Map> mapTwo) { - // Check unique modifications - // TODO Probably not necessary to check - final Sets.SetView duplicates = Sets.intersection(mapOne.keySet(), mapTwo.keySet()); - checkArgument(duplicates.size() == 0, "Duplicates detected: %s. In maps: %s and %s", duplicates, mapOne, mapTwo); - final HashMap> joined = new HashMap<>(); - joined.putAll(mapOne); - joined.putAll(mapTwo); - return joined; - } - - private static ModificationDiff createFromBefore(YangInstanceIdentifier idBefore, DataTreeCandidateNode candidate) { - return new ModificationDiff( - Collections.singletonMap(idBefore, candidate.getDataBefore().get()), - Collections.emptyMap()); - } - - private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) { - return new ModificationDiff( - Collections.singletonMap(id, candidate.getDataBefore().get()), - Collections.singletonMap(id, candidate.getDataAfter().get())); - } - - private static ModificationDiff createFromAfter(YangInstanceIdentifier idAfter, DataTreeCandidateNode candidate) { - return new ModificationDiff( - Collections.emptyMap(), - Collections.singletonMap(idAfter, candidate.getDataAfter().get())); - } - - /** - * Produce a diff from a candidate node recursively. - */ - @Nonnull - static ModificationDiff recursivelyFromCandidate(@Nonnull final YangInstanceIdentifier yangIid, - @Nonnull final DataTreeCandidateNode currentCandidate) { - switch (currentCandidate.getModificationType()) { - case APPEARED: - case DISAPPEARED: - case UNMODIFIED: { - // (dis)appeared nodes are not important, no real data to process - return ModificationDiff.EMPTY_DIFF; - } - case WRITE: { - return currentCandidate.getDataBefore().isPresent() - ? ModificationDiff.create(yangIid, currentCandidate) - : ModificationDiff.createFromAfter(yangIid, currentCandidate); - // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes - } - case DELETE: - return ModificationDiff.createFromBefore(yangIid, currentCandidate); - case SUBTREE_MODIFIED: { - // Modifications here are presented also for leaves. However that kind of granularity is not required - // So if there's a modified leaf, mark current complex node also as modification - java.util.Optional leavesModified = currentCandidate.getChildNodes().stream() - .filter(ModificationDiff::isLeaf) - // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug - // and that messes up our modifications collection here, so we need to skip - .filter(ModificationDiff::isModification) - .map(child -> LEAF_MODIFICATIONS.contains(child.getModificationType())) - .reduce((boolOne, boolTwo) -> boolOne || boolTwo); - - if (leavesModified.isPresent() && leavesModified.get()) { - return ModificationDiff.create(yangIid, currentCandidate); - // TODO HONEYCOMB-94 process children recursively to get modifications for child nodes even if current - // was modified - } else { - // SUBTREE MODIFIED, no modification on current, but process children recursively - return currentCandidate.getChildNodes().stream() - // not interested in modifications to leaves - .filter(child -> !isLeaf(child)) - .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate)) - .reduce(ModificationDiff::merge) - .orElse(EMPTY_DIFF); - } - } - default: - throw new IllegalStateException("Unknown modification type: " - + currentCandidate.getModificationType() + ". Unsupported"); - } - } + return dataObjectBefore == null && dataObjectAfter == null + ? null + : DataObjectUpdate.create(baId, dataObjectBefore, dataObjectAfter); + } - /** - * Check whether candidate.before and candidate.after is different. If not - * return false. - */ - private static boolean isModification(final DataTreeCandidateNode candidateNode) { - if (candidateNode.getDataBefore().isPresent()) { - if (candidateNode.getDataAfter().isPresent()) { - return !candidateNode.getDataAfter().get().equals(candidateNode.getDataBefore().get()); - } else { - return true; - } + @Nullable + private static DataObject getDataObject(@Nonnull final BindingNormalizedNodeSerializer serializer, + @Nullable final NormalizedNode data, + @Nonnull final YangInstanceIdentifier id) { + DataObject dataObject = null; + if (data != null) { + final Map.Entry, DataObject> dataObjectEntry = + serializer.fromNormalizedNode(id, data); + if (dataObjectEntry != null) { + dataObject = dataObjectEntry.getValue(); } - - // considering not a modification if data after is also null - return candidateNode.getDataAfter().isPresent(); - } - - /** - * Check whether candidate node is for a leaf type node. - */ - private static boolean isLeaf(final DataTreeCandidateNode candidateNode) { - // orNull intentional, some candidate nodes have both data after and data before null - return candidateNode.getDataAfter().orNull() instanceof LeafNode - || candidateNode.getDataBefore().orNull() instanceof LeafNode; - } - - @Override - public String toString() { - return "ModificationDiff{" - + "modificationsBefore=" + modificationsBefore - + ", modificationsAfter=" + modificationsAfter - + '}'; } + return dataObject; } + } diff --git a/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java new file mode 100644 index 000000000..abc0062de --- /dev/null +++ b/v3po/data-impl/src/main/java/io/fd/honeycomb/v3po/data/impl/ModificationDiff.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.data.impl; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode; +import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; +import org.opendaylight.yangtools.yang.data.api.schema.MixinNode; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType; + +/** + * Recursively collects and provides all unique and non-null modifications (modified normalized nodes). + */ +final class ModificationDiff { + + private static final ModificationDiff EMPTY_DIFF = new ModificationDiff(Collections.emptyMap()); + private static final EnumSet LEAF_MODIFICATIONS = EnumSet.of(ModificationType.WRITE, ModificationType.DELETE); + + private final Map updates; + + private ModificationDiff(@Nonnull Map updates) { + this.updates = updates; + } + + /** + * Get processed modifications. + * + * @return mapped modifications, where key is keyed {@link YangInstanceIdentifier}. + */ + Map getUpdates() { + return updates; + } + + private ModificationDiff merge(final ModificationDiff other) { + if (this == EMPTY_DIFF) { + return other; + } + + if (other == EMPTY_DIFF) { + return this; + } + + return new ModificationDiff(join(updates, other.updates)); + } + + private static Map join(Map first, + Map second) { + final Map merged = new HashMap<>(); + merged.putAll(first); + merged.putAll(second); + return merged; + } + + private static ModificationDiff create(YangInstanceIdentifier id, DataTreeCandidateNode candidate) { + return new ModificationDiff(ImmutableMap.of(id, NormalizedNodeUpdate.create(id, candidate))); + } + + /** + * Produce an aggregated diff from a candidate node recursively. MixinNodes are ignored as modifications and so + * are complex nodes which direct leaves were not modified. + */ + @Nonnull + static ModificationDiff recursivelyFromCandidate(@Nonnull final YangInstanceIdentifier yangIid, + @Nonnull final DataTreeCandidateNode currentCandidate) { + // recursively process child nodes for exact modifications + return recursivelyChildrenFromCandidate(yangIid, currentCandidate) + // also add modification on current level, if elligible + .merge(isModification(currentCandidate) + ? ModificationDiff.create(yangIid, currentCandidate) + : EMPTY_DIFF); + } + + /** + * Check whether current node was modified. {@link MixinNode}s are ignored + * and only nodes which direct leaves(or choices) are modified are considered a modification. + */ + private static Boolean isModification(@Nonnull final DataTreeCandidateNode currentCandidate) { + // Mixin nodes are not considered modifications + if (isMixin(currentCandidate) && !isAugment(currentCandidate)) { + return false; + } else { + return isCurrentModified(currentCandidate); + } + } + + private static Boolean isCurrentModified(final @Nonnull DataTreeCandidateNode currentCandidate) { + // Check if there are any modified leaves and if so, consider current node as modified + final Boolean directLeavesModified = currentCandidate.getChildNodes().stream() + .filter(ModificationDiff::isLeaf) + // For some reason, we get modifications on unmodified list keys TODO debug and report ODL bug + // and that messes up our modifications collection here, so we need to skip + .filter(ModificationDiff::isBeforeAndAfterDifferent) + .filter(child -> LEAF_MODIFICATIONS.contains(child.getModificationType())) + .findFirst() + .isPresent(); + + return directLeavesModified + // Also check choices (choices do not exist in BA world and if anything within a choice was modified, + // consider its parent as being modified) + || currentCandidate.getChildNodes().stream() + .filter(ModificationDiff::isChoice) + // Recursively check each choice if there was any change to it + .filter(ModificationDiff::isCurrentModified) + .findFirst() + .isPresent(); + } + + /** + * Process all non-leaf child nodes recursively, creating aggregated {@link ModificationDiff}. + */ + private static ModificationDiff recursivelyChildrenFromCandidate(final @Nonnull YangInstanceIdentifier yangIid, + final @Nonnull DataTreeCandidateNode currentCandidate) { + // recursively process child nodes for specific modifications + return currentCandidate.getChildNodes().stream() + // not interested in modifications to leaves + .filter(child -> !isLeaf(child)) + .map(candidate -> recursivelyFromCandidate(yangIid.node(candidate.getIdentifier()), candidate)) + .reduce(ModificationDiff::merge) + .orElse(EMPTY_DIFF); + } + + /** + * Check whether candidate.before and candidate.after is different. If not return false. + */ + private static boolean isBeforeAndAfterDifferent(@Nonnull final DataTreeCandidateNode candidateNode) { + if (candidateNode.getDataBefore().isPresent()) { + return !candidateNode.getDataBefore().get().equals(candidateNode.getDataAfter().orNull()); + } + + // considering not a modification if data after is also null + return candidateNode.getDataAfter().isPresent(); + } + + /** + * Check whether candidate node is for a leaf type node. + */ + private static boolean isLeaf(final DataTreeCandidateNode candidateNode) { + // orNull intentional, some candidate nodes have both data after and data before null + return candidateNode.getDataAfter().orNull() instanceof LeafNode + || candidateNode.getDataBefore().orNull() instanceof LeafNode; + } + + /** + * Check whether candidate node is for a Mixin type node. + */ + private static boolean isMixin(final DataTreeCandidateNode candidateNode) { + // orNull intentional, some candidate nodes have both data after and data before null + return candidateNode.getDataAfter().orNull() instanceof MixinNode + || candidateNode.getDataBefore().orNull() instanceof MixinNode; + } + + /** + * Check whether candidate node is for an Augmentation type node. + */ + private static boolean isAugment(final DataTreeCandidateNode candidateNode) { + // orNull intentional, some candidate nodes have both data after and data before null + return candidateNode.getDataAfter().orNull() instanceof AugmentationNode + || candidateNode.getDataBefore().orNull() instanceof AugmentationNode; + } + + /** + * Check whether candidate node is for a Choice type node. + */ + private static boolean isChoice(final DataTreeCandidateNode candidateNode) { + // orNull intentional, some candidate nodes have both data after and data before null + return candidateNode.getDataAfter().orNull() instanceof ChoiceNode + || candidateNode.getDataBefore().orNull() instanceof ChoiceNode; + } + + @Override + public String toString() { + return "ModificationDiff{updates=" + updates + '}'; + } + + /** + * Update to a normalized node identifiable by its {@link YangInstanceIdentifier}. + */ + static final class NormalizedNodeUpdate { + + @Nonnull + private final YangInstanceIdentifier id; + @Nullable + private final NormalizedNode dataBefore; + @Nullable + private final NormalizedNode dataAfter; + + private NormalizedNodeUpdate(@Nonnull final YangInstanceIdentifier id, + @Nullable final NormalizedNode dataBefore, + @Nullable final NormalizedNode dataAfter) { + this.id = checkNotNull(id); + this.dataAfter = dataAfter; + this.dataBefore = dataBefore; + } + + @Nullable + public NormalizedNode getDataBefore() { + return dataBefore; + } + + @Nullable + public NormalizedNode getDataAfter() { + return dataAfter; + } + + @Nonnull + public YangInstanceIdentifier getId() { + return id; + } + + static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id, + @Nonnull final DataTreeCandidateNode candidate) { + return create(id, candidate.getDataBefore().orNull(), candidate.getDataAfter().orNull()); + } + + static NormalizedNodeUpdate create(@Nonnull final YangInstanceIdentifier id, + @Nullable final NormalizedNode dataBefore, + @Nullable final NormalizedNode dataAfter) { + checkArgument(!(dataBefore == null && dataAfter == null), "Both before and after data are null"); + return new NormalizedNodeUpdate(id, dataBefore, dataAfter); + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + final NormalizedNodeUpdate that = (NormalizedNodeUpdate) other; + + return id.equals(that.id); + + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return "NormalizedNodeUpdate{" + "id=" + id + + ", dataBefore=" + dataBefore + + ", dataAfter=" + dataAfter + + '}'; + } + } + +} diff --git a/v3po/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java b/v3po/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java index 3e871e09a..eabcdcbc8 100644 --- a/v3po/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java +++ b/v3po/data-impl/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/data/impl/rev160411/ConfigDataTreeModule.java @@ -38,8 +38,8 @@ public class ConfigDataTreeModule extends public java.lang.AutoCloseable createInstance() { LOG.debug("ConfigDataTreeModule.createInstance()"); return new CloseableConfigDataTree( - new ModifiableDataTreeDelegator(getSerializerDependency(), getDataTreeDependency(), getWriterRegistryDependency(), - getContextBindingBrokerDependency())); + new ModifiableDataTreeDelegator(getSerializerDependency(), getDataTreeDependency(), + getWriterRegistryBuilderDependency().build(), getContextBindingBrokerDependency())); } private static final class CloseableConfigDataTree implements ModifiableDataManager, AutoCloseable { diff --git a/v3po/data-impl/src/main/yang/data-impl.yang b/v3po/data-impl/src/main/yang/data-impl.yang index 0ea76cf0e..922846371 100644 --- a/v3po/data-impl/src/main/yang/data-impl.yang +++ b/v3po/data-impl/src/main/yang/data-impl.yang @@ -108,11 +108,11 @@ module data-impl { } } - container writer-registry { + container writer-registry-builder { uses config:service-ref { refine type { mandatory true; - config:required-identity tapi:honeycomb-writer-registry; + config:required-identity tapi:honeycomb-writer-registry-builder; } } } diff --git a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java index 086636de6..c2653661a 100644 --- a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java +++ b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModifiableDataTreeDelegatorTest.java @@ -16,27 +16,34 @@ package io.fd.honeycomb.v3po.data.impl; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyMap; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.MockitoAnnotations.initMocks; import com.google.common.base.Optional; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.Futures; import io.fd.honeycomb.v3po.data.DataModification; import io.fd.honeycomb.v3po.translate.TranslationException; +import io.fd.honeycomb.v3po.translate.write.DataObjectUpdate; import io.fd.honeycomb.v3po.translate.write.WriteContext; import io.fd.honeycomb.v3po.translate.write.WriterRegistry; +import java.util.AbstractMap; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -46,18 +53,14 @@ import org.mockito.Mock; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; -import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild; +import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; -import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; -import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode; -import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType; public class ModifiableDataTreeDelegatorTest { @@ -65,112 +68,66 @@ public class ModifiableDataTreeDelegatorTest { private WriterRegistry writer; @Mock private BindingNormalizedNodeSerializer serializer; - @Mock private DataTree dataTree; @Mock private org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification modification; @Mock private DataBroker contextBroker; + @Mock + private org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction tx; private ModifiableDataTreeManager configDataTree; + static final InstanceIdentifier DEFAULT_ID = InstanceIdentifier.create(DataObject.class); + static DataObject DEFAULT_DATA_OBJECT = mockDataObject("serialized", DataObject.class); + @Before - public void setUp() { + public void setUp() throws Exception { initMocks(this); - configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, writer, contextBroker); - } - - @Test - public void testRead() throws Exception { - final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot - snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class); - when(dataTree.takeSnapshot()).thenReturn(snapshot); - when(snapshot.newModification()).thenReturn(modification); + dataTree = ModificationDiffTest.getDataTree(); + when(contextBroker.newReadWriteTransaction()).thenReturn(tx); + when(tx.submit()).thenReturn(Futures.immediateCheckedFuture(null)); - final YangInstanceIdentifier path = mock(YangInstanceIdentifier.class); - final Optional node = mock(Optional.class); - doReturn(node).when(modification).readNode(path); + when(serializer.fromYangInstanceIdentifier(any(YangInstanceIdentifier.class))).thenReturn(((InstanceIdentifier) DEFAULT_ID)); + final Map.Entry, DataObject> parsed = new AbstractMap.SimpleEntry<>(DEFAULT_ID, DEFAULT_DATA_OBJECT); + when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), any(NormalizedNode.class))).thenReturn(parsed); - final DataModification dataTreeSnapshot = configDataTree.newModification(); - final CheckedFuture>, ReadFailedException> future = - dataTreeSnapshot.read(path); - - verify(dataTree, times(2)).takeSnapshot(); - verify(modification).readNode(path); - - assertTrue(future.isDone()); - assertEquals(node, future.get()); + configDataTree = new ModifiableDataTreeDelegator(serializer, dataTree, writer, contextBroker); } @Test - public void testNewModification() throws Exception { - final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot - snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class); - when(dataTree.takeSnapshot()).thenReturn(snapshot); - when(snapshot.newModification()).thenReturn(modification); - - configDataTree.newModification(); - // Snapshot captured twice, so that original data could be provided to translation layer without any possible - // modification - verify(dataTree, times(2)).takeSnapshot(); - verify(snapshot, times(2)).newModification(); + public void testRead() throws Exception { + final ContainerNode topContainer = ModificationDiffTest.getTopContainer("topContainer"); + ModificationDiffTest.addNodeToTree(dataTree, topContainer, ModificationDiffTest.TOP_CONTAINER_ID); + final CheckedFuture>, ReadFailedException> read = + configDataTree.read(ModificationDiffTest.TOP_CONTAINER_ID); + final CheckedFuture>, ReadFailedException> read2 = + configDataTree.newModification().read(ModificationDiffTest.TOP_CONTAINER_ID); + final Optional> normalizedNodeOptional = read.get(); + final Optional> normalizedNodeOptional2 = read2.get(); + + assertEquals(normalizedNodeOptional, normalizedNodeOptional2); + assertTrue(normalizedNodeOptional.isPresent()); + assertEquals(topContainer, normalizedNodeOptional.get()); + assertEquals(dataTree.takeSnapshot().readNode(ModificationDiffTest.TOP_CONTAINER_ID), normalizedNodeOptional); } @Test public void testCommitSuccessful() throws Exception { - final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot - snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class); - when(dataTree.takeSnapshot()).thenReturn(snapshot); - when(snapshot.newModification()).thenReturn(modification); - final DataTreeCandidate prepare = mock(DataTreeCandidate.class); - doReturn(prepare).when(dataTree).prepare(modification); - - final org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction ctxTransaction = mock( - org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction.class); - doReturn(ctxTransaction).when(contextBroker).newReadWriteTransaction(); - doReturn(Futures.immediateCheckedFuture(null)).when(ctxTransaction).submit(); - - final DataObject dataBefore = mockDataObject("before", Ethernet.class); - final DataObject dataAfter = mockDataObject("after", Ethernet.class); - - // Prepare modification: - final DataTreeCandidateNode rootNode = mockRootNode(); - doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType(); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier(); - final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier(); - doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes(); - doReturn(ModificationType.WRITE).when(childNode).getModificationType(); - - // data before: - final ContainerNode nodeBefore = mockContainerNode(dataBefore); - when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); - // data after: - final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter)); - - // Run the test - doReturn(rootNode).when(prepare).getRootNode(); + final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue"); + final DataModification dataModification = configDataTree.newModification(); + dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList); + dataModification.validate(); dataModification.commit(); - // Verify all changes were processed: - verify(writer).update( - mapOf(dataBefore, Ethernet.class), - mapOf(dataAfter, Ethernet.class), - any(WriteContext.class)); - - // Verify modification was validated - verify(dataTree).validate(modification); - // Verify context transaction was finished - verify(ctxTransaction).submit(); - } - - private Map, DataObject> mapOf(final DataObject dataBefore, final Class type) { - return eq(Collections.singletonMap(InstanceIdentifier.create(type),dataBefore)); + final Multimap, DataObjectUpdate> map = HashMultimap.create(); + map.put(DEFAULT_ID, DataObjectUpdate.create(DEFAULT_ID, DEFAULT_DATA_OBJECT, DEFAULT_DATA_OBJECT)); + verify(writer).update(eq(new WriterRegistry.DataObjectUpdates(map, ImmutableMultimap.of())), any(WriteContext.class)); + assertEquals(nestedList, dataTree.takeSnapshot().readNode(ModificationDiffTest.NESTED_LIST_ID).get()); } - private DataObject mockDataObject(final String name, final Class classToMock) { + private static DataObject mockDataObject(final String name, final Class classToMock) { final DataObject dataBefore = mock(classToMock, name); doReturn(classToMock).when(dataBefore).getImplementedInterface(); return dataBefore; @@ -178,171 +135,135 @@ public class ModifiableDataTreeDelegatorTest { @Test public void testCommitUndoSuccessful() throws Exception { - final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot - snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class); - when(dataTree.takeSnapshot()).thenReturn(snapshot); - when(snapshot.newModification()).thenReturn(modification); - final DataTreeCandidate prepare = mock(DataTreeCandidate.class); - doReturn(prepare).when(dataTree).prepare(modification); - - // Prepare data changes: - final DataObject dataBefore = mockDataObject("before", Ethernet.class); - final DataObject dataAfter = mockDataObject("after", Ethernet.class); - - final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter reverter = mock( - io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.class); + final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue"); // Fail on update: + final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class); final TranslationException failedOnUpdateException = new TranslationException("update failed"); - doThrow(new io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter, - failedOnUpdateException)).when(writer).update(anyMap(), anyMap(), any(WriteContext.class)); - - // Prepare modification: - final DataTreeCandidateNode rootNode = mockRootNode(); - doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType(); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier(); - final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier(); - doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes(); - doReturn(ModificationType.WRITE).when(childNode).getModificationType(); - - // data before: - final ContainerNode nodeBefore = mockContainerNode(dataBefore); - when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); - // data after: - final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter)); - - // Run the test + doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException)) + .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class)); + try { - doReturn(rootNode).when(prepare).getRootNode(); + // Run the test final DataModification dataModification = configDataTree.newModification(); + dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList); + dataModification.validate(); dataModification.commit(); - } catch (io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException e) { - verify(writer).update(anyMap(), anyMap(), any(WriteContext.class)); + fail("WriterRegistry.BulkUpdateException was expected"); + } catch (WriterRegistry.BulkUpdateException e) { + verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class)); + assertThat(e.getFailedIds(), hasItem(DEFAULT_ID)); verify(reverter).revert(); assertEquals(failedOnUpdateException, e.getCause()); - return; } - - fail("WriterRegistry.BulkUpdateException was expected"); } @Test public void testCommitUndoFailed() throws Exception { - final org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot - snapshot = mock(org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot.class); - when(dataTree.takeSnapshot()).thenReturn(snapshot); - when(snapshot.newModification()).thenReturn(modification); - final DataTreeCandidate prepare = mock(DataTreeCandidate.class); - doReturn(prepare).when(dataTree).prepare(modification); - - // Prepare data changes: - final DataObject dataBefore = mockDataObject("before", Ethernet.class); - final DataObject dataAfter = mockDataObject("after", Ethernet.class); - - final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter reverter = mock( - io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.class); + final MapNode nestedList = ModificationDiffTest.getNestedList("listEntry", "listValue"); // Fail on update: - doThrow(new io.fd.honeycomb.v3po.translate.write.WriterRegistry.BulkUpdateException(InstanceIdentifier.create(Ethernet.class), reverter, - new TranslationException("update failed"))).when(writer).update(anyMap(), anyMap(), any(WriteContext.class)); + final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class); + final TranslationException failedOnUpdateException = new TranslationException("update failed"); + doThrow(new WriterRegistry.BulkUpdateException(Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException)) + .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class)); // Fail on revert: - final TranslationException failedOnRevertException = new TranslationException("update failed"); - final io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException revertFailedException = - new io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException(Collections.>emptyList(), - failedOnRevertException); - doThrow(revertFailedException).when(reverter).revert(); - - // Prepare modification: - final DataTreeCandidateNode rootNode = mockRootNode(); - doReturn(ModificationType.SUBTREE_MODIFIED).when(rootNode).getModificationType(); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(rootNode).getIdentifier(); - final DataTreeCandidateNode childNode = mock(DataTreeCandidateNode.class); - doReturn(mock(YangInstanceIdentifier.PathArgument.class)).when(childNode).getIdentifier(); - doReturn(Collections.singleton(childNode)).when(rootNode).getChildNodes(); - doReturn(ModificationType.WRITE).when(childNode).getModificationType(); - - // data before: - final ContainerNode nodeBefore = mockContainerNode(dataBefore); - when(childNode.getDataBefore()).thenReturn(Optional.fromNullable(nodeBefore)); - // data after: - final ContainerNode nodeAfter = mockContainerNode(dataAfter); - when(childNode.getDataAfter()).thenReturn(Optional.fromNullable(nodeAfter)); - - // Run the test + final TranslationException failedOnRevertException = new TranslationException("revert failed"); + doThrow(new WriterRegistry.Reverter.RevertFailedException(Collections.emptySet(), failedOnRevertException)) + .when(reverter).revert(); + try { - doReturn(rootNode).when(prepare).getRootNode(); + // Run the test final DataModification dataModification = configDataTree.newModification(); + dataModification.write(ModificationDiffTest.NESTED_LIST_ID, nestedList); + dataModification.validate(); dataModification.commit(); - } catch (io.fd.honeycomb.v3po.translate.write.WriterRegistry.Reverter.RevertFailedException e) { - verify(writer).update(anyMap(), anyMap(), any(WriteContext.class)); + fail("WriterRegistry.Reverter.RevertFailedException was expected"); + } catch (WriterRegistry.Reverter.RevertFailedException e) { + verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class)); verify(reverter).revert(); assertEquals(failedOnRevertException, e.getCause()); - return; } - - fail("RevertFailedException was expected"); } + private abstract static class DataObject1 implements DataObject {} + private abstract static class DataObject2 implements DataObject {} + private abstract static class DataObject3 implements DataObject {} + @Test - public void testChildrenFromNormalized() throws Exception { - final BindingNormalizedNodeSerializer serializer = mock(BindingNormalizedNodeSerializer.class); - - final Map> map = new HashMap<>(); - - // init child1 (will not be serialized) - final DataContainerChild child1 = mock(DataContainerChild.class); - when(child1.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class)); - when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child1))).thenReturn(null); - map.put(mock(YangInstanceIdentifier.class), child1); - - // init child 2 (will be serialized) - final DataContainerChild child2 = mock(DataContainerChild.class); - when(child2.getIdentifier()).thenReturn(mock(YangInstanceIdentifier.PathArgument.class)); - - final Map.Entry entry = mock(Map.Entry.class); - final InstanceIdentifier id = mock(InstanceIdentifier.class); - doReturn(id).when(entry).getKey(); - final DataObject data = mock(DataObject.class); - doReturn(data).when(entry).getValue(); - when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(child2))).thenReturn(entry); - map.put(mock(YangInstanceIdentifier.class), child2); - - // run tested method - final Map, DataObject> baMap = - ModifiableDataTreeDelegator.toBindingAware(map, serializer); - assertEquals(1, baMap.size()); - assertEquals(data, baMap.get(id)); + public void testToBindingAware() throws Exception { + when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null); + + final Map biNodes = new HashMap<>(); + // delete + final QName nn1 = QName.create("namespace", "nn1"); + final YangInstanceIdentifier yid1 = mockYid(nn1); + final InstanceIdentifier iid1 = mockIid(yid1, DataObject1.class); + final NormalizedNode nn1B = mockNormalizedNode(nn1); + final DataObject1 do1B = mockDataObject(yid1, iid1, nn1B, DataObject1.class); + biNodes.put(yid1, ModificationDiff.NormalizedNodeUpdate.create(yid1, nn1B, null)); + + // create + final QName nn2 = QName.create("namespace", "nn1"); + final YangInstanceIdentifier yid2 = mockYid(nn2); + final InstanceIdentifier iid2 = mockIid(yid2, DataObject2.class);; + final NormalizedNode nn2A = mockNormalizedNode(nn2); + final DataObject2 do2A = mockDataObject(yid2, iid2, nn2A, DataObject2.class); + biNodes.put(yid2, ModificationDiff.NormalizedNodeUpdate.create(yid2, null, nn2A)); + + // update + final QName nn3 = QName.create("namespace", "nn1"); + final YangInstanceIdentifier yid3 = mockYid(nn3); + final InstanceIdentifier iid3 = mockIid(yid3, DataObject3.class); + final NormalizedNode nn3B = mockNormalizedNode(nn3); + final DataObject3 do3B = mockDataObject(yid3, iid3, nn3B, DataObject3.class); + final NormalizedNode nn3A = mockNormalizedNode(nn3); + final DataObject3 do3A = mockDataObject(yid3, iid3, nn3A, DataObject3.class);; + biNodes.put(yid3, ModificationDiff.NormalizedNodeUpdate.create(yid3, nn3B, nn3A)); + + final WriterRegistry.DataObjectUpdates dataObjectUpdates = + ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer); + + assertThat(dataObjectUpdates.getDeletes().size(), is(1)); + assertThat(dataObjectUpdates.getDeletes().keySet(), hasItem(((InstanceIdentifier) iid1))); + assertThat(dataObjectUpdates.getDeletes().values(), hasItem( + ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid1, do1B, null)))); + + assertThat(dataObjectUpdates.getUpdates().size(), is(2)); + assertThat(dataObjectUpdates.getUpdates().keySet(), hasItems((InstanceIdentifier) iid2, (InstanceIdentifier) iid3)); + assertThat(dataObjectUpdates.getUpdates().values(), hasItems( + DataObjectUpdate.create(iid2, null, do2A), + DataObjectUpdate.create(iid3, do3B, do3A))); + + assertThat(dataObjectUpdates.getTypeIntersection().size(), is(3)); } - private DataTreeCandidateNode mockRootNode() { - final DataTreeCandidate candidate = mock(DataTreeCandidate.class); - when(dataTree.prepare(modification)).thenReturn(candidate); - - final DataTreeCandidateNode rootNode = mock(DataTreeCandidateNode.class); - when(candidate.getRootNode()).thenReturn(rootNode); - - return rootNode; + private D mockDataObject(final YangInstanceIdentifier yid1, + final InstanceIdentifier iid1, + final NormalizedNode nn1B, + final Class type) { + final D do1B = mock(type); + when(serializer.fromNormalizedNode(yid1, nn1B)).thenReturn(new AbstractMap.SimpleEntry<>(iid1, do1B)); + return do1B; } - private ContainerNode mockContainerNode(DataObject modification) { - final YangInstanceIdentifier.NodeIdentifier identifier = - YangInstanceIdentifier.NodeIdentifier.create(QName.create("/")); - - final ContainerNode node = mock(ContainerNode.class); - when(node.getIdentifier()).thenReturn(identifier); - - final Map.Entry entry = mock(Map.Entry.class); - final Class implementedInterface = - (Class) modification.getImplementedInterface(); - final InstanceIdentifier id = InstanceIdentifier.create(implementedInterface); + private NormalizedNode mockNormalizedNode(final QName nn1) { + final NormalizedNode nn1B = mock(NormalizedNode.class); + when(nn1B.getNodeType()).thenReturn(nn1); + return nn1B; + } - doReturn(id).when(entry).getKey(); - doReturn(modification).when(entry).getValue(); - doReturn(entry).when(serializer).fromNormalizedNode(any(YangInstanceIdentifier.class), eq(node)); + private InstanceIdentifier mockIid(final YangInstanceIdentifier yid1, + final Class type) { + final InstanceIdentifier iid1 = InstanceIdentifier.create(type); + when(serializer.fromYangInstanceIdentifier(yid1)).thenReturn(iid1); + return iid1; + } - return node; + private YangInstanceIdentifier mockYid(final QName nn1) { + final YangInstanceIdentifier yid1 = mock(YangInstanceIdentifier.class); + when(yid1.getLastPathArgument()).thenReturn(new YangInstanceIdentifier.NodeIdentifier(nn1)); + return yid1; } } diff --git a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java index 3475973d6..bc7582e93 100644 --- a/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java +++ b/v3po/data-impl/src/test/java/io/fd/honeycomb/v3po/data/impl/ModificationDiffTest.java @@ -1,7 +1,8 @@ package io.fd.honeycomb.v3po.data.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; import java.util.Map; import org.junit.Test; @@ -11,6 +12,8 @@ import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode; import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree; +import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateTip; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification; import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot; @@ -28,15 +31,23 @@ import org.opendaylight.yangtools.yang.parser.stmt.rfc6020.YangStatementSourceIm public class ModificationDiffTest { - private static final QName TOP_CONTAINER_QNAME = + static final QName TOP_CONTAINER_QNAME = QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "top-container"); - private static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string"); - private static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name"); - private static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text"); - private static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list"); - private static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list"); + static final QName STRING_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "string"); + static final QName NAME_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "name"); + static final QName TEXT_LEAF_QNAME = QName.create(TOP_CONTAINER_QNAME, "text"); + static final QName NESTED_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "nested-list"); + static final QName DEEP_LIST_QNAME = QName.create(TOP_CONTAINER_QNAME, "deep-list"); + + static final QName WITH_CHOICE_CONTAINER_QNAME = + QName.create("urn:opendaylight:params:xml:ns:yang:test:diff", "2015-01-05", "with-choice"); + static final QName CHOICE_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "choice"); + static final QName IN_CASE1_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case1"); + static final QName IN_CASE2_LEAF_QNAME = QName.create(WITH_CHOICE_CONTAINER_QNAME, "in-case2"); + + static final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME); + static final YangInstanceIdentifier NESTED_LIST_ID = TOP_CONTAINER_ID.node(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME)); - private static final YangInstanceIdentifier TOP_CONTAINER_ID = YangInstanceIdentifier.of(TOP_CONTAINER_QNAME); @Test public void testInitialWrite() throws Exception { @@ -47,10 +58,33 @@ public class ModificationDiffTest { dataTreeModification.write(TOP_CONTAINER_ID, topContainer); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); + final ModificationDiff modificationDiff = getModificationDiff(prepare); - assertTrue(modificationDiff.getModificationsBefore().isEmpty()); - assertAfter(topContainer, TOP_CONTAINER_ID, modificationDiff); + assertThat(modificationDiff.getUpdates().size(), is(1)); + assertThat(modificationDiff.getUpdates().values().size(), is(1)); + assertUpdate(modificationDiff.getUpdates().values().iterator().next(), TOP_CONTAINER_ID, null, topContainer); + } + + @Test + public void testInitialWriteForContainerWithChoice() throws Exception { + final TipProducingDataTree dataTree = getDataTree(); + final DataTreeModification dataTreeModification = getModification(dataTree); + final ContainerNode containerWithChoice = Builders.containerBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(WITH_CHOICE_CONTAINER_QNAME)) + .withChild(Builders.choiceBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(CHOICE_QNAME)) + .withChild(ImmutableNodes.leafNode(IN_CASE1_LEAF_QNAME, "withinCase1")) + .build()) + .build(); + final YangInstanceIdentifier WITH_CHOICE_CONTAINER_ID = YangInstanceIdentifier.of(WITH_CHOICE_CONTAINER_QNAME); + dataTreeModification.write(WITH_CHOICE_CONTAINER_ID, containerWithChoice); + final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); + + final Map updates = getModificationDiff(prepare).getUpdates(); + + assertThat(updates.size(), is(1)); + assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class), + WITH_CHOICE_CONTAINER_ID, null, containerWithChoice); } private DataTreeModification getModification(final TipProducingDataTree dataTree) { @@ -66,10 +100,9 @@ public class ModificationDiffTest { dataTreeModification.write(TOP_CONTAINER_ID, topContainer); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); + final ModificationDiff modificationDiff = getModificationDiff(prepare); - assertTrue(modificationDiff.getModificationsBefore().isEmpty()); - assertTrue(modificationDiff.getModificationsAfter().isEmpty()); + assertThat(modificationDiff.getUpdates().size(), is(0)); } private DataTreeCandidateTip prepareModification(final TipProducingDataTree dataTree, @@ -83,60 +116,61 @@ public class ModificationDiffTest { @Test public void testUpdateWrite() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - final NormalizedNode topContainerBefore = addTopContainer(dataTree); + final ContainerNode topContainer = getTopContainer("string1"); + addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID); final DataTreeModification dataTreeModification = getModification(dataTree); final NormalizedNode topContainerAfter = getTopContainer("string2"); dataTreeModification.write(TOP_CONTAINER_ID, topContainerAfter); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); + final Map updates = getModificationDiff(prepare).getUpdates(); - assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff); - assertAfter(topContainerAfter, TOP_CONTAINER_ID, modificationDiff); + assertThat(updates.size(), is(1)); + assertThat(updates.values().size(), is(1)); + assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter); } - private ModifiableDataTreeDelegator.ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) { - return ModifiableDataTreeDelegator.ModificationDiff - .recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode()); + private ModificationDiff getModificationDiff(final DataTreeCandidateTip prepare) { + return ModificationDiff.recursivelyFromCandidate(YangInstanceIdentifier.EMPTY, prepare.getRootNode()); } @Test public void testUpdateMerge() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - final NormalizedNode topContainerBefore = addTopContainer(dataTree); + final ContainerNode topContainer = getTopContainer("string1"); + addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID); final DataTreeModification dataTreeModification = getModification(dataTree); final NormalizedNode topContainerAfter = getTopContainer("string2"); dataTreeModification.merge(TOP_CONTAINER_ID, topContainerAfter); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = - getModificationDiff(prepare); - - assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff); - assertAfter(topContainerAfter, TOP_CONTAINER_ID, modificationDiff); + final Map updates = getModificationDiff(prepare).getUpdates(); + assertThat(updates.size(), is(1)); + assertThat(updates.values().size(), is(1)); + assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, topContainerAfter); } @Test public void testUpdateDelete() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - final NormalizedNode topContainerBefore = addTopContainer(dataTree); + final ContainerNode topContainer = getTopContainer("string1"); + addNodeToTree(dataTree, topContainer, TOP_CONTAINER_ID); final DataTreeModification dataTreeModification = getModification(dataTree); dataTreeModification.delete(TOP_CONTAINER_ID); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); - - assertBefore(topContainerBefore, TOP_CONTAINER_ID, modificationDiff); - assertTrue(modificationDiff.getModificationsAfter().isEmpty()); + final Map updates = getModificationDiff(prepare).getUpdates(); + assertThat(updates.size(), is(1)); + assertThat(updates.values().size(), is(1)); + assertUpdate(updates.values().iterator().next(), TOP_CONTAINER_ID, topContainer, null); } @Test public void testWriteAndUpdateInnerList() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - addTopContainer(dataTree); DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot(); DataTreeModification dataTreeModification = dataTreeSnapshot.newModification(); @@ -146,15 +180,17 @@ public class ModificationDiffTest { new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME)); final MapNode mapNode = getNestedList("name1", "text"); + final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier()); dataTreeModification.write(listId, mapNode); dataTreeModification.ready(); dataTree.validate(dataTreeModification); DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification); - ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); + Map updates = getModificationDiff(prepare).getUpdates(); - assertTrue(modificationDiff.getModificationsBefore().isEmpty()); - assertAfter(mapNode, listId, modificationDiff); + assertThat(updates.size(), is(1)); + assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), + listEntryId, null, mapNode.getValue().iterator().next()); // Commit so that update can be tested next dataTree.commit(prepare); @@ -171,9 +207,17 @@ public class ModificationDiffTest { dataTree.validate(dataTreeModification); prepare = dataTree.prepare(dataTreeModification); - modificationDiff = getModificationDiff(prepare); - assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff); - assertAfter(mapEntryNode, listItemId, modificationDiff); + updates = getModificationDiff(prepare).getUpdates(); + assertThat(updates.size(), is(1 /*Actual list entry*/)); + } +// + private void assertUpdate(final ModificationDiff.NormalizedNodeUpdate update, + final YangInstanceIdentifier idExpected, + final NormalizedNode beforeExpected, + final NormalizedNode afterExpected) { + assertThat(update.getId(), is(idExpected)); + assertThat(update.getDataBefore(), is(beforeExpected)); + assertThat(update.getDataAfter(), is(afterExpected)); } @Test @@ -192,25 +236,44 @@ public class ModificationDiffTest { new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME)); final MapNode mapNode = getNestedList("name1", "text"); + final YangInstanceIdentifier listEntryId = listId.node(mapNode.getValue().iterator().next().getIdentifier()); + dataTreeModification.write(listId, mapNode); final DataTreeCandidateTip prepare = prepareModification(dataTree, dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); + final Map updates = getModificationDiff(prepare).getUpdates(); + + assertThat(updates.size(), is(2)); + assertThat(updates.values().size(), is(2)); + assertUpdate(getNormalizedNodeUpdateForAfterType(updates, ContainerNode.class), TOP_CONTAINER_ID, null, + Builders.containerBuilder(topContainer).withChild(mapNode).build()); + assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), listEntryId, null, mapNode.getValue().iterator().next()); + // Assert that keys of the updates map are not wildcarded YID + assertThat(updates.keySet(), hasItems( + TOP_CONTAINER_ID, + listEntryId)); + } - assertTrue(modificationDiff.getModificationsBefore().isEmpty()); + private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForAfterType( + final Map updates, + final Class> containerNodeClass) { + return updates.values().stream() + .filter(update -> containerNodeClass.isAssignableFrom(update.getDataAfter().getClass())) + .findFirst().get(); + } - // TODO HONEYCOMB-94 2 after modifications should appear, for top-container and nested-list entry - assertAfter(Builders.containerBuilder(topContainer) - .withChild(mapNode) - .build(), - TOP_CONTAINER_ID, modificationDiff); + private ModificationDiff.NormalizedNodeUpdate getNormalizedNodeUpdateForBeforeType( + final Map updates, + final Class> containerNodeClass) { + return updates.values().stream() + .filter(update -> containerNodeClass.isAssignableFrom(update.getDataBefore().getClass())) + .findFirst().get(); } @Test public void testWriteDeepList() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - addTopContainer(dataTree); DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot(); DataTreeModification dataTreeModification = dataTreeSnapshot.newModification(); @@ -250,7 +313,8 @@ public class ModificationDiffTest { .withChild(ImmutableNodes.leafNode(NAME_LEAF_QNAME, "name1")).build()); dataTreeModification.merge( deepListId, - Builders.mapBuilder().withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME)) + Builders.mapBuilder() + .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(DEEP_LIST_QNAME)) .build()); dataTreeModification.merge( deepListEntryId, @@ -261,16 +325,14 @@ public class ModificationDiffTest { prepare = dataTree.prepare(dataTreeModification); dataTree.commit(prepare); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); - - assertTrue(modificationDiff.getModificationsBefore().isEmpty()); - assertAfter(getDeepList("name1"), deepListId, modificationDiff); + final Map updates = getModificationDiff(prepare).getUpdates(); + assertThat(updates.size(), is(1)); + assertUpdate(getNormalizedNodeUpdateForAfterType(updates, MapEntryNode.class), deepListEntryId, null, deepListEntry); } @Test public void testDeleteInnerListItem() throws Exception { final TipProducingDataTree dataTree = getDataTree(); - addTopContainer(dataTree); DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot(); DataTreeModification dataTreeModification = dataTreeSnapshot.newModification(); @@ -298,58 +360,37 @@ public class ModificationDiffTest { dataTree.validate(dataTreeModification); prepare = dataTree.prepare(dataTreeModification); - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff = getModificationDiff(prepare); - - assertBefore(mapNode.getValue().iterator().next(), listItemId, modificationDiff); - assertTrue(modificationDiff.getModificationsAfter().isEmpty()); + final Map updates = getModificationDiff(prepare).getUpdates(); + assertThat(updates.size(), is(1)); + assertUpdate(getNormalizedNodeUpdateForBeforeType(updates, MapEntryNode.class), listItemId, mapNode.getValue().iterator().next(), null); } - private NormalizedNode addTopContainer(final TipProducingDataTree dataTree) + static void addNodeToTree(final DataTree dataTree, final NormalizedNode node, + final YangInstanceIdentifier id) throws DataValidationFailedException { DataTreeSnapshot dataTreeSnapshot = dataTree.takeSnapshot(); DataTreeModification dataTreeModification = dataTreeSnapshot.newModification(); - final NormalizedNode topContainerBefore = getTopContainer("string1"); - dataTreeModification.write(TOP_CONTAINER_ID, topContainerBefore); + dataTreeModification.write(id, node); dataTreeModification.ready(); dataTree.validate(dataTreeModification); - DataTreeCandidateTip prepare = dataTree.prepare(dataTreeModification); + DataTreeCandidate prepare = dataTree.prepare(dataTreeModification); dataTree.commit(prepare); - return topContainerBefore; - } - - private void assertAfter(final NormalizedNode topContainer, final YangInstanceIdentifier TOP_CONTAINER_ID, - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) { - assertModification(topContainer, TOP_CONTAINER_ID, modificationDiff.getModificationsAfter()); - } - - private void assertModification(final NormalizedNode topContainer, - final YangInstanceIdentifier TOP_CONTAINER_ID, - final Map> modificationMap) { - assertEquals(1, modificationMap.keySet().size()); - assertEquals(TOP_CONTAINER_ID, modificationMap.keySet().iterator().next()); - assertEquals(topContainer, modificationMap.values().iterator().next()); - } - - private void assertBefore(final NormalizedNode topContainerBefore, - final YangInstanceIdentifier TOP_CONTAINER_ID, - final ModifiableDataTreeDelegator.ModificationDiff modificationDiff) { - assertModification(topContainerBefore, TOP_CONTAINER_ID, modificationDiff.getModificationsBefore()); } - private TipProducingDataTree getDataTree() throws ReactorException { + static TipProducingDataTree getDataTree() throws ReactorException { final TipProducingDataTree dataTree = InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION); dataTree.setSchemaContext(getSchemaCtx()); return dataTree; } - private ContainerNode getTopContainer(final String stringValue) { + static ContainerNode getTopContainer(final String stringValue) { return Builders.containerBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(TOP_CONTAINER_QNAME)) .withChild(ImmutableNodes.leafNode(STRING_LEAF_QNAME, stringValue)) .build(); } - private MapNode getNestedList(final String listItemName, final String text) { + static MapNode getNestedList(final String listItemName, final String text) { return Builders.mapBuilder() .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(NESTED_LIST_QNAME)) .withChild( @@ -378,9 +419,9 @@ public class ModificationDiffTest { .build(); } - private SchemaContext getSchemaCtx() throws ReactorException { + private static SchemaContext getSchemaCtx() throws ReactorException { final CrossSourceStatementReactor.BuildAction buildAction = YangInferencePipeline.RFC6020_REACTOR.newBuild(); - buildAction.addSource(new YangStatementSourceImpl(getClass().getResourceAsStream("/test-diff.yang"))); + buildAction.addSource(new YangStatementSourceImpl(ModificationDiffTest.class.getResourceAsStream("/test-diff.yang"))); return buildAction.buildEffective(); } } \ No newline at end of file diff --git a/v3po/data-impl/src/test/resources/test-diff.yang b/v3po/data-impl/src/test/resources/test-diff.yang index 7e8721f00..5cccc8718 100644 --- a/v3po/data-impl/src/test/resources/test-diff.yang +++ b/v3po/data-impl/src/test/resources/test-diff.yang @@ -34,4 +34,21 @@ module test-diff { } } + container with-choice { + + choice choice { + case case1 { + leaf in-case1 { + type string; + } + } + + case case2 { + leaf in-case2 { + type string; + } + } + } + } + } diff --git a/v3po/features/pom.xml b/v3po/features/pom.xml index 43b5e83bc..2929e5d87 100644 --- a/v3po/features/pom.xml +++ b/v3po/features/pom.xml @@ -225,6 +225,11 @@ translate-utils ${project.version} + + org.jgrapht + jgrapht-core + 0.9.2 + ${project.groupId} vpp-translate-utils diff --git a/v3po/features/src/main/features/features.xml b/v3po/features/src/main/features/features.xml index 89c64726c..8419fbebd 100644 --- a/v3po/features/src/main/features/features.xml +++ b/v3po/features/src/main/features/features.xml @@ -38,6 +38,7 @@ mvn:io.fd.honeycomb.v3po/v3po-impl/${project.version} mvn:io.fd.honeycomb.v3po/translate-api/${project.version} mvn:io.fd.honeycomb.v3po/translate-spi/${project.version} + mvn:org.jgrapht/jgrapht-core/{{VERSION}} mvn:io.fd.honeycomb.v3po/translate-utils/${project.version} mvn:io.fd.honeycomb.v3po/vpp-translate-utils/${project.version} mvn:io.fd.honeycomb.v3po/data-api/${project.version} diff --git a/v3po/impl/src/main/config/default-config.xml b/v3po/impl/src/main/config/default-config.xml index 00c586d12..cc6c7c6e2 100644 --- a/v3po/impl/src/main/config/default-config.xml +++ b/v3po/impl/src/main/config/default-config.xml @@ -83,10 +83,10 @@ prefix:binding-dom-mapping-service runtime-mapping-singleton - - prefix:honeycomb-writer-registry - write-registry - + + prefix:honeycomb-writer-registry-builder + write-registry-builder + binding:binding-async-data-broker honeycomb-context-binding-data-broker @@ -200,6 +200,13 @@ /modules/module[type='delegating-writer-registry'][name='write-registry'] + + prefix:honeycomb-writer-registry-builder + + write-registry-builder + /modules/module[type='delegating-writer-registry'][name='write-registry'] + + prefix:data-tree diff --git a/v3po/impl/src/main/config/initializer-config.xml b/v3po/impl/src/main/config/initializer-config.xml index cb98a2a89..dd2ff2b1e 100644 --- a/v3po/impl/src/main/config/initializer-config.xml +++ b/v3po/impl/src/main/config/initializer-config.xml @@ -34,8 +34,8 @@ - prefix:noop-writer-registry - noop-writer-registry + prefix:noop-writer-registry-builder + noop-writer-registry-builder @@ -50,10 +50,10 @@ prefix:binding-dom-mapping-service runtime-mapping-singleton - - prefix:honeycomb-writer-registry - noop-writer-registry - + + prefix:honeycomb-writer-registry-builder + noop-writer-registry-builder + binding:binding-async-data-broker honeycomb-context-binding-data-broker @@ -166,10 +166,10 @@ - prefix:honeycomb-writer-registry + prefix:honeycomb-writer-registry-builder - noop-writer-registry - /modules/module[type='noop-writer-registry'][name='noop-writer-registry'] + noop-writer-registry-builder + /modules/module[type='noop-writer-registry-builder'][name='noop-writer-registry-builder'] diff --git a/v3po/translate-api/pom.xml b/v3po/translate-api/pom.xml index 8e244fa8d..1b08e85e8 100644 --- a/v3po/translate-api/pom.xml +++ b/v3po/translate-api/pom.xml @@ -19,7 +19,7 @@ io.fd.honeycomb.common impl-parent 1.0.0-SNAPSHOT - ../../common/api-parent + ../../common/impl-parent 4.0.0 diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java deleted file mode 100644 index b38f26983..000000000 --- a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ChildWriter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.write; - -import com.google.common.annotations.Beta; -import javax.annotation.Nonnull; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -/** - * Child writer allowing its parent to pass the builder object - * - * @param Specific DataObject derived type, that is handled by this writer - */ -@Beta -public interface ChildWriter extends Writer { - - /** - * Extract data object managed by this writer from parent data and perform write. - * - * @param parentId Id of parent node - * @param parentDataAfter Parent data from modification to extract data object from - * @param ctx Write context for current modification - */ - void writeChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException; - - /** - * Extract data object managed by this writer(if necessary) from parent data and perform delete. - * - * @param parentId Id of parent node - * @param parentDataBefore Parent data before modification to extract data object from - * @param ctx Write context for current modification - */ - void deleteChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, - @Nonnull final WriteContext ctx) throws WriteFailedException; - - /** - * Extract data object managed by this writer(if necessary) from parent data and perform delete. - * - * @param parentId Id of parent node - * @param parentDataBefore Parent data before modification to extract data object from - * @param parentDataAfter Parent data from modification to extract data object from - * @param ctx Write context for current modification - */ - void updateChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, - @Nonnull final DataObject parentDataAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException; -} diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java new file mode 100644 index 000000000..0d891ecba --- /dev/null +++ b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/DataObjectUpdate.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.write; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Simple wrapper for BA id + data before and after state. Does not allow both before and after to be null. + */ +public class DataObjectUpdate { + + @Nonnull + private final InstanceIdentifier id; + @Nullable + private final DataObject dataBefore; + @Nullable + private final DataObject dataAfter; + + private DataObjectUpdate(@Nonnull final InstanceIdentifier id, + @Nullable final DataObject dataBefore, + @Nullable final DataObject dataAfter) { + this.id = checkNotNull(id); + this.dataAfter = dataAfter; + this.dataBefore = dataBefore; + } + + public DataObject getDataBefore() { + return dataBefore; + } + + public DataObject getDataAfter() { + return dataAfter; + } + + public InstanceIdentifier getId() { + return id; + } + + public static DataObjectUpdate create(@Nonnull final InstanceIdentifier id, + @Nullable final DataObject dataBefore, + @Nullable final DataObject dataAfter) { + checkArgument(!(dataBefore == null && dataAfter == null), "Both before and after data are null"); + if (dataBefore != null) { + checkArgument(id.getTargetType().isAssignableFrom(dataBefore.getClass())); + } + if (dataAfter != null) { + checkArgument(id.getTargetType().isAssignableFrom(dataAfter.getClass())); + } + + return dataAfter == null + ? new DataObjectDelete(id, dataBefore) + : new DataObjectUpdate(id, dataBefore, dataAfter); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final DataObjectUpdate that = (DataObjectUpdate) o; + + return id.equals(that.id); + + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public String toString() { + return "DataObjectUpdate{" + "id=" + id + + ", dataBefore=" + dataBefore + + ", dataAfter=" + dataAfter + + '}'; + } + + public DataObjectUpdate reverse() { + return DataObjectUpdate.create(id, dataAfter, dataBefore); + } + + public static class DataObjectDelete extends DataObjectUpdate { + + private DataObjectDelete(@Nonnull final InstanceIdentifier id, + @Nullable final DataObject dataBefore) { + super(id, dataBefore, null); + } + } +} diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java new file mode 100644 index 000000000..71ecbb806 --- /dev/null +++ b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/ModifiableWriterRegistry.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.write; + +import com.google.common.annotations.Beta; +import java.util.Collection; +import java.util.Set; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Mutable registry that allows adding new writers. + */ +@Beta +public interface ModifiableWriterRegistry { + + /** + * Add a writer responsible for writing only a single complex node. + */ + ModifiableWriterRegistry addWriter(@Nonnull Writer writer); + + /** + * Add a writer responsible for writing multiple complex nodes within a subtree its responsible for. + * Identifiers for subtree nodes handled by a single writer have to be relative from {@link DataObject} that + * represents subtree root. + */ + ModifiableWriterRegistry addSubtreeWriter(@Nonnull Set> handledChildren, + @Nonnull Writer writer); + + /** + * Add a writer and make sure it will be executed before writer identifier by relatedType is executed. + */ + ModifiableWriterRegistry addWriterBefore(@Nonnull Writer writer, + @Nonnull InstanceIdentifier relatedType); + + ModifiableWriterRegistry addSubtreeWriterBefore(@Nonnull Set> handledChildren, + @Nonnull Writer writer, + @Nonnull InstanceIdentifier relatedType); + + ModifiableWriterRegistry addWriterBefore(@Nonnull Writer writer, + @Nonnull Collection> relatedTypes); + + ModifiableWriterRegistry addSubtreeWriterBefore(@Nonnull Set> handledChildren, + @Nonnull Writer writer, + @Nonnull Collection> relatedTypes); + + /** + * Add a writer and make sure it will be executed after writer identifier by relatedType is executed. + */ + ModifiableWriterRegistry addWriterAfter(@Nonnull Writer writer, + @Nonnull InstanceIdentifier relatedType); + + ModifiableWriterRegistry addSubtreeWriterAfter(@Nonnull Set> handledChildren, + @Nonnull Writer writer, + @Nonnull InstanceIdentifier relatedType); + + ModifiableWriterRegistry addWriterAfter(@Nonnull Writer writer, + @Nonnull Collection> relatedTypes); + + ModifiableWriterRegistry addSubtreeWriterAfter(@Nonnull Set> handledChildren, + @Nonnull Writer writer, + @Nonnull Collection> relatedTypes); +} diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java new file mode 100644 index 000000000..4287964db --- /dev/null +++ b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.write; + +import com.google.common.annotations.Beta; + +@Beta +public interface WriterFactory { + + /** + * Initialize 1 or more writers and add them to provided registry. + */ + void init(ModifiableWriterRegistry registry); +} diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistry.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistry.java index d30f06d13..64735017f 100644 --- a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistry.java +++ b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistry.java @@ -19,47 +19,131 @@ package io.fd.honeycomb.v3po.translate.write; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.annotations.Beta; -import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; import io.fd.honeycomb.v3po.translate.TranslationException; -import java.util.List; -import java.util.Map; +import java.util.Set; import javax.annotation.Nonnull; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; /** - * Special {@link Writer} capable of performing bulk updates + * Special {@link Writer} capable of performing bulk updates. */ @Beta public interface WriterRegistry extends Writer { /** - * Performs bulk update + * Performs bulk update. * * @throws BulkUpdateException in case bulk update fails - * @throws TranslationException in case some other error occurs while processing update request + * @throws TranslationException in case some other error occurs while processing update request */ - void update(@Nonnull final Map, DataObject> dataBefore, - @Nonnull final Map, DataObject> dataAfter, - @Nonnull final WriteContext ctx) throws TranslationException; + void update(@Nonnull DataObjectUpdates updates, + @Nonnull WriteContext ctx) throws TranslationException; + + /** + * Simple DTO containing updates for {@link WriterRegistry}. Currently only deletes and updates (create + update) + * are distinguished. + */ + @Beta + final class DataObjectUpdates { + + private final Multimap, DataObjectUpdate> updates; + private final Multimap, DataObjectUpdate.DataObjectDelete> deletes; + + /** + * Create new instance. + * + * @param updates All updates indexed by their unkeyed {@link InstanceIdentifier} + * @param deletes All deletes indexed by their unkeyed {@link InstanceIdentifier} + */ + public DataObjectUpdates(@Nonnull final Multimap, DataObjectUpdate> updates, + @Nonnull final Multimap, DataObjectUpdate.DataObjectDelete> deletes) { + this.deletes = deletes; + this.updates = updates; + } + + public Multimap, DataObjectUpdate> getUpdates() { + return updates; + } + + public Multimap, DataObjectUpdate.DataObjectDelete> getDeletes() { + return deletes; + } + + public boolean isEmpty() { + return updates.isEmpty() && deletes.isEmpty(); + } + + @Override + public String toString() { + return "DataObjectUpdates{" + "updates=" + updates + ", deletes=" + deletes + '}'; + } + + /** + * Get a {@link Set} containing all update types from both updates as well as deletes. + */ + public Set> getTypeIntersection() { + return Sets.union(deletes.keySet(), updates.keySet()); + } + + /** + * Check whether there is only a single type of data object to be updated. + * + * @return true if there is only a single type of updates (update + delete) + */ + public boolean containsOnlySingleType() { + return getTypeIntersection().size() == 1; + } + + @Override + public boolean equals(final Object other) { + if (this == other) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + + final DataObjectUpdates that = (DataObjectUpdates) other; + + if (!updates.equals(that.updates)) { + return false; + } + return deletes.equals(that.deletes); + + } + + @Override + public int hashCode() { + int result = updates.hashCode(); + result = 31 * result + deletes.hashCode(); + return result; + } + + } /** * Thrown when bulk update failed. */ @Beta - class BulkUpdateException extends WriteFailedException { + class BulkUpdateException extends TranslationException { private final Reverter reverter; + private final Set> failedIds; /** * Constructs an BulkUpdateException. - * - * @param failedId instance identifier of the data object that caused bulk update to fail. - * @param cause the cause of bulk update failure + * @param failedIds instance identifiers of the data objects that were not processed during bulk update. + * @param cause the cause of bulk update failure */ - public BulkUpdateException(@Nonnull final InstanceIdentifier failedId, @Nonnull final Reverter reverter, - final Throwable cause) { - super(failedId, "Bulk update failed at " + failedId, cause); + public BulkUpdateException(@Nonnull final Set> failedIds, + @Nonnull final Reverter reverter, + @Nonnull final Throwable cause) { + super("Bulk update failed at: " + failedIds, cause); + this.failedIds = failedIds; this.reverter = checkNotNull(reverter, "reverter should not be null"); } @@ -72,10 +156,13 @@ public interface WriterRegistry extends Writer { reverter.revert(); } + public Set> getFailedIds() { + return failedIds; + } } /** - * Abstraction over revert mechanism in case of a bulk update failure + * Abstraction over revert mechanism in case of a bulk update failure. */ @Beta interface Reverter { @@ -95,19 +182,19 @@ public interface WriterRegistry extends Writer { class RevertFailedException extends TranslationException { // TODO change to list of VppDataModifications to make debugging easier - private final List> notRevertedChanges; + private final Set> notRevertedChanges; /** - * Constructs an RevertFailedException with the list of changes that were not reverted. + * Constructs a RevertFailedException with the list of changes that were not reverted. * * @param notRevertedChanges list of changes that were not reverted * @param cause the cause of revert failure */ - public RevertFailedException(@Nonnull final List> notRevertedChanges, + public RevertFailedException(@Nonnull final Set> notRevertedChanges, final Throwable cause) { super(cause); checkNotNull(notRevertedChanges, "notRevertedChanges should not be null"); - this.notRevertedChanges = ImmutableList.copyOf(notRevertedChanges); + this.notRevertedChanges = ImmutableSet.copyOf(notRevertedChanges); } /** @@ -116,7 +203,7 @@ public interface WriterRegistry extends Writer { * @return list of changes that were not reverted */ @Nonnull - public List> getNotRevertedChanges() { + public Set> getNotRevertedChanges() { return notRevertedChanges; } } diff --git a/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java new file mode 100644 index 000000000..55ef66ec6 --- /dev/null +++ b/v3po/translate-api/src/main/java/io/fd/honeycomb/v3po/translate/write/WriterRegistryBuilder.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.write; + +/** + * Builder for writer registries. + */ +public interface WriterRegistryBuilder { + + WriterRegistry build(); +} diff --git a/v3po/translate-api/src/main/yang/translate-api.yang b/v3po/translate-api/src/main/yang/translate-api.yang index 4a8093ab8..414ee20e1 100644 --- a/v3po/translate-api/src/main/yang/translate-api.yang +++ b/v3po/translate-api/src/main/yang/translate-api.yang @@ -24,14 +24,19 @@ module translate-api { config:java-class io.fd.honeycomb.v3po.translate.read.ReaderRegistry; } - identity honeycomb-writer { + identity honeycomb-writer-factory { base "config:service-type"; - config:java-class io.fd.honeycomb.v3po.translate.write.Writer; + config:java-class io.fd.honeycomb.v3po.translate.write.WriterFactory; } identity honeycomb-writer-registry { base "config:service-type"; - config:java-class io.fd.honeycomb.v3po.translate.write.WriterRegistry; + config:java-class io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry; + } + + identity honeycomb-writer-registry-builder { + base "config:service-type"; + config:java-class io.fd.honeycomb.v3po.translate.write.WriterRegistryBuilder; } identity honeycomb-mapping-context { diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java index 0212c086b..5f1391c74 100644 --- a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java +++ b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/AbstractCompositeWriter.java @@ -18,82 +18,33 @@ package io.fd.honeycomb.v3po.translate.impl.write; import static com.google.common.base.Preconditions.checkArgument; -import com.google.common.base.Optional; -import com.google.common.collect.Lists; -import io.fd.honeycomb.v3po.translate.impl.TraversalType; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; import io.fd.honeycomb.v3po.translate.write.WriteContext; import io.fd.honeycomb.v3po.translate.write.WriteFailedException; import io.fd.honeycomb.v3po.translate.write.Writer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class AbstractCompositeWriter implements Writer { +abstract class AbstractCompositeWriter implements Writer { private static final Logger LOG = LoggerFactory.getLogger(AbstractCompositeWriter.class); - private final Map, ChildWriter>> childWriters; - private final Map, ChildWriter>> augWriters; private final InstanceIdentifier instanceIdentifier; - private TraversalType traversalType; - public AbstractCompositeWriter(final Class type, - final List>> childWriters, - final List>> augWriters, - final TraversalType traversalType) { - this.traversalType = traversalType; - this.instanceIdentifier = InstanceIdentifier.create(type); - this.childWriters = RWUtils.uniqueLinkedIndex(childWriters, RWUtils.MANAGER_CLASS_FUNCTION); - this.augWriters = RWUtils.uniqueLinkedIndex(augWriters, RWUtils.MANAGER_CLASS_AUG_FUNCTION); + AbstractCompositeWriter(final InstanceIdentifier type) { + this.instanceIdentifier = type; } protected void writeCurrent(final InstanceIdentifier id, final D data, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Writing current: {} data: {}", this, id, data); - - switch (traversalType) { - case PREORDER: { - LOG.trace("{}: Writing current attributes", this); - writeCurrentAttributes(id, data, ctx); - writeChildren(id, data, ctx); - break; - } - case POSTORDER: { - writeChildren(id, data, ctx); - LOG.trace("{}: Writing current attributes", this); - writeCurrentAttributes(id, data, ctx); - break; - } - } - + writeCurrentAttributes(id, data, ctx); LOG.debug("{}: Current node written successfully", this); } - private void writeChildren(final InstanceIdentifier id, final D data, final WriteContext ctx) - throws WriteFailedException { - for (ChildWriter> child : childWriters.values()) { - LOG.debug("{}: Writing child in: {}", this, child); - child.writeChild(id, data, ctx); - } - - for (ChildWriter> child : augWriters.values()) { - LOG.debug("{}: Writing augment in: {}", this, child); - child.writeChild(id, data, ctx); - } - } - protected void updateCurrent(final InstanceIdentifier id, final D dataBefore, final D dataAfter, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Updating current: {} dataBefore: {}, datAfter: {}", this, id, dataBefore, dataAfter); @@ -103,69 +54,14 @@ public abstract class AbstractCompositeWriter implements W // No change, ignore return; } - - switch (traversalType) { - case PREORDER: { - LOG.trace("{}: Updating current attributes", this); - updateCurrentAttributes(id, dataBefore, dataAfter, ctx); - updateChildren(id, dataBefore, dataAfter, ctx); - break; - } - case POSTORDER: { - updateChildren(id, dataBefore, dataAfter, ctx); - LOG.trace("{}: Updating current attributes", this); - updateCurrentAttributes(id, dataBefore, dataAfter, ctx); - break; - } - } - + updateCurrentAttributes(id, dataBefore, dataAfter, ctx); LOG.debug("{}: Current node updated successfully", this); } - private void updateChildren(final InstanceIdentifier id, final D dataBefore, final D dataAfter, - final WriteContext ctx) throws WriteFailedException { - for (ChildWriter> child : childWriters.values()) { - LOG.debug("{}: Updating child in: {}", this, child); - child.updateChild(id, dataBefore, dataAfter, ctx); - } - - for (ChildWriter> child : augWriters.values()) { - LOG.debug("{}: Updating augment in: {}", this, child); - child.updateChild(id, dataBefore, dataAfter, ctx); - } - } - protected void deleteCurrent(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) throws WriteFailedException { LOG.debug("{}: Deleting current: {} dataBefore: {}", this, id, dataBefore); - - switch (traversalType) { - case PREORDER: { - deleteChildren(id, dataBefore, ctx); - LOG.trace("{}: Deleting current attributes", this); - deleteCurrentAttributes(id, dataBefore, ctx); - break; - } - case POSTORDER: { - LOG.trace("{}: Deleting current attributes", this); - deleteCurrentAttributes(id, dataBefore, ctx); - deleteChildren(id, dataBefore, ctx); - break; - } - } - } - - private void deleteChildren(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) - throws WriteFailedException { - for (ChildWriter> child : reverseCollection(augWriters.values())) { - LOG.debug("{}: Deleting augment in: {}", this, child); - child.deleteChild(id, dataBefore, ctx); - } - - for (ChildWriter> child : reverseCollection(childWriters.values())) { - LOG.debug("{}: Deleting child in: {}", this, child); - child.deleteChild(id, dataBefore, ctx); - } + deleteCurrentAttributes(id, dataBefore, ctx); } @SuppressWarnings("unchecked") @@ -177,28 +73,20 @@ public abstract class AbstractCompositeWriter implements W LOG.debug("{}: Updating : {}", this, id); LOG.trace("{}: Updating : {}, from: {} to: {}", this, id, dataBefore, dataAfter); - if (idPointsToCurrent(id)) { - if(isWrite(dataBefore, dataAfter)) { - writeCurrent((InstanceIdentifier) id, castToManaged(dataAfter), ctx); - } else if(isDelete(dataBefore, dataAfter)) { - deleteCurrent((InstanceIdentifier) id, castToManaged(dataBefore), ctx); - } else { - checkArgument(dataBefore != null && dataAfter != null, "No data to process"); - updateCurrent((InstanceIdentifier) id, castToManaged(dataBefore), castToManaged(dataAfter), ctx); - } + checkArgument(idPointsToCurrent(id), "Cannot handle data: %s. Only: %s can be handled by writer: %s", + id, getManagedDataObjectType(), this); + + if (isWrite(dataBefore, dataAfter)) { + writeCurrent((InstanceIdentifier) id, castToManaged(dataAfter), ctx); + } else if (isDelete(dataBefore, dataAfter)) { + deleteCurrent((InstanceIdentifier) id, castToManaged(dataBefore), ctx); } else { - if (isWrite(dataBefore, dataAfter)) { - writeSubtree(id, dataAfter, ctx); - } else if (isDelete(dataBefore, dataAfter)) { - deleteSubtree(id, dataBefore, ctx); - } else { - checkArgument(dataBefore != null && dataAfter != null, "No data to process"); - updateSubtree(id, dataBefore, dataAfter, ctx); - } + checkArgument(dataBefore != null && dataAfter != null, "No data to process"); + updateCurrent((InstanceIdentifier) id, castToManaged(dataBefore), castToManaged(dataAfter), ctx); } } - private void checkDataType(final @Nullable DataObject dataAfter) { + private void checkDataType(@Nonnull final DataObject dataAfter) { checkArgument(getManagedDataObjectType().getTargetType().isAssignableFrom(dataAfter.getClass())); } @@ -217,100 +105,10 @@ public abstract class AbstractCompositeWriter implements W return dataAfter == null && dataBefore != null; } - private void writeSubtree(final InstanceIdentifier id, - final DataObject dataAfter, final WriteContext ctx) throws WriteFailedException { - LOG.debug("{}: Writing subtree: {}", this, id); - - final Writer> writer = getNextChildWriter(id); - final Writer> augWriter = getNextAgumentationWriter(id); - - if (writer != null) { - LOG.debug("{}: Writing subtree: {} in: {}", this, id, writer); - writer.update(id, null, dataAfter, ctx); - } else if (augWriter != null) { - LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter); - augWriter.update(id, null, dataAfter, ctx); - } else { - // If there's no dedicated writer, use write current - // But we need current data after to do so - final InstanceIdentifier currentId = RWUtils.cutId(id, getManagedDataObjectType()); - Optional currentDataAfter = ctx.readAfter(currentId); - LOG.debug("{}: Dedicated subtree writer missing for: {}. Writing current.", this, - RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter); - writeCurrent(currentId, castToManaged(currentDataAfter.get()), ctx); - } - } - private boolean idPointsToCurrent(final @Nonnull InstanceIdentifier id) { return id.getTargetType().equals(getManagedDataObjectType().getTargetType()); } - private void deleteSubtree(final InstanceIdentifier id, - final DataObject dataBefore, final WriteContext ctx) throws WriteFailedException { - LOG.debug("{}: Deleting subtree: {}", this, id); - - final Writer> writer = getNextChildWriter(id); - final Writer> augWriter = getNextAgumentationWriter(id); - - if (writer != null) { - LOG.debug("{}: Deleting subtree: {} in: {}", this, id, writer); - writer.update(id, dataBefore, null, ctx); - } else if (augWriter != null) { - LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter); - augWriter.update(id, dataBefore, null, ctx); - } else { - updateSubtreeFromCurrent(id, ctx); - } - } - - @SuppressWarnings("unchecked") - private void updateSubtreeFromCurrent(final InstanceIdentifier id, final WriteContext ctx) - throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.cutId(id, getManagedDataObjectType()); - Optional currentDataBefore = ctx.readBefore(currentId); - Optional currentDataAfter = ctx.readAfter(currentId); - LOG.debug("{}: Dedicated subtree writer missing for: {}. Updating current without subtree", this, - RWUtils.getNextId(id, getManagedDataObjectType()).getType(), currentDataAfter); - updateCurrent((InstanceIdentifier) id, castToManaged(currentDataBefore.orNull()), - castToManaged(currentDataAfter.orNull()), ctx); - } - - private void updateSubtree(final InstanceIdentifier id, - final DataObject dataBefore, - final DataObject dataAfter, - final WriteContext ctx) throws WriteFailedException { - LOG.debug("{}: Updating subtree: {}", this, id); - final Writer> writer = getNextChildWriter(id); - final Writer> augWriter = getNextAgumentationWriter(id); - - if (writer != null) { - LOG.debug("{}: Updating subtree: {} in: {}", this, id, writer); - writer.update(id, dataBefore, dataAfter, ctx); - } else if (augWriter != null) { - LOG.debug("{}: Updating augmented subtree: {} in: {}", this, id, augWriter); - augWriter.update(id, dataBefore, dataAfter, ctx); - } else { - updateSubtreeFromCurrent(id, ctx); - } - } - - private Writer> getNextChildWriter(final InstanceIdentifier id) { - final Class next = RWUtils.getNextId(id, getManagedDataObjectType()).getType(); - return childWriters.get(next); - } - - private Writer> getNextAgumentationWriter(final InstanceIdentifier id) { - final Class next = RWUtils.getNextId(id, getManagedDataObjectType()).getType(); - return augWriters.get(next); - } - - private static List reverseCollection(final Collection original) { - // TODO find a better reverse mechanism (probably a different collection for child writers is necessary) - final ArrayList list = Lists.newArrayList(original); - Collections.reverse(list); - return list; - } - protected abstract void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, @Nonnull final WriteContext ctx) throws WriteFailedException; diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java deleted file mode 100644 index 6e0841deb..000000000 --- a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeChildWriter.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.impl.write; - -import com.google.common.base.Optional; -import io.fd.honeycomb.v3po.translate.impl.TraversalType; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import io.fd.honeycomb.v3po.translate.write.WriteContext; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.spi.write.ChildWriterCustomizer; -import io.fd.honeycomb.v3po.translate.write.WriteFailedException; -import java.util.List; -import javax.annotation.Nonnull; -import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.ChildOf; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -public class CompositeChildWriter extends AbstractCompositeWriter - implements ChildWriter { - - private final ChildWriterCustomizer customizer; - - public CompositeChildWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final ChildWriterCustomizer customizer) { - this(type, childWriters, augWriters, customizer, TraversalType.PREORDER); - } - - - public CompositeChildWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final ChildWriterCustomizer customizer, - @Nonnull final TraversalType traversalType) { - super(type, childWriters, augWriters, traversalType); - this.customizer = customizer; - } - - public CompositeChildWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final ChildWriterCustomizer customizer) { - this(type, childWriters, RWUtils.emptyAugWriterList(), customizer); - } - - public CompositeChildWriter(@Nonnull final Class type, - @Nonnull final ChildWriterCustomizer customizer) { - this(type, RWUtils.emptyChildWriterList(), RWUtils.emptyAugWriterList(), customizer); - } - - @Override - protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, - @Nonnull final WriteContext ctx) throws WriteFailedException { - customizer.writeCurrentAttributes(id, data, ctx); - } - - @Override - protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull WriteContext ctx) throws WriteFailedException { - customizer.deleteCurrentAttributes(id, dataBefore, ctx); - } - - @Override - protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull final D dataAfter, @Nonnull WriteContext ctx) - throws WriteFailedException { - customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx); - } - - @Override - public void writeChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentData, @Nonnull WriteContext ctx) - throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Optional currentData = customizer.extract(currentId, parentData); - if(currentData.isPresent()) { - writeCurrent(currentId, currentData.get(), ctx); - } - } - - @Override - public void deleteChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, - @Nonnull final WriteContext ctx) throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Optional dataBefore = customizer.extract(currentId, parentDataBefore); - if(dataBefore.isPresent()) { - deleteCurrent(currentId, dataBefore.get(), ctx); - } - } - - @Override - public void updateChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, @Nonnull final DataObject parentDataAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Optional before = customizer.extract(currentId, parentDataBefore); - final Optional after = customizer.extract(currentId, parentDataAfter); - - if(before.isPresent()) { - if(after.isPresent()) { - updateCurrent(currentId, before.get(), after.get(), ctx); - } else { - deleteCurrent(currentId, before.get(), ctx); - } - } else if (after.isPresent()){ - writeCurrent(currentId, after.get(), ctx); - } - } -} diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java deleted file mode 100644 index fe9e8d5e3..000000000 --- a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeListWriter.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.impl.write; - -import com.google.common.base.Function; -import com.google.common.base.Optional; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import io.fd.honeycomb.v3po.translate.impl.TraversalType; -import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import io.fd.honeycomb.v3po.translate.write.WriteContext; -import io.fd.honeycomb.v3po.translate.write.WriteFailedException; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import javax.annotation.Nonnull; -import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.ChildOf; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.Identifiable; -import org.opendaylight.yangtools.yang.binding.Identifier; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -public class CompositeListWriter, K extends Identifier> extends - AbstractCompositeWriter - implements ChildWriter { - - private static final Function INDEX_FUNCTION = input -> input instanceof Identifiable - ? ((Identifiable) input).getKey() - : input; - - - private final ListWriterCustomizer customizer; - - public CompositeListWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final ListWriterCustomizer customizer) { - this(type, childWriters, augWriters, customizer, TraversalType.PREORDER); - } - - public CompositeListWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final ListWriterCustomizer customizer, - @Nonnull final TraversalType traversalType) { - super(type, childWriters, augWriters, traversalType); - this.customizer = customizer; - } - - public CompositeListWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final ListWriterCustomizer customizer) { - this(type, childWriters, RWUtils.emptyAugWriterList(), customizer); - } - - public CompositeListWriter(@Nonnull final Class type, - @Nonnull final ListWriterCustomizer customizer) { - this(type, RWUtils.emptyChildWriterList(), RWUtils.emptyAugWriterList(), customizer); - - } - - @Override - protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, - @Nonnull final WriteContext ctx) throws WriteFailedException { - customizer.writeCurrentAttributes(id, data, ctx); - } - - @Override - protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull final WriteContext ctx) throws WriteFailedException { - customizer.deleteCurrentAttributes(id, dataBefore, ctx); - } - - @Override - protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull final D dataAfter, @Nonnull final WriteContext ctx) - throws WriteFailedException { - customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx); - } - - @Override - public void writeChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentData, - @Nonnull final WriteContext ctx) throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Optional> currentData = customizer.extract(currentId, parentData); - if (currentData.isPresent()) { - for (D entry : currentData.get()) { - writeCurrent(currentId, entry, ctx); - } - } - } - - @Override - public void deleteChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, - @Nonnull final WriteContext ctx) throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Optional> dataBefore = customizer.extract(currentId, parentDataBefore); - if (dataBefore.isPresent()) { - for (D entry : dataBefore.get()) { - deleteCurrent(currentId, entry, ctx); - } - } - } - - private Map listOfIdentifiableToMap(Optional> list) { - if (list.isPresent()) { - return Maps.uniqueIndex(list.get(), INDEX_FUNCTION); - } else { - return Collections.emptyMap(); - } - - } - - @Override - public void updateChild(@Nonnull final InstanceIdentifier parentId, - @Nonnull final DataObject parentDataBefore, @Nonnull final DataObject parentDataAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException { - final InstanceIdentifier currentId = RWUtils.appendTypeToId(parentId, getManagedDataObjectType()); - final Map dataBefore = listOfIdentifiableToMap(customizer.extract(currentId, parentDataBefore)); - final Map dataAfter = listOfIdentifiableToMap(customizer.extract(currentId, parentDataAfter)); - - // The order of delete/write/update operations can have side-effects for devices like VPP - // TODO make it configurable - - // First perform delete: - for (Object deletedNodeKey : Sets.difference(dataBefore.keySet(), dataAfter.keySet())) { - final D deleted = dataBefore.get(deletedNodeKey); - deleteCurrent(currentId, deleted, ctx); - } - - // Then write/update: - for (Map.Entry after : dataAfter.entrySet()) { - final D before = dataBefore.get(after.getKey()); - if(before == null) { - writeCurrent(currentId, after.getValue(), ctx); - } else { - updateCurrent(currentId, before, after.getValue(), ctx); - } - } - - } - - @Override - protected void writeCurrent(final InstanceIdentifier id, final D data, final WriteContext ctx) - throws WriteFailedException { - // Make sure the key is present - if(isWildcarded(id)) { - super.writeCurrent(getSpecificId(id, data), data, ctx); - } else { - super.writeCurrent(id, data, ctx); - } - } - - @Override - protected void updateCurrent(final InstanceIdentifier id, final D dataBefore, final D dataAfter, - final WriteContext ctx) throws WriteFailedException { - // Make sure the key is present - if(isWildcarded(id)) { - super.updateCurrent(getSpecificId(id, dataBefore), dataBefore, dataAfter, ctx); - } else { - super.updateCurrent(id, dataBefore, dataAfter, ctx); - } - } - - @Override - protected void deleteCurrent(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) - throws WriteFailedException { - // Make sure the key is present - if(isWildcarded(id)) { - super.deleteCurrent(getSpecificId(id, dataBefore), dataBefore, ctx); - } else { - super.deleteCurrent(id, dataBefore, ctx); - } - } - - private boolean isWildcarded(final InstanceIdentifier id) { - return id.firstIdentifierOf(getManagedDataObjectType().getTargetType()).isWildcarded(); - } - - private InstanceIdentifier getSpecificId(final InstanceIdentifier currentId, final D current) { - return RWUtils.replaceLastInId(currentId, - new InstanceIdentifier.IdentifiableItem<>(currentId.getTargetType(), current.getKey())); - } -} diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeRootWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeRootWriter.java deleted file mode 100644 index 6f46359ff..000000000 --- a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/CompositeRootWriter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.impl.write; - -import io.fd.honeycomb.v3po.translate.impl.TraversalType; -import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import io.fd.honeycomb.v3po.translate.write.WriteContext; -import io.fd.honeycomb.v3po.translate.write.WriteFailedException; -import java.util.List; -import javax.annotation.Nonnull; -import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.ChildOf; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; - -public class CompositeRootWriter extends AbstractCompositeWriter { - - private final RootWriterCustomizer customizer; - - public CompositeRootWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final RootWriterCustomizer customizer) { - this(type, childWriters, augWriters, customizer, TraversalType.PREORDER); - } - - public CompositeRootWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final List>> augWriters, - @Nonnull final RootWriterCustomizer customizer, - @Nonnull final TraversalType traversalType) { - super(type, childWriters, augWriters, traversalType); - this.customizer = customizer; - } - - public CompositeRootWriter(@Nonnull final Class type, - @Nonnull final List>> childWriters, - @Nonnull final RootWriterCustomizer customizer) { - this(type, childWriters, RWUtils.emptyAugWriterList(), customizer); - } - - public CompositeRootWriter(@Nonnull final Class type, - @Nonnull final RootWriterCustomizer customizer) { - this(type, RWUtils.emptyChildWriterList(), RWUtils.emptyAugWriterList(), customizer); - } - - @Override - protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, - @Nonnull final WriteContext ctx) throws WriteFailedException { - // TODO wrap all customizer invocations in try catch, and wrap runtime exceptions in ReadFailed - // TODO same for readers - customizer.writeCurrentAttributes(id, data, ctx); - } - - @Override - protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, - @Nonnull final WriteContext ctx) throws WriteFailedException { - customizer.deleteCurrentAttributes(id, dataBefore, ctx); - } - - @Override - protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, - @Nonnull final D dataBefore, - @Nonnull final D dataAfter, - @Nonnull final WriteContext ctx) throws WriteFailedException { - customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx); - } -} diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java new file mode 100644 index 000000000..b61fb51f7 --- /dev/null +++ b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriter.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write; + +import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer; +import io.fd.honeycomb.v3po.translate.util.RWUtils; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.write.WriteFailedException; +import io.fd.honeycomb.v3po.translate.write.Writer; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.Identifiable; +import org.opendaylight.yangtools.yang.binding.Identifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Special writer handling updates for nodes of type list. + */ +public final class GenericListWriter, K extends Identifier> extends + AbstractCompositeWriter implements Writer { + + private final ListWriterCustomizer customizer; + + public GenericListWriter(@Nonnull final InstanceIdentifier type, + @Nonnull final ListWriterCustomizer customizer) { + super(type); + this.customizer = customizer; + } + + @Override + protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, + @Nonnull final WriteContext ctx) throws WriteFailedException { + customizer.writeCurrentAttributes(id, data, ctx); + } + + @Override + protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, + @Nonnull final WriteContext ctx) throws WriteFailedException { + customizer.deleteCurrentAttributes(id, dataBefore, ctx); + } + + @Override + protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, + @Nonnull final D dataAfter, @Nonnull final WriteContext ctx) + throws WriteFailedException { + customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx); + } + + @Override + protected void writeCurrent(final InstanceIdentifier id, final D data, final WriteContext ctx) + throws WriteFailedException { + // Make sure the key is present + if (isWildcarded(id)) { + super.writeCurrent(getSpecificId(id, data), data, ctx); + } else { + super.writeCurrent(id, data, ctx); + } + } + + @Override + protected void updateCurrent(final InstanceIdentifier id, final D dataBefore, final D dataAfter, + final WriteContext ctx) throws WriteFailedException { + // Make sure the key is present + if (isWildcarded(id)) { + super.updateCurrent(getSpecificId(id, dataBefore), dataBefore, dataAfter, ctx); + } else { + super.updateCurrent(id, dataBefore, dataAfter, ctx); + } + } + + @Override + protected void deleteCurrent(final InstanceIdentifier id, final D dataBefore, final WriteContext ctx) + throws WriteFailedException { + // Make sure the key is present + if (isWildcarded(id)) { + super.deleteCurrent(getSpecificId(id, dataBefore), dataBefore, ctx); + } else { + super.deleteCurrent(id, dataBefore, ctx); + } + } + + private boolean isWildcarded(final InstanceIdentifier id) { + return id.firstIdentifierOf(getManagedDataObjectType().getTargetType()).isWildcarded(); + } + + private InstanceIdentifier getSpecificId(final InstanceIdentifier currentId, final D current) { + return RWUtils.replaceLastInId(currentId, + new InstanceIdentifier.IdentifiableItem<>(currentId.getTargetType(), current.getKey())); + } +} diff --git a/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriter.java b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriter.java new file mode 100644 index 000000000..6ca80ca37 --- /dev/null +++ b/v3po/translate-impl/src/main/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriter.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write; + +import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import io.fd.honeycomb.v3po.translate.write.WriteFailedException; +import javax.annotation.Nonnull; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +/** + * Special writer handling updates for any complex nodes. + */ +public final class GenericWriter extends AbstractCompositeWriter { + + private final RootWriterCustomizer customizer; + + public GenericWriter(@Nonnull final InstanceIdentifier type, + @Nonnull final RootWriterCustomizer customizer) { + super(type); + this.customizer = customizer; + } + + @Override + protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D data, + @Nonnull final WriteContext ctx) throws WriteFailedException { + // TODO wrap all customizer invocations in try catch, and wrap runtime exceptions in ReadFailed + // TODO same for readers + customizer.writeCurrentAttributes(id, data, ctx); + } + + @Override + protected void deleteCurrentAttributes(@Nonnull final InstanceIdentifier id, @Nonnull final D dataBefore, + @Nonnull final WriteContext ctx) throws WriteFailedException { + customizer.deleteCurrentAttributes(id, dataBefore, ctx); + } + + @Override + protected void updateCurrentAttributes(@Nonnull final InstanceIdentifier id, + @Nonnull final D dataBefore, + @Nonnull final D dataAfter, + @Nonnull final WriteContext ctx) throws WriteFailedException { + customizer.updateCurrentAttributes(id, dataBefore, dataAfter, ctx); + } +} diff --git a/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java new file mode 100644 index 000000000..54a7466e1 --- /dev/null +++ b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericListWriterTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.fd.honeycomb.v3po.translate.spi.write.ListWriterCustomizer; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import java.util.Collections; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.Identifiable; +import org.opendaylight.yangtools.yang.binding.Identifier; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class GenericListWriterTest { + + private static final InstanceIdentifier + DATA_OBJECT_INSTANCE_IDENTIFIER = InstanceIdentifier.create(IdentifiableDataObject.class); + @Mock + private ListWriterCustomizer customizer; + @Mock + private WriteContext ctx; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testUpdate() throws Exception { + final GenericListWriter writer = + new GenericListWriter<>(DATA_OBJECT_INSTANCE_IDENTIFIER, customizer); + + final IdentifiableDataObject before = mock(IdentifiableDataObject.class); + final DataObjectIdentifier beforeKey = mock(DataObjectIdentifier.class); + when(before.getKey()).thenReturn(beforeKey); + final IdentifiableDataObject after = mock(IdentifiableDataObject.class); + final DataObjectIdentifier keyAfter = mock(DataObjectIdentifier.class); + when(after.getKey()).thenReturn(keyAfter); + + assertEquals(DATA_OBJECT_INSTANCE_IDENTIFIER, writer.getManagedDataObjectType()); + + final InstanceIdentifier keyedIdBefore = + (InstanceIdentifier) InstanceIdentifier.create(Collections + .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, beforeKey))); + final InstanceIdentifier keyedIdAfter = + (InstanceIdentifier) InstanceIdentifier.create(Collections + .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, keyAfter))); + + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx); + verify(customizer).updateCurrentAttributes(keyedIdBefore, before, after, ctx); + + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, null, ctx); + verify(customizer).deleteCurrentAttributes(keyedIdBefore, before, ctx); + + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, null, after, ctx); + verify(customizer).writeCurrentAttributes(keyedIdAfter, after, ctx); + } + + private abstract static class IdentifiableDataObject implements DataObject, Identifiable {} + private abstract static class DataObjectIdentifier implements Identifier {} +} \ No newline at end of file diff --git a/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java new file mode 100644 index 000000000..919072bcb --- /dev/null +++ b/v3po/translate-impl/src/test/java/io/fd/honeycomb/v3po/translate/impl/write/GenericWriterTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fd.honeycomb.v3po.translate.impl.write; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import io.fd.honeycomb.v3po.translate.spi.write.RootWriterCustomizer; +import io.fd.honeycomb.v3po.translate.write.WriteContext; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class GenericWriterTest { + + private static final InstanceIdentifier + DATA_OBJECT_INSTANCE_IDENTIFIER = InstanceIdentifier.create(DataObject.class); + @Mock + private RootWriterCustomizer customizer; + @Mock + private WriteContext ctx; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testUpdate() throws Exception { + final GenericWriter writer = + new GenericWriter<>(DATA_OBJECT_INSTANCE_IDENTIFIER, customizer); + + final DataObject before = mock(DataObject.class); + final DataObject after = mock(DataObject.class); + + assertEquals(DATA_OBJECT_INSTANCE_IDENTIFIER, writer.getManagedDataObjectType()); + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx); + verify(customizer).updateCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, before, after, ctx); + + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, before, null, ctx); + verify(customizer).deleteCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, before, ctx); + + writer.update(DATA_OBJECT_INSTANCE_IDENTIFIER, null, after, ctx); + verify(customizer).writeCurrentAttributes(DATA_OBJECT_INSTANCE_IDENTIFIER, after, ctx); + } +} \ No newline at end of file diff --git a/v3po/translate-utils/pom.xml b/v3po/translate-utils/pom.xml index 86a11bdba..e4197cf0e 100644 --- a/v3po/translate-utils/pom.xml +++ b/v3po/translate-utils/pom.xml @@ -70,6 +70,11 @@ org.opendaylight.yangtools yang-data-codec-gson + + org.jgrapht + jgrapht-core + 0.9.2 + 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); + } +} 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> rootReadersDependency = Lists.transform(getRootWritersDependency(), - new Function>() { - - @SuppressWarnings("unchecked") - @Override - public Writer 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 vppId; - private final InstanceIdentifier vppStateId; - private final InstanceIdentifier interfaceId; - - private WriteContext ctx; - private Writer writer; - private Writer vppStateWriter; - private Writer interfacesWriter; - - private DelegatingWriterRegistry registry; - - public DelegatingWriterRegistryTest() { - vppId = InstanceIdentifier.create(Vpp.class); - vppStateId = InstanceIdentifier.create(VppState.class); - interfaceId = InstanceIdentifier.create(Interfaces.class); - } - - @SuppressWarnings("unchecked") - private Writer mockWriter(Class clazz) { - final Writer mock = (Writer) Mockito.mock(Writer.class); - doReturn(InstanceIdentifier.create(clazz)).when(mock).getManagedDataObjectType(); - return mock; - } - - private DataObject mockDataObject(final String name, final Class classToMock) { - final DataObject dataBefore = mock(classToMock, name); - doReturn(classToMock).when(dataBefore).getImplementedInterface(); - return dataBefore; - } - - @SuppressWarnings("unchecked") - private static Map, DataObject> asMap(DataObject... objects) { - final Map, DataObject> map = new HashMap<>(); - for (DataObject object : objects) { - final Class implementedInterface = - (Class) 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> 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, Writer> mappedWriters = + flatWriterRegistryBuilder.getMappedWriters(); + + final ArrayList> 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, Writer> mappedWriters = + flatWriterRegistryBuilder.getMappedWriters(); + + final List> 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, Writer> mappedWriters = + flatWriterRegistryBuilder.getMappedWriters(); + final ArrayList> 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 mockWriter(final Class 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 IID = InstanceIdentifier.create(DataObject1.class); + } + private abstract static class DataObject2 implements DataObject { + static InstanceIdentifier IID = InstanceIdentifier.create(DataObject2.class); + } + private abstract static class DataObject3 implements DataObject { + static InstanceIdentifier IID = InstanceIdentifier.create(DataObject3.class); + private abstract static class DataObject31 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject3.IID.child(DataObject31.class); + } + } + private abstract static class DataObject4 implements DataObject { + static InstanceIdentifier IID = InstanceIdentifier.create(DataObject4.class); + private abstract static class DataObject5 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject4.IID.child(DataObject5.class); + private abstract static class DataObject51 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject5.IID.child(DataObject51.class); + } + } + private abstract static class DataObject6 implements DataObject, ChildOf { + static InstanceIdentifier 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 writer1; + @Mock + private Writer writer2; + @Mock + private Writer 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 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, DataObjectUpdate> updates = HashMultimap.create(); + final InstanceIdentifier iid = InstanceIdentifier.create(DataObject1.class); + final InstanceIdentifier 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, DataObjectUpdate> updates = HashMultimap.create(); + final InstanceIdentifier iid = InstanceIdentifier.create(DataObject1.class); + final DataObject1 dataObject = mock(DataObject1.class); + updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject)); + final InstanceIdentifier 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, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create(); + final InstanceIdentifier iid = InstanceIdentifier.create(DataObject1.class); + final DataObject1 dataObject = mock(DataObject1.class); + deletes.put(DataObject1.IID, ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null))); + final InstanceIdentifier 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, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create(); + final Multimap, DataObjectUpdate> updates = HashMultimap.create(); + final InstanceIdentifier 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 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, 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, 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, DataObjectUpdate> updates = HashMultimap.create(); + addUpdate(updates, DataObject1.class); + addUpdate(updates, DataObject3.class); + final InstanceIdentifier 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, 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 void addUpdate(final Multimap, DataObjectUpdate> updates, + final Class type) throws Exception { + final InstanceIdentifier iid = (InstanceIdentifier) 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 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 IID = InstanceIdentifier.create(DataObject1.class); + } + private abstract static class DataObject2 implements DataObject { + static final InstanceIdentifier IID = InstanceIdentifier.create(DataObject2.class); + } + private abstract static class DataObject3 implements DataObject { + static final InstanceIdentifier 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 writer; + @Mock + Writer 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 IID = InstanceIdentifier.create(DataObject1.class); + private abstract static class DataObject11 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject1.IID.child(DataObject11.class); + private abstract static class DataObject111 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject11.IID.child(DataObject111.class); + } + } + private abstract static class DataObject12 implements DataObject, ChildOf { + static InstanceIdentifier IID = DataObject1.IID.child(DataObject12.class); + } + } +} \ No newline at end of file diff --git a/v3po/v3po2vpp/src/main/config/default-config.xml b/v3po/v3po2vpp/src/main/config/default-config.xml index 1ee177553..6b487a13f 100644 --- a/v3po/v3po2vpp/src/main/config/default-config.xml +++ b/v3po/v3po2vpp/src/main/config/default-config.xml @@ -162,14 +162,14 @@ prefix:delegating-writer-registry write-registry - - prefix:honeycomb-writer + + prefix:honeycomb-writer-factory vpp-honeycomb-writer - - - prefix:honeycomb-writer + + + prefix:honeycomb-writer-factory interfaces-honeycomb-writer - + @@ -205,7 +205,7 @@ - prefix:honeycomb-writer + prefix:honeycomb-writer-factory vpp-honeycomb-writer /modules/module[type='vpp-honeycomb-writer'][name='vpp-honeycomb-writer'] diff --git a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java index d87370a8b..8cc740a48 100644 --- a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java +++ b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/InterfacesHoneycombWriterModule.java @@ -1,21 +1,24 @@ package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import io.fd.honeycomb.v3po.translate.impl.TraversalType; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.util.write.CloseableWriter; -import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.write.ReflexiveAugmentWriterCustomizer; -import io.fd.honeycomb.v3po.translate.v3po.interfaces.*; +import com.google.common.collect.Sets; +import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter; +import io.fd.honeycomb.v3po.translate.impl.write.GenericWriter; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.EthernetCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.InterfaceCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.L2Customizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.RoutingCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.TapCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.VhostUserCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.VxlanCustomizer; +import io.fd.honeycomb.v3po.translate.v3po.interfaces.VxlanGpeCustomizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4AddressCustomizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4Customizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv4NeighbourCustomizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.Ipv6Customizer; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; +import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext; +import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry; +import io.fd.honeycomb.v3po.translate.write.WriterFactory; +import java.util.Set; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.Interface1; @@ -24,15 +27,18 @@ import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev14061 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Address; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ip.rev140616.interfaces._interface.ipv4.Neighbor; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppInterfaceAugmentation; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.*; -import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.ChildOf; - -import java.util.ArrayList; -import java.util.List; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Ethernet; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.L2; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Routing; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Tap; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.VhostUser; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.Vxlan; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.interfaces._interface.VxlanGpe; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.openvpp.jvpp.future.FutureJVpp; public class InterfacesHoneycombWriterModule extends - org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractInterfacesHoneycombWriterModule { + org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractInterfacesHoneycombWriterModule { public InterfacesHoneycombWriterModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) { super(identifier, dependencyResolver); @@ -52,91 +58,98 @@ public class InterfacesHoneycombWriterModule extends @Override public java.lang.AutoCloseable createInstance() { - - final List>> ifcAugmentations = Lists.newArrayList(); - ifcAugmentations.add(getVppIfcAugmentationWriter()); - ifcAugmentations.add(getInterface1AugmentationWriter()); - ifcAugmentations.add( - SubinterfaceAugmentationWriterFactory.createInstance(getVppJvppIfcDependency(), getInterfaceContextDependency(), - getBridgeDomainContextDependency())); - - final ChildWriter interfaceWriter = new CompositeListWriter<>(Interface.class, - RWUtils.emptyChildWriterList(), - ifcAugmentations, - new InterfaceCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency()), - // It's important that this customizer is handled in a postorder way, because you first have to handle child nodes - // e.g. Vxlan before setting other interface or vppInterfaceAugmentation leaves - TraversalType.POSTORDER); - - final List>> childWriters = new ArrayList<>(); - childWriters.add(interfaceWriter); - - // FIXME if we just return the root writer and cfg subsystem takes care to set it into reader registry, - // we loose the ordering information for root writers - // Or can we rely to the order in which readers are configured ? - return new CloseableWriter<>(new CompositeRootWriter<>(Interfaces.class, - childWriters, new NoopWriterCustomizer<>())); + return new InterfacesWriterFactory(getVppJvppIfcDependency(), + getBridgeDomainContextDependency(), + getInterfaceContextDependency()); } - private ChildWriter> getInterface1AugmentationWriter() { - - final ChildWriter neighborWriter = new CompositeListWriter<>(Neighbor.class, - new Ipv4NeighbourCustomizer(getVppJvppIfcDependency(),getInterfaceContextDependency())); - - final ChildWriter
addressWriter = new CompositeListWriter<>(Address.class, - new Ipv4AddressCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - final ChildWriter ipv4Writer = new CompositeChildWriter<>(Ipv4.class, - ImmutableList.of(neighborWriter,addressWriter), - new Ipv4Customizer(getVppJvppIfcDependency(),getInterfaceContextDependency())); - final ChildWriter ipv6Writer = new CompositeChildWriter<>(Ipv6.class, - new Ipv6Customizer(getVppJvppIfcDependency())); + private static class InterfacesWriterFactory implements WriterFactory, AutoCloseable { + + private final FutureJVpp jvpp; + private final NamingContext bdContext; + private final NamingContext ifcContext; + + InterfacesWriterFactory(final FutureJVpp vppJvppIfcDependency, + final NamingContext bridgeDomainContextDependency, + final NamingContext interfaceContextDependency) { + this.jvpp = vppJvppIfcDependency; + this.bdContext = bridgeDomainContextDependency; + this.ifcContext = interfaceContextDependency; + } + + @Override + public void close() throws Exception { + // unregister is not supported in ModifiableWriterRegistry (not really needed though) + } + + @Override + public void init(final ModifiableWriterRegistry registry) { + // Interfaces + // Interface = + final InstanceIdentifier ifcId = InstanceIdentifier.create(Interfaces.class).child(Interface.class); + registry.addWriter(new GenericListWriter<>(ifcId, new InterfaceCustomizer(jvpp, ifcContext))); + // VppInterfaceAugmentation + addVppInterfaceAgmentationWriters(ifcId, registry); + // Interface1 (ietf-ip augmentation) + addInterface1AugmentationWriters(ifcId, registry); + // SubinterfaceAugmentation TODO make dedicated module for subIfc writer factory + new SubinterfaceAugmentationWriterFactory(ifcId, jvpp, ifcContext, bdContext).init(registry); + } + + private void addInterface1AugmentationWriters(final InstanceIdentifier ifcId, + final ModifiableWriterRegistry registry) { + final InstanceIdentifier ifc1AugId = ifcId.augmentation(Interface1.class); + // Ipv6(after interface) TODO unfinished customizer = + registry.addWriterAfter(new GenericWriter<>(ifc1AugId.child(Ipv6.class), new Ipv6Customizer(jvpp)), + ifcId); + // Ipv4(after interface) + final InstanceIdentifier ipv4Id = ifc1AugId.child(Ipv4.class); + registry.addWriterAfter(new GenericWriter<>(ipv4Id, new Ipv4Customizer(jvpp, ifcContext)), + ifcId); + // Address(after Ipv4) = + final InstanceIdentifier
ipv4AddressId = ipv4Id.child(Address.class); + registry.addWriterAfter(new GenericListWriter<>(ipv4AddressId, new Ipv4AddressCustomizer(jvpp, ifcContext)), + ipv4Id); + // Neighbor(after ipv4Address) + registry.addWriterAfter(new GenericListWriter<>(ipv4Id.child(Neighbor.class), new Ipv4NeighbourCustomizer(jvpp, ifcContext)), + ipv4AddressId); + } + + private void addVppInterfaceAgmentationWriters(final InstanceIdentifier ifcId, + final ModifiableWriterRegistry registry) { + final InstanceIdentifier vppIfcAugId = ifcId.augmentation(VppInterfaceAugmentation.class); + // VhostUser(Needs to be executed before Interface customizer) = + final InstanceIdentifier vhostId = vppIfcAugId.child(VhostUser.class); + registry.addWriterBefore(new GenericWriter<>(vhostId, new VhostUserCustomizer(jvpp, ifcContext)), + ifcId); + // Vxlan(Needs to be executed before Interface customizer) = + final InstanceIdentifier vxlanId = vppIfcAugId.child(Vxlan.class); + registry.addWriterBefore(new GenericWriter<>(vxlanId, new VxlanCustomizer(jvpp, ifcContext)), + ifcId); + // VxlanGpe(Needs to be executed before Interface customizer) = + final InstanceIdentifier vxlanGpeId = vppIfcAugId.child(VxlanGpe.class); + registry.addWriterBefore(new GenericWriter<>(vxlanGpeId, new VxlanGpeCustomizer(jvpp, ifcContext)), + ifcId); + // Tap(Needs to be executed before Interface customizer) = + final InstanceIdentifier tapId = vppIfcAugId.child(Tap.class); + registry.addWriterBefore(new GenericWriter<>(tapId, new TapCustomizer(jvpp, ifcContext)), + ifcId); + + final Set> specificIfcTypes = Sets.newHashSet(vhostId, vxlanGpeId, vxlanGpeId, tapId); + + // Ethernet(No dependency, customizer not finished TODO) = + registry.addWriter(new GenericWriter<>(vppIfcAugId.child(Ethernet.class), new EthernetCustomizer(jvpp))); + // Routing(Execute only after specific interface customizers) = + registry.addWriterAfter( + new GenericWriter<>(vppIfcAugId.child(Routing.class), new RoutingCustomizer(jvpp, ifcContext)), + specificIfcTypes); + // Routing(Execute only after specific interface customizers) = + registry.addWriterAfter( + new GenericWriter<>(vppIfcAugId.child(L2.class), new L2Customizer(jvpp, ifcContext, bdContext)), + specificIfcTypes); + } - final List>> interface1ChildWriters = Lists.newArrayList(); - interface1ChildWriters.add(ipv4Writer); - interface1ChildWriters.add(ipv6Writer); - - return new CompositeChildWriter<>(Interface1.class, - interface1ChildWriters, new ReflexiveAugmentWriterCustomizer<>()); } - private ChildWriter getVppIfcAugmentationWriter() { - - final ChildWriter ethernetWriter = new CompositeChildWriter<>(Ethernet.class, - new EthernetCustomizer(getVppJvppIfcDependency())); - - final ChildWriter routingWriter = new CompositeChildWriter<>(Routing.class, - new RoutingCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - - final ChildWriter vxlanWriter = new CompositeChildWriter<>(Vxlan.class, - new VxlanCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - - final ChildWriter vxlanGpeWriter = new CompositeChildWriter<>(VxlanGpe.class, - new VxlanGpeCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - - final ChildWriter vhostUserWriter = new CompositeChildWriter<>(VhostUser.class, - new VhostUserCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - - final ChildWriter tapWriter = new CompositeChildWriter<>(Tap.class, - new TapCustomizer(getVppJvppIfcDependency(), getInterfaceContextDependency())); - - final ChildWriter l2Writer = new CompositeChildWriter<>(L2.class, - new L2Customizer(getVppJvppIfcDependency(), getInterfaceContextDependency(), - getBridgeDomainContextDependency()) - ); - - final List>> vppIfcChildWriters = Lists.newArrayList(); - vppIfcChildWriters.add(vhostUserWriter); - vppIfcChildWriters.add(vxlanWriter); - vppIfcChildWriters.add(vxlanGpeWriter); - vppIfcChildWriters.add(tapWriter); - vppIfcChildWriters.add(ethernetWriter); - vppIfcChildWriters.add(l2Writer); - vppIfcChildWriters.add(routingWriter); - - return new CompositeChildWriter<>(VppInterfaceAugmentation.class, - vppIfcChildWriters, - RWUtils.emptyAugWriterList(), - new ReflexiveAugmentWriterCustomizer<>()); - } } diff --git a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/SubinterfaceAugmentationWriterFactory.java b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/SubinterfaceAugmentationWriterFactory.java index f24dbf7e0..589ba92be 100644 --- a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/SubinterfaceAugmentationWriterFactory.java +++ b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/SubinterfaceAugmentationWriterFactory.java @@ -16,90 +16,86 @@ package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406; -import static io.fd.honeycomb.v3po.translate.util.RWUtils.singletonChildWriterList; - -import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.util.write.ReflexiveAugmentWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer; +import com.google.common.collect.Sets; +import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter; +import io.fd.honeycomb.v3po.translate.impl.write.GenericWriter; import io.fd.honeycomb.v3po.translate.v3po.interfaces.RewriteCustomizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.SubInterfaceCustomizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.SubInterfaceL2Customizer; import io.fd.honeycomb.v3po.translate.v3po.interfaces.ip.SubInterfaceIpv4AddressCustomizer; import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nonnull; +import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry; +import io.fd.honeycomb.v3po.translate.write.WriterFactory; +import org.opendaylight.yang.gen.v1.urn.ieee.params.xml.ns.yang.dot1q.types.rev150626.dot1q.tag.or.any.Dot1qTag; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.SubinterfaceAugmentation; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.SubInterfaces; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.sub.interfaces.SubInterface; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.interfaces._interface.sub.interfaces.SubInterfaceKey; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.L2; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.match.attributes.match.type.vlan.tagged.VlanTagged; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.Match; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.Tags; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.l2.Rewrite; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.tags.Tag; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.ip4.attributes.Ipv4; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.ip4.attributes.ipv4.Address; -import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.tag.rewrite.PushTags; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.openvpp.jvpp.future.FutureJVpp; -final class SubinterfaceAugmentationWriterFactory { - - private SubinterfaceAugmentationWriterFactory() { - } - - private static ChildWriter getIp4Writer( - @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext) { +final class SubinterfaceAugmentationWriterFactory implements WriterFactory { - final ChildWriter
addressWriter = new CompositeListWriter<>( - Address.class, - new SubInterfaceIpv4AddressCustomizer(futureJvpp, interfaceContext)); + private final InstanceIdentifier ifcId; + private final FutureJVpp jvpp; + private final NamingContext ifcContext; + private final NamingContext bdContext; - return new CompositeChildWriter<>( - Ipv4.class, - RWUtils.singletonChildWriterList(addressWriter), - new ReflexiveChildWriterCustomizer<>()); + public SubinterfaceAugmentationWriterFactory( + final InstanceIdentifier ifcId, final FutureJVpp jvpp, + final NamingContext ifcContext, final NamingContext bdContext) { + this.ifcId = ifcId; + this.jvpp = jvpp; + this.ifcContext = ifcContext; + this.bdContext = bdContext; } - private static ChildWriter getL2Writer( - @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext, - @Nonnull final NamingContext bridgeDomainContext) { - - final ChildWriter> rewriteWriter = - new CompositeChildWriter<>(Rewrite.class, new RewriteCustomizer(futureJvpp, interfaceContext)); - - return new CompositeChildWriter<>( - L2.class, - singletonChildWriterList(rewriteWriter), - new SubInterfaceL2Customizer(futureJvpp, interfaceContext, bridgeDomainContext) - ); - } - - static ChildWriter createInstance( - @Nonnull final FutureJVpp futureJvpp, @Nonnull final NamingContext interfaceContext, - @Nonnull final NamingContext bridgeDomainContext) { - final List>> childWriters = new ArrayList<>(); - - // TODO L2 is ChildOf, but SubInterface extends SubInterfaceBaseAttributes - // If we use containers inside groupings, we need to cast and lose static type checking. - // Can we get rid of the cast? - childWriters.add((ChildWriter) getL2Writer(futureJvpp, interfaceContext, bridgeDomainContext)); - childWriters.add((ChildWriter) getIp4Writer(futureJvpp, interfaceContext)); - - final CompositeListWriter subInterfaceWriter = new CompositeListWriter<>( - SubInterface.class, - childWriters, - new SubInterfaceCustomizer(futureJvpp, interfaceContext)); - - final ChildWriter subInterfacesWriter = new CompositeChildWriter<>( - SubInterfaces.class, - singletonChildWriterList(subInterfaceWriter), - new ReflexiveChildWriterCustomizer<>()); + @Override + public void init(final ModifiableWriterRegistry registry) { + final InstanceIdentifier subIfcAugId = + ifcId.augmentation(SubinterfaceAugmentation.class); + // Subinterfaces + // Subinterface(Handle only after all interface related stuff gets processed) = + final InstanceIdentifier subIfcId = subIfcAugId.child(SubInterfaces.class).child(SubInterface.class); + registry.addSubtreeWriterAfter( + // TODO this customizer covers quite a lot of complex child nodes (maybe refactor ?) + Sets.newHashSet( + InstanceIdentifier.create(SubInterface.class).child(Tags.class), + InstanceIdentifier.create(SubInterface.class).child(Tags.class).child(Tag.class), + InstanceIdentifier.create(SubInterface.class).child(Tags.class).child(Tag.class).child( + Dot1qTag.class), + InstanceIdentifier.create(SubInterface.class).child(Match.class), + InstanceIdentifier.create(SubInterface.class).child(Match.class).child(VlanTagged.class)), + new GenericListWriter<>(subIfcId, new SubInterfaceCustomizer(jvpp, ifcContext)), + ifcId); + // L2 = + final InstanceIdentifier + l2Id = subIfcId.child( + org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.vlan.rev150527.sub._interface.base.attributes.L2.class); + registry.addWriterAfter(new GenericWriter<>(l2Id, new SubInterfaceL2Customizer(jvpp, ifcContext, bdContext)), + subIfcId); + // Rewrite(also handles pushTags + pushTags/dot1qtag) = + final InstanceIdentifier rewriteId = l2Id.child(Rewrite.class); + registry.addSubtreeWriterAfter( + Sets.newHashSet( + InstanceIdentifier.create(Rewrite.class).child(PushTags.class), + InstanceIdentifier.create(Rewrite.class).child(PushTags.class) + .child(org.opendaylight.yang.gen.v1.urn.ieee.params.xml.ns.yang.dot1q.types.rev150626.dot1q.tag.Dot1qTag.class)), + new GenericWriter<>(rewriteId, new RewriteCustomizer(jvpp, ifcContext)), + l2Id); + // Ipv4(handled after L2 and L2/rewrite is done) = + final InstanceIdentifier
ipv4SubifcAddressId = subIfcId.child(Ipv4.class).child(Address.class); + registry.addWriterAfter(new GenericListWriter<>(ipv4SubifcAddressId, + new SubInterfaceIpv4AddressCustomizer(jvpp, ifcContext)), + rewriteId); - return new CompositeChildWriter<>( - SubinterfaceAugmentation.class, - singletonChildWriterList(subInterfacesWriter), - RWUtils.emptyAugWriterList(), - new ReflexiveAugmentWriterCustomizer<>()); } } diff --git a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/VppHoneycombWriterModule.java b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/VppHoneycombWriterModule.java index 6bf3da104..d9a542bde 100644 --- a/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/VppHoneycombWriterModule.java +++ b/v3po/v3po2vpp/src/main/java/org/opendaylight/yang/gen/v1/urn/honeycomb/params/xml/ns/yang/v3po2vpp/rev160406/VppHoneycombWriterModule.java @@ -1,25 +1,18 @@ package org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.util.write.CloseableWriter; -import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer; +import io.fd.honeycomb.v3po.translate.impl.write.GenericListWriter; +import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext; import io.fd.honeycomb.v3po.translate.v3po.vpp.BridgeDomainCustomizer; import io.fd.honeycomb.v3po.translate.v3po.vpp.L2FibEntryCustomizer; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import java.util.ArrayList; -import java.util.List; +import io.fd.honeycomb.v3po.translate.write.ModifiableWriterRegistry; +import io.fd.honeycomb.v3po.translate.write.WriterFactory; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.L2FibTable; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.l2.fib.table.L2FibEntry; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.l2.fib.attributes.l2.fib.table.L2FibEntryKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey; -import org.opendaylight.yangtools.yang.binding.ChildOf; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.openvpp.jvpp.future.FutureJVpp; public class VppHoneycombWriterModule extends org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.v3po2vpp.rev160406.AbstractVppHoneycombWriterModule { @@ -42,38 +35,45 @@ public class VppHoneycombWriterModule extends @Override public java.lang.AutoCloseable createInstance() { - final CompositeListWriter bridgeDomainWriter = new CompositeListWriter<>( - BridgeDomain.class, - RWUtils.singletonChildWriterList(l2FibTableWriter()), - new BridgeDomainCustomizer(getVppJvppWriterDependency(), getBridgeDomainContextVppDependency())); - - final ChildWriter bridgeDomainsWriter = new CompositeChildWriter<>( - BridgeDomains.class, - RWUtils.singletonChildWriterList(bridgeDomainWriter), - new ReflexiveChildWriterCustomizer<>()); - - final List>> childWriters = new ArrayList<>(); - childWriters.add(bridgeDomainsWriter); - - return new CloseableWriter<>(new CompositeRootWriter<>( - Vpp.class, - childWriters, - new NoopWriterCustomizer<>())); + return new VppHoneycombWriterFactory( + getVppJvppWriterDependency(), + getBridgeDomainContextVppDependency(), + getInterfaceContextVppDependency()); } - private ChildWriter l2FibTableWriter() { - final CompositeListWriter l2FibEntryWriter = - new CompositeListWriter<>(L2FibEntry.class, - new L2FibEntryCustomizer(getVppJvppWriterDependency(), - getBridgeDomainContextVppDependency(), getInterfaceContextVppDependency())); + private static final class VppHoneycombWriterFactory implements WriterFactory, AutoCloseable { - final ChildWriter l2FibTableWriter = new CompositeChildWriter<>( - L2FibTable.class, - RWUtils.singletonChildWriterList(l2FibEntryWriter), - new ReflexiveChildWriterCustomizer<>()); + private final FutureJVpp jvpp; + private final NamingContext bdContext; + private final NamingContext ifcContext; - return l2FibTableWriter; - } + VppHoneycombWriterFactory(final FutureJVpp vppJvppWriterDependency, + final NamingContext bridgeDomainContextVppDependency, + final NamingContext interfaceContextVppDependency) { + this.jvpp = vppJvppWriterDependency; + this.bdContext = bridgeDomainContextVppDependency; + this.ifcContext = interfaceContextVppDependency; + } + @Override + public void close() throws Exception { + // unregister is not supported in ModifiableWriterRegistry (not really needed though) + } + @Override + public void init(final ModifiableWriterRegistry registry) { + // Vpp has no handlers + // BridgeDomains has no handlers + // BridgeDomain = + final InstanceIdentifier bdId = + InstanceIdentifier.create(Vpp.class).child(BridgeDomains.class).child(BridgeDomain.class); + registry.addWriter(new GenericListWriter<>(bdId, new BridgeDomainCustomizer(jvpp, bdContext))); + // L2FibTable has no handlers + // L2FibEntry(handled after BridgeDomain) = + final InstanceIdentifier l2FibEntryId = bdId.child(L2FibTable.class).child(L2FibEntry.class); + registry.addWriterAfter( + new GenericListWriter<>(l2FibEntryId, new L2FibEntryCustomizer(jvpp, bdContext, ifcContext)), + bdId); + } + } } diff --git a/v3po/v3po2vpp/src/main/yang/v3po2vpp.yang b/v3po/v3po2vpp/src/main/yang/v3po2vpp.yang index cf89cbd51..8a5527248 100644 --- a/v3po/v3po2vpp/src/main/yang/v3po2vpp.yang +++ b/v3po/v3po2vpp/src/main/yang/v3po2vpp.yang @@ -149,7 +149,7 @@ module v3po2vpp { identity vpp-honeycomb-writer { base config:module-type; - config:provided-service tapi:honeycomb-writer; + config:provided-service tapi:honeycomb-writer-factory; } augment "/config:modules/config:module/config:configuration" { @@ -187,7 +187,7 @@ module v3po2vpp { identity interfaces-honeycomb-writer { base config:module-type; - config:provided-service tapi:honeycomb-writer; + config:provided-service tapi:honeycomb-writer-factory; } augment "/config:modules/config:module/config:configuration" { diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java deleted file mode 100644 index e713c623d..000000000 --- a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppTest.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.v3po.vpp; - -import static io.fd.honeycomb.v3po.translate.v3po.test.ContextTestUtils.getMapping; -import static io.fd.honeycomb.v3po.translate.v3po.test.ContextTestUtils.getMappingIid; -import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -import com.google.common.base.Optional; -import com.google.common.collect.Iterators; -import com.google.common.collect.Lists; -import io.fd.honeycomb.v3po.translate.MappingContext; -import io.fd.honeycomb.v3po.translate.ModificationCache; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter; -import io.fd.honeycomb.v3po.translate.util.write.DelegatingWriterRegistry; -import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext; -import io.fd.honeycomb.v3po.translate.write.WriteContext; -import io.fd.honeycomb.v3po.translate.write.Writer; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.opendaylight.yang.gen.v1.urn.honeycomb.params.xml.ns.yang.naming.context.rev160513.contexts.naming.context.Mappings; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.VppBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomainsBuilder; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainBuilder; -import org.opendaylight.yangtools.yang.binding.DataObject; -import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; -import org.openvpp.jvpp.VppInvocationException; -import org.openvpp.jvpp.dto.BridgeDomainAddDel; -import org.openvpp.jvpp.dto.BridgeDomainAddDelReply; -import org.openvpp.jvpp.future.FutureJVpp; - -public class VppTest { - - private static final byte ADD_OR_UPDATE_BD = 1; - private static final byte ZERO = 0; - private static final String BD_NAME = "bdn1"; - private static final String BD_CONTEXT_NAME = "test-instance"; - - @Mock - private FutureJVpp api; - @Mock - private WriteContext ctx; - @Mock - private MappingContext mappingContext; - - private DelegatingWriterRegistry rootRegistry; - private CompositeRootWriter vppWriter; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - NamingContext bdContext = new NamingContext("generatedBdName", BD_CONTEXT_NAME); - final ModificationCache toBeReturned = new ModificationCache(); - doReturn(toBeReturned).when(ctx).getModificationCache(); - doReturn(mappingContext).when(ctx).getMappingContext(); - - vppWriter = VppUtils.getVppWriter(api, bdContext); - rootRegistry = new DelegatingWriterRegistry( - Collections.>singletonList(vppWriter)); - } - - private BridgeDomains getBridgeDomains(String... name) { - final List bdmns = Lists.newArrayList(); - for (String s : name) { - bdmns.add(new BridgeDomainBuilder() - .setName(s) - .setArpTermination(false) - .setFlood(true) - .setForward(false) - .setLearn(true) - .build()); - } - return new BridgeDomainsBuilder() - .setBridgeDomain(bdmns) - .build(); - } - - private void whenBridgeDomainAddDelThenSuccess() - throws ExecutionException, VppInvocationException, InterruptedException { - final CompletionStage replyCs = mock(CompletionStage.class); - final CompletableFuture replyFuture = mock(CompletableFuture.class); - when(replyCs.toCompletableFuture()).thenReturn(replyFuture); - final BridgeDomainAddDelReply reply = new BridgeDomainAddDelReply(); - when(replyFuture.get()).thenReturn(reply); - when(api.bridgeDomainAddDel(any(BridgeDomainAddDel.class))).thenReturn(replyCs); - } - - private void verifyBridgeDomainAddDel(final BridgeDomain bd, final int bdId) throws VppInvocationException { - final byte arpTerm = BridgeDomainTestUtils.booleanToByte(bd.isArpTermination()); - final byte flood = BridgeDomainTestUtils.booleanToByte(bd.isFlood()); - final byte forward = BridgeDomainTestUtils.booleanToByte(bd.isForward()); - final byte learn = BridgeDomainTestUtils.booleanToByte(bd.isLearn()); - final byte uuf = BridgeDomainTestUtils.booleanToByte(bd.isUnknownUnicastFlood()); - - // TODO adding equals methods for jvpp DTOs would make ArgumentCaptor usage obsolete - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(BridgeDomainAddDel.class); - verify(api).bridgeDomainAddDel(argumentCaptor.capture()); - final BridgeDomainAddDel actual = argumentCaptor.getValue(); - assertEquals(arpTerm, actual.arpTerm); - assertEquals(flood, actual.flood); - assertEquals(forward, actual.forward); - assertEquals(learn, actual.learn); - assertEquals(uuf, actual.uuFlood); - assertEquals(ADD_OR_UPDATE_BD, actual.isAdd); - assertEquals(bdId, actual.bdId); - } - - private void verifyBridgeDomainDeleteWasInvoked(final int bdId) throws VppInvocationException { - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(BridgeDomainAddDel.class); - verify(api).bridgeDomainAddDel(argumentCaptor.capture()); - final BridgeDomainAddDel actual = argumentCaptor.getValue(); - assertEquals(bdId, actual.bdId); - assertEquals(ZERO, actual.arpTerm); - assertEquals(ZERO, actual.flood); - assertEquals(ZERO, actual.forward); - assertEquals(ZERO, actual.learn); - assertEquals(ZERO, actual.uuFlood); - assertEquals(ZERO, actual.isAdd); - } - - @Test - public void writeVppUsingRootRegistry() throws Exception { - final int bdId = 1; - final BridgeDomains bdn1 = getBridgeDomains(BD_NAME); - whenBridgeDomainAddDelThenSuccess(); - - // Returning no Mappings for "test-instance" makes bdContext.containsName() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class)); - // Make bdContext.containsIndex() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME)); - - rootRegistry.update( - InstanceIdentifier.create(Vpp.class), - null, - new VppBuilder().setBridgeDomains(bdn1).build(), - ctx); - - verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId); - } - - @Test - public void writeVppUsingVppWriter() throws Exception { - final int bdId = 1; - final BridgeDomains bdn1 = getBridgeDomains(BD_NAME); - whenBridgeDomainAddDelThenSuccess(); - - // Returning no Mappings for "test-instance" makes bdContext.containsName() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class)); - // Make bdContext.containsIndex() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME)); - - vppWriter.update(InstanceIdentifier.create(Vpp.class), - null, - new VppBuilder().setBridgeDomains(bdn1).build(), - ctx); - - verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId); - verify(mappingContext).put(getMappingIid(BD_NAME, BD_CONTEXT_NAME), getMapping(BD_NAME, 1).get()); - } - - @Test - public void writeVppFromRoot() throws Exception { - final BridgeDomains bdn1 = getBridgeDomains(BD_NAME); - final int bdId = 1; - final Vpp vpp = new VppBuilder().setBridgeDomains(bdn1).build(); - whenBridgeDomainAddDelThenSuccess(); - - // Returning no Mappings for "test-instance" makes bdContext.containsName() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME).firstIdentifierOf(Mappings.class)); - // Make bdContext.containsIndex() return false - doReturn(Optional.absent()).when(mappingContext) - .read(getMappingIid(BD_NAME, BD_CONTEXT_NAME)); - - rootRegistry.update(Collections.emptyMap(), - Collections., DataObject>singletonMap(InstanceIdentifier.create(Vpp.class), - vpp), ctx); - - verifyBridgeDomainAddDel(Iterators.getOnlyElement(bdn1.getBridgeDomain().iterator()), bdId); - } - - @Test - public void deleteVpp() throws Exception { - final BridgeDomains bdn1 = getBridgeDomains(BD_NAME); - final int bdId = 1; - whenBridgeDomainAddDelThenSuccess(); - doReturn(getMapping(BD_NAME, bdId)).when(mappingContext).read(getMappingIid(BD_NAME, BD_CONTEXT_NAME)); - - rootRegistry.update( - InstanceIdentifier.create(Vpp.class), - new VppBuilder().setBridgeDomains(bdn1).build(), - null, - ctx); - - verifyBridgeDomainDeleteWasInvoked(bdId); - } - - @Test - public void updateVppNoActualChange() throws Exception { - rootRegistry.update( - InstanceIdentifier.create(Vpp.class), - new VppBuilder().setBridgeDomains(getBridgeDomains(BD_NAME)).build(), - new VppBuilder().setBridgeDomains(getBridgeDomains(BD_NAME)).build(), - ctx); - - verifyZeroInteractions(api); - } - - @Test - public void writeUpdate() throws Exception { - final int bdn1Id = 1; - doReturn(getMapping(BD_NAME, bdn1Id)).when(mappingContext).read(getMappingIid(BD_NAME, BD_CONTEXT_NAME)); - - final BridgeDomains domainsBefore = getBridgeDomains(BD_NAME); - final BridgeDomain bdn1Before = domainsBefore.getBridgeDomain().get(0); - - final BridgeDomain bdn1After = new BridgeDomainBuilder(bdn1Before).setFlood(!bdn1Before.isFlood()).build(); - final BridgeDomains domainsAfter = new BridgeDomainsBuilder() - .setBridgeDomain(Collections.singletonList(bdn1After)) - .build(); - - whenBridgeDomainAddDelThenSuccess(); - - rootRegistry.update( - InstanceIdentifier.create(Vpp.class), - new VppBuilder().setBridgeDomains(domainsBefore).build(), - new VppBuilder().setBridgeDomains(domainsAfter).build(), - ctx); - - // bdn1 is created with negated flood value - verifyBridgeDomainAddDel(bdn1After, bdn1Id); - } - - // TODO test unkeyed list - // TODO test update of a child without dedicated writer -} \ No newline at end of file diff --git a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java b/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java deleted file mode 100644 index 4b3eb5adc..000000000 --- a/v3po/v3po2vpp/src/test/java/io/fd/honeycomb/v3po/translate/v3po/vpp/VppUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016 Cisco and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.fd.honeycomb.v3po.translate.v3po.vpp; - -import io.fd.honeycomb.v3po.translate.impl.write.CompositeChildWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeListWriter; -import io.fd.honeycomb.v3po.translate.impl.write.CompositeRootWriter; -import io.fd.honeycomb.v3po.translate.util.RWUtils; -import io.fd.honeycomb.v3po.translate.util.write.NoopWriterCustomizer; -import io.fd.honeycomb.v3po.translate.util.write.ReflexiveChildWriterCustomizer; -import io.fd.honeycomb.v3po.translate.v3po.util.NamingContext; -import io.fd.honeycomb.v3po.translate.write.ChildWriter; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nonnull; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.Vpp; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.BridgeDomains; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomain; -import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev150105.vpp.bridge.domains.BridgeDomainKey; -import org.opendaylight.yangtools.yang.binding.ChildOf; -import org.openvpp.jvpp.future.FutureJVpp; - -final class VppUtils { - - public VppUtils() {} - - /** - * Create root Vpp writer with all its children wired - */ - static CompositeRootWriter getVppWriter(@Nonnull final FutureJVpp vppApi, final NamingContext bdContext) { - - final CompositeListWriter bridgeDomainWriter = new CompositeListWriter<>( - BridgeDomain.class, - new BridgeDomainCustomizer(vppApi, bdContext)); - - final ChildWriter bridgeDomainsReader = new CompositeChildWriter<>( - BridgeDomains.class, - RWUtils.singletonChildWriterList(bridgeDomainWriter), - new ReflexiveChildWriterCustomizer()); - - final List>> childWriters = new ArrayList<>(); - childWriters.add(bridgeDomainsReader); - - return new CompositeRootWriter<>( - Vpp.class, - childWriters, - new NoopWriterCustomizer()); - } -} diff --git a/v3po/vpp-translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/v3po/util/NamingContext.java b/v3po/vpp-translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/v3po/util/NamingContext.java index dc77106fb..f6e0e6d48 100644 --- a/v3po/vpp-translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/v3po/util/NamingContext.java +++ b/v3po/vpp-translate-utils/src/main/java/io/fd/honeycomb/v3po/translate/v3po/util/NamingContext.java @@ -75,10 +75,6 @@ public final class NamingContext implements AutoCloseable { public synchronized String getName(final int index, @Nonnull final MappingContext mappingContext) { if (!containsName(index, mappingContext)) { final String artificialName = getArtificialName(index); - LOG.info("Assigning artificial name: {} for index: {}", artificialName, index); - for (StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) { - LOG.error("{}", stackTraceElement.toString()); - } addName(index, artificialName, mappingContext); } -- cgit 1.2.3-korg