import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.fd.honeycomb.data.DataModification;
+import io.fd.honeycomb.test.model.Ids;
import io.fd.honeycomb.translate.impl.write.registry.FlatWriterRegistryBuilder;
import io.fd.honeycomb.translate.util.YangDAG;
import io.fd.honeycomb.translate.write.WriteContext;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.junit.Test;
import org.mockito.InOrder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.ComplexAugment;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TipProducingDataTree;
import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
-import io.fd.honeycomb.test.model.Ids;
/**
* Testing honeycomb writes from data tree up to mocked writers.
private static <D extends DataObject> Writer<D> mockWriter(final InstanceIdentifier<D> id) {
final Writer<D> mock = (Writer<D>) mock(Writer.class);
when(mock.getManagedDataObjectType()).thenReturn(id);
+ //TODO - HONEYCOMB-412 - to call default impl of canProcess()
+ when(mock.canProcess(any())).thenAnswer(invocationOnMock -> {
+ final Writer writer = Writer.class.cast(invocationOnMock.getMock());
+ final Writer delegatingWriter = new Writer() {
+ @Nonnull
+ @Override
+ public InstanceIdentifier getManagedDataObjectType() {
+ return writer.getManagedDataObjectType();
+ }
+
+ @Override
+ public boolean supportsDirectUpdate() {
+ return writer.supportsDirectUpdate();
+ }
+
+ @Override
+ public void processModification(@Nonnull final InstanceIdentifier id,
+ @Nullable final DataObject dataBefore,
+ @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx)
+ throws WriteFailedException {
+ writer.processModification(id, dataBefore, dataAfter, ctx);
+ }
+ };
+ return delegatingWriter.canProcess(InstanceIdentifier.class.cast(invocationOnMock.getArguments()[0]));
+ });
return mock;
}
for (Writer<?> orderedWriter : orderedWriters) {
verify(orderedWriter).getManagedDataObjectType();
- verifyNoMoreInteractions(orderedWriter);
+ //TODO - HONEYCOMB-412
+ //verifyNoMoreInteractions(orderedWriter);
}
+
+ verify(complexAugmentContainerWriter, times(1)).processModification(any(), any(), any(), any());
+ verify(c3Writer, times(1)).processModification(any(), any(), any(), any());
+ verify(simpleAugmentWriter, times(1)).processModification(any(), any(), any(), any());
+ verify(simpleContainerWriter, times(1)).processModification(any(), any(), any(), any());
+ verify(containerWithChoiceWriter, times(1)).processModification(any(), any(), any(), any());
+ verify(containerFromGroupingWriter, times(1)).processModification(any(), any(), any(), any());
+ verify(nestedListWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(listInContainerWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(containerInListWriter, times(2)).processModification(any(), any(), any(), any());
}
private Writer<?>[] getOrderedWriters() {
for (Writer<?> orderedWriter : orderedWriters) {
verify(orderedWriter).getManagedDataObjectType();
- verifyNoMoreInteractions(orderedWriter);
+ //TODO - HONEYCOMB-412
+ // verifyNoMoreInteractions(orderedWriter);
}
+
+ verify(complexAugmentContainerWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(c3Writer, times(2)).processModification(any(), any(), any(), any());
+ verify(simpleAugmentWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(simpleContainerWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(containerWithChoiceWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(containerFromGroupingWriter, times(2)).processModification(any(), any(), any(), any());
+ verify(nestedListWriter, times(4)).processModification(any(), any(), any(), any());
+ verify(listInContainerWriter, times(4)).processModification(any(), any(), any(), any());
+ verify(containerInListWriter, times(4)).processModification(any(), any(), any(), any());
}
private void writeContainerWithList(final DataModification dataModification) {
.collect(Collectors.toSet()));
}
- public static class YangModules {
+ static class YangModules {
private final Set<Class<? extends YangModelBindingProvider>> yangBindings;
- public YangModules(final Set<Class<? extends YangModelBindingProvider>> yangBindings) {
+ YangModules(final Set<Class<? extends YangModelBindingProvider>> yangBindings) {
this.yangBindings = yangBindings;
}
InstanceIdentifier.create(ApplicationRib.class);
private static final InstanceIdentifier<Tables> TABLES_IID = AR_IID.child(Tables.class);
-
- // TODO (HONEYCOMB-359):
- // BGP models are huge, we need some kind of wildcarded subtree writer, that works for whole subtree.
- // 1) we can either move checking handledTypes to writers (getHandledTypes, isAffected, writer.getHandedTypes, ...)
- // but then precondition check in flatWriterRegistry might be slower (we need to check if we have all writers
- // in order to avoid unnecessary reverts).
- //
- // 2) alternative is to compute all child nodes during initialization (might introduce some footprint penalty).
@Override
public void init(final ModifiableWriterRegistryBuilder registry) {
registry.subtreeAdd(ImmutableSet.of(TABLES_IID), new BindingBrokerWriter<>(InstanceIdentifier.create(ApplicationRib.class), dataBroker)
package io.fd.honeycomb.infra.bgp.neighbors;
-import com.google.common.collect.Sets;
import com.google.inject.Inject;
import io.fd.honeycomb.infra.bgp.BgpConfiguration;
import io.fd.honeycomb.translate.impl.write.GenericListWriter;
import io.fd.honeycomb.translate.write.WriterFactory;
import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder;
-import javax.annotation.Nonnull;
import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
import org.opendaylight.protocol.bgp.rib.impl.spi.BGPPeerRegistry;
import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
-import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.multiprotocol.rev151009.bgp.common.afi.safi.list.AfiSafi;
-import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.AfiSafis;
-import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.Config;
-import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.Timers;
-import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.Transport;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbors.Neighbor;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.Bgp;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.top.bgp.Neighbors;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.NetworkInstance;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.Protocols;
import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.network.instance.rev151018.network.instance.top.network.instances.network.instance.protocols.Protocol;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.AfiSafi1;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.Config1;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.Config2;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.openconfig.extensions.rev160614.Protocol1;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import javax.annotation.Nonnull;
+
/**
* Initializes writer for Bgp Neighbors ({@link Neighbor} node) and all its parents required by HC infra.
*/
public final class BgpPeerWriterFactory implements WriterFactory {
private static final InstanceIdentifier<NetworkInstance> NETWORK_INSTANCE_ID =
- InstanceIdentifier.create(NetworkInstances.class)
- .child(NetworkInstance.class);
+ InstanceIdentifier.create(NetworkInstances.class)
+ .child(NetworkInstance.class);
private static final InstanceIdentifier<Protocol> PROTOCOL_ID =
- NETWORK_INSTANCE_ID.child(Protocols.class).child(Protocol.class);
+ NETWORK_INSTANCE_ID.child(Protocols.class).child(Protocol.class);
private static final InstanceIdentifier<Neighbor> NEIGHBOR_ID =
- PROTOCOL_ID.augmentation(Protocol1.class).child(Bgp.class).child(Neighbors.class).child(Neighbor.class);
+ PROTOCOL_ID.augmentation(Protocol1.class).child(Bgp.class).child(Neighbors.class).child(Neighbor.class);
@Inject
private BgpConfiguration configuration;
// NetworkInstances
// NetworkInstance =
registry.add(new GenericListWriter<>(NETWORK_INSTANCE_ID,
- new NetworkInstanceCustomizer(configuration.bgpNetworkInstanceName)));
+ new NetworkInstanceCustomizer(configuration.bgpNetworkInstanceName)));
// Protocols
// Protocol =
registry.add(
- new GenericListWriter<>(PROTOCOL_ID, new ProtocolCustomizer(configuration.bgpProtocolInstanceName.get())));
+ new GenericListWriter<>(PROTOCOL_ID, new ProtocolCustomizer(configuration.bgpProtocolInstanceName.get())));
// Protocol1 augmentation (from bgp-openconfig-extensions)
// Bgp
// Neighbors
// Neighbor=
- final InstanceIdentifier<Neighbor> neighbor = InstanceIdentifier.create(Neighbor.class);
- registry.subtreeAdd(
- // TODO (HONEYCOMB-359): there might be more subnodes that needs to be handled
- Sets.newHashSet(
- neighbor.child(Config.class),
- neighbor.child(Config.class).augmentation(Config2.class),
- neighbor.child(AfiSafis.class),
- neighbor.child(AfiSafis.class).child(AfiSafi.class),
- neighbor.child(AfiSafis.class).child(AfiSafi.class).augmentation(AfiSafi1.class),
- neighbor.child(Timers.class),
- neighbor.child(Timers.class).child(
- org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.timers.Config.class),
- neighbor.child(Transport.class),
- neighbor.child(Transport.class).child(
- org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.transport.Config.class),
- neighbor.child(Transport.class).child(
- org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.rev151009.bgp.neighbor.group.transport.Config.class)
- .augmentation(Config1.class)
- ),
- new GenericListWriter<>(
- NEIGHBOR_ID,
- new NeighborCustomizer(globalRib, peerRegistry, tableTypeRegistry)));
+ registry.wildcardedSubtreeAdd(new GenericListWriter<>(NEIGHBOR_ID, new NeighborCustomizer(globalRib, peerRegistry, tableTypeRegistry)));
}
}
package io.fd.honeycomb.translate;
-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;
+import javax.annotation.Nonnull;
+import java.util.Collection;
+import java.util.Set;
+
/**
* Registry builder where {@link SubtreeManager}s can be added with or without relationships between them.
* The relationships express what the order of execution should be.
ModifiableSubtreeManagerRegistryBuilder<S> subtreeAdd(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
@Nonnull S handler);
+ /**
+ * Add a handler responsible for writing all complex nodes within a subtree its responsible for.
+ */
+ ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAdd(@Nonnull S handler);
+
/**
* Add a handler and make sure it will be executed before handler identifier by relatedType is executed.
*/
ModifiableSubtreeManagerRegistryBuilder<S> addBefore(@Nonnull S handler,
@Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+ ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddBefore(@Nonnull S handler,
+ @Nonnull InstanceIdentifier<?> relatedType);
+
+ ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddBefore(@Nonnull S handler,
+ @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+
ModifiableSubtreeManagerRegistryBuilder<S> subtreeAddBefore(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
@Nonnull S handler,
@Nonnull InstanceIdentifier<?> relatedType);
ModifiableSubtreeManagerRegistryBuilder<S> addAfter(@Nonnull S handler,
@Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+ ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddAfter(@Nonnull S handler,
+ @Nonnull InstanceIdentifier<?> relatedType);
+
+ ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddAfter(@Nonnull S handler,
+ @Nonnull Collection<InstanceIdentifier<?>> relatedTypes);
+
ModifiableSubtreeManagerRegistryBuilder<S> subtreeAddAfter(@Nonnull Set<InstanceIdentifier<?>> handledChildren,
@Nonnull S handler,
@Nonnull InstanceIdentifier<?> relatedType);
@Nonnull final WriteContext ctx) throws WriteFailedException;
/**
- * Indicates whether there is direct support for updating nodes handled by this writer,
+ * Indicates whether there is direct support for updating nodes handled by writer,
* or they must be broken up to individual deletes and creates.
*/
boolean supportsDirectUpdate();
+
+ /**
+ * Returns true if node identified by this identifier can be processes by this writer
+ *
+ * @param instanceIdentifier identifier to be checked
+ */
+ default boolean canProcess(@Nonnull final InstanceIdentifier<? extends DataObject> instanceIdentifier) {
+ return getManagedDataObjectType().equals(instanceIdentifier);
+ }
}
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>io.fd.honeycomb.it</groupId>
+ <artifactId>honeycomb-test-model</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.7.25</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
package io.fd.honeycomb.translate.impl.read.registry;
-import static com.google.common.base.Preconditions.checkArgument;
-
import com.google.common.collect.ImmutableMap;
import io.fd.honeycomb.translate.impl.read.GenericListReader;
import io.fd.honeycomb.translate.impl.read.GenericReader;
import io.fd.honeycomb.translate.read.registry.ReaderRegistryBuilder;
import io.fd.honeycomb.translate.util.AbstractSubtreeManagerRegistryBuilderBuilder;
import io.fd.honeycomb.translate.util.YangDAG;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
import org.opendaylight.yangtools.concepts.Builder;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
@NotThreadSafe
public final class CompositeReaderRegistryBuilder
extends AbstractSubtreeManagerRegistryBuilderBuilder<Reader<? extends DataObject, ? extends Builder<?>>, ReaderRegistry>
: SubtreeReader.createForReader(handledChildren, reader);
}
+ @Override
+ protected Reader<? extends DataObject, ? extends Builder<?>> getWildcardedSubtreeHandler(@Nonnull Reader<? extends DataObject, ? extends Builder<?>> handler) {
+ throw new UnsupportedOperationException("Wildcarded readers are not supported");
+ }
+
@Override
public <D extends DataObject> void addStructuralReader(@Nonnull InstanceIdentifier<D> id,
@Nonnull Class<? extends Builder<D>> builderType) {
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
-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.translate.write.Writer;
import io.fd.honeycomb.translate.write.registry.UpdateFailedException;
import io.fd.honeycomb.translate.write.registry.WriterRegistry;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
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;
private static final Logger LOG = LoggerFactory.getLogger(FlatWriterRegistry.class);
- // All types handled by writers directly or as children
- private final ImmutableSet<InstanceIdentifier<?>> handledTypes;
-
private final Set<InstanceIdentifier<?>> writersOrderReversed;
private final Set<InstanceIdentifier<?>> writersOrder;
- private final Map<InstanceIdentifier<?>, Writer<?>> writers;
+ private final Map<InstanceIdentifier<?>, Writer<?>> writersById;
+ private final Set<? extends 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
+ * @param writersById immutable, ordered map of writers to use to process updates. Order of the writers has to be one in
* which create and update operations should be handled. Deletes will be handled in reversed order.
* All deletes are handled before handling all the updates.
*/
- FlatWriterRegistry(@Nonnull final ImmutableMap<InstanceIdentifier<?>, Writer<?>> writers) {
- this.writers = writers;
- this.writersOrderReversed = Sets.newLinkedHashSet(Lists.reverse(Lists.newArrayList(writers.keySet())));
- this.writersOrder = writers.keySet();
- this.handledTypes = getAllHandledTypes(writers);
- }
-
- private static ImmutableSet<InstanceIdentifier<?>> getAllHandledTypes(
- @Nonnull final ImmutableMap<InstanceIdentifier<?>, Writer<?>> writers) {
- final ImmutableSet.Builder<InstanceIdentifier<?>> handledTypesBuilder = ImmutableSet.builder();
- for (Map.Entry<InstanceIdentifier<?>, Writer<?>> writerEntry : writers.entrySet()) {
- final InstanceIdentifier<?> writerType = writerEntry.getKey();
- final Writer<?> writer = writerEntry.getValue();
- handledTypesBuilder.add(writerType);
- if (writer instanceof SubtreeWriter) {
- handledTypesBuilder.addAll(((SubtreeWriter<?>) writer).getHandledChildTypes());
- }
- }
- return handledTypesBuilder.build();
+ FlatWriterRegistry(@Nonnull final ImmutableMap<InstanceIdentifier<?>, Writer<?>> writersById) {
+ this.writersById = writersById;
+ this.writersOrderReversed = Sets.newLinkedHashSet(Lists.reverse(Lists.newArrayList(writersById.keySet())));
+ this.writersOrder = writersById.keySet();
+ this.writers = writersById.entrySet().stream().map(Map.Entry::getValue).collect(Collectors.toSet());
}
@Override
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);
+ checkArgument(writer != null, "Unable to process update. Missing writers for: %s",
+ singleType);
singleTypeUpdates = getParentDataObjectUpdate(ctx, updates, writer);
}
@Nullable
private Writer<?> getSubtreeWriterResponsible(final InstanceIdentifier<?> singleType) {
- return writers.values().stream()
+ return writersById.values().stream()
.filter(w -> w instanceof SubtreeWriter)
- .filter(w -> ((SubtreeWriter<?>) w).getHandledChildTypes().contains(singleType))
+ .filter(w -> w.canProcess(singleType))
.findFirst()
.orElse(null);
}
private void checkAllTypesCanBeHandled(
@Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates) {
- if (!handledTypes.containsAll(updates.keySet())) {
- final Sets.SetView<InstanceIdentifier<?>> missingWriters = Sets.difference(updates.keySet(), handledTypes);
- LOG.warn("Unable to process update. Missing writers for: {}", missingWriters);
- throw new IllegalArgumentException("Unable to process update. Missing writers for: " + missingWriters);
+
+ List<InstanceIdentifier<?>> noWriterNodes = new ArrayList<>();
+ for (InstanceIdentifier<?> id : updates.keySet()) {
+ // either there is direct writer for the iid
+ if (writersById.containsKey(id)) {
+ continue;
+ } else {
+ // or subtree one
+ if (writers.stream().anyMatch(o -> o.canProcess(id))) {
+ continue;
+ }
+ }
+ noWriterNodes.add(id);
+ }
+
+ if (!noWriterNodes.isEmpty()) {
+ throw new IllegalArgumentException("Unable to process update. Missing writers for: " + noWriterNodes);
}
}
@Nullable
private Writer<?> getWriter(@Nonnull final InstanceIdentifier<?> singleType) {
- return writers.get(singleType);
+ return writersById.get(singleType);
}
}
import io.fd.honeycomb.translate.write.registry.ModifiableWriterRegistryBuilder;
import io.fd.honeycomb.translate.write.registry.WriterRegistry;
import io.fd.honeycomb.translate.write.registry.WriterRegistryBuilder;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.NotThreadSafe;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+import java.util.Set;
+import java.util.stream.Collectors;
+
/**
* Builder for {@link FlatWriterRegistry} allowing users to specify inter-writer relationships.
*/
return SubtreeWriter.createForWriter(handledChildren, writer);
}
+ @Override
+ protected Writer<? extends DataObject> getWildcardedSubtreeHandler(@Nonnull Writer<? extends DataObject> handler) {
+ return SubtreeWriter.createWildcardedForWriter(handler);
+ }
+
/**
* Create FlatWriterRegistry with writers ordered according to submitted relationships.
*/
private final Writer<D> delegate;
private final Set<InstanceIdentifier<?>> handledChildTypes = new HashSet<>();
+ private boolean isWildcarded = false;
- private SubtreeWriter(final Writer<D> delegate, Set<InstanceIdentifier<?>> handledTypes) {
+ private SubtreeWriter(final Writer<D> delegate, final Set<InstanceIdentifier<?>> handledTypes) {
this.delegate = delegate;
for (InstanceIdentifier<?> handledType : handledTypes) {
// Iid has to start with writer's handled root type
checkArgument(delegate.getManagedDataObjectType().getTargetType().equals(
handledType.getPathArguments().iterator().next().getType()),
"Handled node from subtree has to be identified by an instance identifier starting from: %s."
- + "Instance identifier was: %s", getManagedDataObjectType().getTargetType(), handledType);
+ + "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(
}
}
+ private SubtreeWriter(final Writer<D> delegate) {
+ this.delegate = delegate;
+ this.isWildcarded = true;
+ }
+
/**
* 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.
return delegate.supportsDirectUpdate();
}
+ @Override
+ public boolean canProcess(@Nonnull InstanceIdentifier<?> instanceIdentifier) {
+ if (isWildcarded) {
+ final Class<D> parent = delegate.getManagedDataObjectType().getTargetType();
+ for (InstanceIdentifier.PathArgument pathArgument : instanceIdentifier.getPathArguments()) {
+ if (pathArgument.getType().equals(parent)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ return handledChildTypes.contains(instanceIdentifier);
+ }
+
@Override
@Nonnull
public InstanceIdentifier<D> getManagedDataObjectType() {
@Nonnull final Writer<? extends DataObject> writer) {
return new SubtreeWriter<>(writer, handledChildren);
}
+
+ /**
+ * Wrap a writer as a subtree writer.
+ */
+ static Writer<?> createWildcardedForWriter(@Nonnull final Writer<? extends DataObject> writer) {
+ return new SubtreeWriter<>(writer);
+ }
}
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import io.fd.honeycomb.translate.util.DataObjects.DataObject2;
import io.fd.honeycomb.translate.write.DataObjectUpdate;
import io.fd.honeycomb.translate.write.WriteContext;
+import io.fd.honeycomb.translate.write.WriteFailedException;
import io.fd.honeycomb.translate.write.Writer;
import io.fd.honeycomb.translate.write.registry.UpdateFailedException;
import io.fd.honeycomb.translate.write.registry.WriterRegistry;
import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
when(writer1.getManagedDataObjectType()).thenReturn(DataObject1.IID);
when(writer2.getManagedDataObjectType()).thenReturn(DataObject2.IID);
when(writer3.getManagedDataObjectType()).thenReturn(DataObjects.DataObject3.IID);
+ when(writer4.getManagedDataObjectType()).thenReturn(DataObjects.DataObject1ChildK.IID);
+ // TODO - HONEYCOMB-412 - thenCallRealMethod doest work with default methods
+ // https://stackoverflow.com/questions/27663252/can-you-make-mockito-1-10-17-work-with-default-methods-in-interfaces
+ when(writer1.canProcess(any())).thenAnswer(answerWithImpl());
+ when(writer2.canProcess(any())).thenAnswer(answerWithImpl());
+ when(writer3.canProcess(any())).thenAnswer(answerWithImpl());
+ when(writer4.canProcess(any())).thenAnswer(answerWithImpl());
+ }
+
+ private static Answer<Object> answerWithImpl() {
+ return invocationOnMock -> new CheckedMockWriter(Writer.class.cast(invocationOnMock.getMock())).canProcess(
+ InstanceIdentifier.class.cast(invocationOnMock.getArguments()[0]));
}
@Test
inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
- verifyNoMoreInteractions(writer1);
- verifyNoMoreInteractions(writer2);
+ // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
+ // we are really interested just in invocations of processModification(),so adding specific verify to check that
+ verify(writer1,times(1)).processModification(any(),any(),any(),any());
+ verify(writer2,times(1)).processModification(any(),any(),any(),any());
+ //verifyNoMoreInteractions(writer1);
+ //verifyNoMoreInteractions(writer2);
}
@Test
inOrder.verify(writer2).processModification(iid2, dataObject2, null, ctx);
inOrder.verify(writer1).processModification(iid, dataObject, null, ctx);
- verifyNoMoreInteractions(writer1);
- verifyNoMoreInteractions(writer2);
+ // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
+ // we are really interested just in invocations of processModification(),so adding specific verify to check that
+ verify(writer1,times(1)).processModification(any(),any(),any(),any());
+ verify(writer2,times(1)).processModification(any(),any(),any(),any());
+ //verifyNoMoreInteractions(writer1);
+ //verifyNoMoreInteractions(writer2);
}
@Test
inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
- verifyNoMoreInteractions(writer1);
- verifyNoMoreInteractions(writer2);
+ // TODO - HONEYCOMB-412 -reintroduce verifyNoMoreInteractions and remove manual verify
+ // we are really interested just in invocations of processModification(),so adding specific verify to check that
+ verify(writer1,times(2)).processModification(any(),any(),any(),any());
+ verify(writer2,times(2)).processModification(any(),any(),any(),any());
+ //verifyNoMoreInteractions(writer1);
+ //verifyNoMoreInteractions(writer2);
}
@Test(expected = IllegalArgumentException.class)
final InstanceIdentifier<D> iid) {
return DataObjectUpdate.create(iid, mock(type), mock(type));
}
+
+ //TODO - HONEYCOMB-412 - remove after
+ /**
+ * Used to utilize default implementation of canProcess()
+ * */
+ static class CheckedMockWriter implements Writer{
+
+ private final Writer mockedWriter;
+
+ CheckedMockWriter(final Writer mockedWriter) {
+ this.mockedWriter = mockedWriter;
+ }
+
+ @Override
+ public void processModification(@Nonnull final InstanceIdentifier id, @Nullable final DataObject dataBefore,
+ @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx)
+ throws WriteFailedException {
+ mockedWriter.processModification(id,dataBefore,dataAfter,ctx);
+ }
+
+ @Override
+ public boolean supportsDirectUpdate() {
+ return mockedWriter.supportsDirectUpdate();
+ }
+
+ @Nonnull
+ @Override
+ public InstanceIdentifier getManagedDataObjectType() {
+ return mockedWriter.getManagedDataObjectType();
+ }
+ }
}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2017 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.translate.impl.write.registry;
+
+import io.fd.honeycomb.translate.write.Writer;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.AugTarget;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.FromAugmentListAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.SimpleNestedAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.FromAugment;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.aug.test.rev161222.aug.target.from.augment.FromAugmentEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.ContainerWithChoice;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.ContainerWithList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.container.with.choice.choice.c3.C3;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.container.with.list.ListInContainer;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.container.with.list.list.in.container.ContainerInList;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.hc.test.rev150105.container.with.list.list.in.container.container.in.list.NestedList;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WildcardedSubtreeWriterTest {
+
+ private static final InstanceIdentifier<ContainerWithList> C_WITH_LIST = InstanceIdentifier.create(ContainerWithList.class);
+ private static final InstanceIdentifier<ContainerWithChoice> C_WITH_CHOICE = InstanceIdentifier.create(ContainerWithChoice.class);
+ private static final InstanceIdentifier<AugTarget> C_AUG = InstanceIdentifier.create(AugTarget.class);
+
+ private static final InstanceIdentifier<ListInContainer> L_IN_CONTAINER = C_WITH_LIST.child(ListInContainer.class);
+ private static final InstanceIdentifier<ContainerInList> C_IN_LIST = L_IN_CONTAINER.child(ContainerInList.class);
+
+ private static final InstanceIdentifier<NestedList> N_LIST = C_IN_LIST.child(NestedList.class);
+
+ private Writer subtreeContainerWithList;
+ private Writer subtreeContainerWithChoice;
+ private Writer subtreeAugTarget;
+
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ Writer<ContainerWithList> writerContainerWithList = mock(Writer.class);
+ Writer<ContainerWithChoice> writerContainerWithChoice = mock(Writer.class);
+ Writer<AugTarget> writerAugTarget = mock(Writer.class);
+ when(writerContainerWithList.getManagedDataObjectType()).thenReturn(C_WITH_LIST);
+ when(writerContainerWithChoice.getManagedDataObjectType()).thenReturn(C_WITH_CHOICE);
+ when(writerAugTarget.getManagedDataObjectType()).thenReturn(C_AUG);
+ subtreeContainerWithList = SubtreeWriter.createWildcardedForWriter(writerContainerWithList);
+ subtreeContainerWithChoice = SubtreeWriter.createWildcardedForWriter(writerContainerWithChoice);
+ subtreeAugTarget = SubtreeWriter.createWildcardedForWriter(writerAugTarget);
+ }
+
+ @Test
+ public void testParent() {
+ assertTrue(subtreeContainerWithList.canProcess(C_WITH_LIST));
+ assertFalse(subtreeContainerWithList.canProcess(C_WITH_CHOICE));
+
+ assertTrue(subtreeContainerWithChoice.canProcess(C_WITH_CHOICE));
+ assertFalse(subtreeContainerWithChoice.canProcess(C_WITH_LIST));
+
+ assertTrue(subtreeAugTarget.canProcess(C_AUG));
+ assertFalse(subtreeAugTarget.canProcess(C_WITH_LIST));
+ }
+
+ @Test
+ public void testDirectChild() {
+ assertTrue(subtreeContainerWithList.canProcess(L_IN_CONTAINER));
+ assertFalse(subtreeContainerWithList.canProcess(C_WITH_CHOICE.child(C3.class)));
+
+ assertTrue(subtreeContainerWithChoice.canProcess(C_WITH_CHOICE.child(C3.class)));
+ assertFalse(subtreeContainerWithChoice.canProcess(L_IN_CONTAINER));
+ }
+
+ @Test
+ public void testIndirectChild() {
+ assertTrue(subtreeContainerWithList.canProcess(C_IN_LIST));
+ assertTrue(subtreeContainerWithList.canProcess(N_LIST));
+ }
+
+ @Test
+ public void testAugDirectChild() {
+ assertTrue(subtreeAugTarget.canProcess(C_AUG.augmentation(FromAugmentAugment.class).child(FromAugment.class)));
+ assertFalse(subtreeContainerWithList.canProcess(C_AUG.augmentation(FromAugmentAugment.class).child(FromAugment.class)));
+ }
+
+ @Test
+ public void testAugIndirectChild() {
+ assertTrue(subtreeAugTarget.canProcess(C_AUG.augmentation(FromAugmentAugment.class)
+ .child(FromAugment.class)
+ .augmentation(SimpleNestedAugment.class)));
+ assertFalse(subtreeContainerWithList.canProcess(C_AUG.augmentation(FromAugmentAugment.class)
+ .child(FromAugment.class)
+ .augmentation(FromAugmentListAugment.class)
+ .child(FromAugmentEntry.class)));
+ }
+}
import io.fd.honeycomb.translate.ModifiableSubtreeManagerRegistryBuilder;
import io.fd.honeycomb.translate.SubtreeManager;
import io.fd.honeycomb.translate.SubtreeManagerRegistryBuilder;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+import javax.annotation.Nonnull;
import java.util.Collection;
import java.util.HashMap;
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;
public abstract class AbstractSubtreeManagerRegistryBuilderBuilder<S extends SubtreeManager<? extends DataObject>, R>
implements ModifiableSubtreeManagerRegistryBuilder<S>, SubtreeManagerRegistryBuilder<R> {
return this;
}
+ @Override
+ public ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAdd(@Nonnull S handler) {
+ add(getWildcardedSubtreeHandler(handler));
+ return this;
+ }
+
private void checkWriterNotPresentYet(final InstanceIdentifier<?> targetType) {
Preconditions.checkArgument(!handlersMap.containsKey(targetType),
"Writer for type: %s already present: %s", targetType, handlersMap.get(targetType));
return addBefore(getSubtreeHandler(handledChildren, handler), relatedType);
}
+ @Override
+ public ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddBefore(@Nonnull final S handler,
+ @Nonnull final InstanceIdentifier<?> relatedType) {
+ return addBefore(getWildcardedSubtreeHandler(handler), relatedType);
+ }
+
@Override
public AbstractSubtreeManagerRegistryBuilderBuilder<S, R> subtreeAddBefore(
@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
return addBefore(getSubtreeHandler(handledChildren, handler), relatedTypes);
}
+ @Override
+ public ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddBefore(@Nonnull final S handler,
+ @Nonnull final Collection<InstanceIdentifier<?>> relatedTypes) {
+ return addBefore(getWildcardedSubtreeHandler(handler), relatedTypes);
+ }
+
protected abstract S getSubtreeHandler(@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
@Nonnull final S handler);
+ protected abstract S getWildcardedSubtreeHandler(@Nonnull final S handler);
+
/**
* Add handler with relationship: to be executed after handler handling relatedType.
*/
return addAfter(getSubtreeHandler(handledChildren, handler), relatedType);
}
+ @Override
+ public ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddAfter(@Nonnull final S handler,
+ @Nonnull final InstanceIdentifier<?> relatedType) {
+ return addAfter(getWildcardedSubtreeHandler(handler), relatedType);
+ }
+
@Override
public AbstractSubtreeManagerRegistryBuilderBuilder<S, R> subtreeAddAfter(
@Nonnull final Set<InstanceIdentifier<?>> handledChildren,
return addAfter(getSubtreeHandler(handledChildren, handler), relatedTypes);
}
+ @Override
+ public ModifiableSubtreeManagerRegistryBuilder<S> wildcardedSubtreeAddAfter(@Nonnull S handler, @Nonnull Collection<InstanceIdentifier<?>> relatedTypes) {
+ return addAfter(getWildcardedSubtreeHandler(handler), relatedTypes);
+ }
+
protected ImmutableMap<InstanceIdentifier<?>, S> getMappedHandlers() {
final ImmutableMap.Builder<InstanceIdentifier<?>, S> builder = ImmutableMap.builder();
// Iterate writer types according to their relationships from graph
package io.fd.honeycomb.translate.util.write;
-import static com.google.common.base.Preconditions.checkArgument;
-
import io.fd.honeycomb.translate.util.RWUtils;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
import io.fd.honeycomb.translate.write.Writer;
-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;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
public abstract class AbstractGenericWriter<D extends DataObject> implements Writer<D> {
private static final Logger LOG = LoggerFactory.getLogger(AbstractGenericWriter.class);
package io.fd.honeycomb.translate.util.write;
-import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
-
import com.google.common.util.concurrent.CheckedFuture;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
import io.fd.honeycomb.translate.write.Writer;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
+
/**
* Simple DataBroker backed writer allowing to delegate writes to different brokers.
*/