HONEYCOMB-386 - Make update optional 54/7954/7
authorJan Srnicek <jsrnicek@cisco.com>
Wed, 16 Aug 2017 07:21:24 +0000 (09:21 +0200)
committerMarek Gradzki <mgradzki@cisco.com>
Wed, 16 Aug 2017 07:56:39 +0000 (07:56 +0000)
If customizer does not support update directly,
updates for its handled nodes are broken up to delete + create pairs.

Change-Id: I2929109e8c9a1db0bef108367cf7d839135ce173
Signed-off-by: Jan Srnicek <jsrnicek@cisco.com>
24 files changed:
infra/data-impl/src/main/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegator.java
infra/data-impl/src/test/java/io/fd/honeycomb/data/impl/ModifiableDataTreeDelegatorTest.java
infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/HoneycombWriteInfraTest.java
infra/it/it-test/src/test/java/io/fd/honeycomb/data/impl/NestedAugmentationWriteTest.java
infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java
infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/registry/WriterRegistry.java
infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java
infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java
infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java
infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/NoopWriters.java [new file with mode: 0644]
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryBuilderTest.java
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java
infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriterTest.java
infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java
infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java
infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriter.java
infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/NoopWriterRegistry.java
infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriterTest.java
infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/BindingBrokerWriterTest.java
infra/translate-utils/src/test/java/io/fd/honeycomb/translate/util/write/NoopWriterRegistryTest.java
samples/interfaces/mapping/src/main/java/io/fd/honeycomb/samples/interfaces/mapping/config/InterfaceWriterCustomizer.java

index ccc4057..66dcbe5 100644 (file)
@@ -124,12 +124,13 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
             LOG.debug("ConfigDataTree.modify() diff: {}", modificationDiff);
 
             // Distinguish between updates (create + update) and deletes
-            final WriterRegistry.DataObjectUpdates baUpdates = toBindingAware(modificationDiff.getUpdates());
+            final WriterRegistry.DataObjectUpdates baUpdates =
+                    toBindingAware(writerRegistry, modificationDiff.getUpdates());
             LOG.debug("ConfigDataTree.modify() extracted updates={}", baUpdates);
 
             WriteContext ctx = getTransactionWriteContext();
             try {
-                writerRegistry.update(baUpdates, ctx);
+                writerRegistry.processModifications(baUpdates, ctx);
 
                 final CheckedFuture<Void, TransactionCommitFailedException> contextUpdateResult =
                     ((TransactionMappingContext) ctx.getMappingContext()).submit();
@@ -193,14 +194,15 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
             return new TransactionWriteContext(serializer, beforeTx, afterTx, mappingContext);
         }
 
-        private WriterRegistry.DataObjectUpdates toBindingAware(
+        private WriterRegistry.DataObjectUpdates toBindingAware(final WriterRegistry registry,
                 final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes) {
-            return ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+            return ModifiableDataTreeDelegator.toBindingAware(registry, biNodes, serializer);
         }
     }
 
     @VisibleForTesting
     static WriterRegistry.DataObjectUpdates toBindingAware(
+            final WriterRegistry registry,
             final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes,
             final BindingNormalizedNodeSerializer serializer) {
 
@@ -209,15 +211,27 @@ public final class ModifiableDataTreeDelegator extends ModifiableDataTreeManager
                 HashMultimap.create();
 
         for (Map.Entry<YangInstanceIdentifier, NormalizedNodeUpdate> biEntry : biNodes.entrySet()) {
+            final InstanceIdentifier<?> keyedId = serializer.fromYangInstanceIdentifier(biEntry.getKey());
             final InstanceIdentifier<?> unkeyedIid =
-                    RWUtils.makeIidWildcarded(serializer.fromYangInstanceIdentifier(biEntry.getKey()));
+                    RWUtils.makeIidWildcarded(keyedId);
 
             NormalizedNodeUpdate normalizedNodeUpdate = biEntry.getValue();
             final DataObjectUpdate dataObjectUpdate = toDataObjectUpdate(normalizedNodeUpdate, serializer);
             if (dataObjectUpdate != null) {
                 if (dataObjectUpdate instanceof DataObjectUpdate.DataObjectDelete) {
+                    // is delete
                     dataObjectDeletes.put(unkeyedIid, (DataObjectUpdate.DataObjectDelete) dataObjectUpdate);
+                } else if (dataObjectUpdate.getDataBefore() != null && !registry.writerSupportsUpdate(unkeyedIid)) {
+                    // is update and direct update operation is not supported
+                    // breaks update to delete + create pair
+
+                    dataObjectDeletes.put(unkeyedIid,
+                            (DataObjectUpdate.DataObjectDelete) DataObjectUpdate.DataObjectDelete
+                                    .create(keyedId, dataObjectUpdate.getDataBefore(), null));
+                    dataObjectUpdates
+                            .put(unkeyedIid, DataObjectUpdate.create(keyedId, null, dataObjectUpdate.getDataAfter()));
                 } else {
+                    // is create
                     dataObjectUpdates.put(unkeyedIid, dataObjectUpdate);
                 }
             }
index 432833b..ccd35a9 100644 (file)
@@ -128,8 +128,9 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
         dataModification.commit();
 
         final Multimap<InstanceIdentifier<?>, 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));
+        // data before should be null as it is create
+        map.put(DEFAULT_ID, DataObjectUpdate.create(DEFAULT_ID, null, DEFAULT_DATA_OBJECT));
+        verify(writer).processModifications(eq(new WriterRegistry.DataObjectUpdates(map, ImmutableMultimap.of())), any(WriteContext.class));
         assertEquals(nestedList, dataTree.takeSnapshot().readNode(NESTED_LIST_ID).get());
     }
 
@@ -147,7 +148,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
         final WriterRegistry.Reverter reverter = mock(WriterRegistry.Reverter.class);
         final TranslationException failedOnUpdateException = new TranslationException("update failed");
         doThrow(new WriterRegistry.BulkUpdateException(DEFAULT_ID, update, Collections.singleton(DEFAULT_ID), reverter, failedOnUpdateException))
-                .when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+                .when(writer).processModifications(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
 
         try {
             // Run the test
@@ -157,7 +158,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
             dataModification.commit();
             fail("WriterRegistry.RevertSuccessException was expected");
         } catch (WriterRegistry.Reverter.RevertSuccessException e) {
-            verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+            verify(writer).processModifications(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
             assertThat(e.getFailedIds(), hasItem(DEFAULT_ID));
             verify(reverter).revert(any(WriteContext.class));
         }
@@ -173,7 +174,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
         final WriterRegistry.BulkUpdateException bulkFailEx =
                 new WriterRegistry.BulkUpdateException(DEFAULT_ID, update, Collections.singleton(DEFAULT_ID), reverter,
                         failedOnUpdateException);
-        doThrow(bulkFailEx).when(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+        doThrow(bulkFailEx).when(writer).processModifications(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
 
         // Fail on revert:
         doThrow(new WriterRegistry.Reverter.RevertFailedException(bulkFailEx))
@@ -187,7 +188,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
             dataModification.commit();
             fail("WriterRegistry.Reverter.RevertFailedException was expected");
         } catch (WriterRegistry.Reverter.RevertFailedException e) {
-            verify(writer).update(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
+            verify(writer).processModifications(any(WriterRegistry.DataObjectUpdates.class), any(WriteContext.class));
             verify(reverter).revert(any(WriteContext.class));
             assertEquals(bulkFailEx, e.getCause());
         }
@@ -200,7 +201,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
     @Test
     public void testToBindingAware() throws Exception {
         when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
-
+        when(writer.writerSupportsUpdate(any())).thenReturn(true);
         final Map<YangInstanceIdentifier, NormalizedNodeUpdate> biNodes = new HashMap<>();
         // delete
         final QName nn1 = QName.create("namespace", "nn1");
@@ -229,7 +230,7 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
         biNodes.put(yid3, NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
 
         final WriterRegistry.DataObjectUpdates dataObjectUpdates =
-                ModifiableDataTreeDelegator.toBindingAware(biNodes, serializer);
+                ModifiableDataTreeDelegator.toBindingAware(writer, biNodes, serializer);
 
         assertThat(dataObjectUpdates.getDeletes().size(), is(1));
         assertThat(dataObjectUpdates.getDeletes().keySet(), hasItem(((InstanceIdentifier<?>) iid1)));
@@ -245,6 +246,57 @@ public class ModifiableDataTreeDelegatorTest extends ModificationBaseTest {
         assertThat(dataObjectUpdates.getTypeIntersection().size(), is(3));
     }
 
+    @Test
+    public void testToBindingAwareUpdateNotSupported() throws Exception {
+        when(serializer.fromNormalizedNode(any(YangInstanceIdentifier.class), eq(null))).thenReturn(null);
+        when(writer.writerSupportsUpdate(any())).thenReturn(false);
+        final Map<YangInstanceIdentifier, NormalizedNodeUpdate> 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, 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, NormalizedNodeUpdate.create(yid2, null, nn2A));
+
+        // processModifications
+        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, NormalizedNodeUpdate.create(yid3, nn3B, nn3A));
+
+        final WriterRegistry.DataObjectUpdates dataObjectUpdates =
+                ModifiableDataTreeDelegator.toBindingAware(writer, biNodes, serializer);
+
+        // should have also id and data for delete as delete + create pair was created
+        assertThat(dataObjectUpdates.getDeletes().size(), is(2));
+        assertThat(dataObjectUpdates.getDeletes().keySet(),
+                hasItems(((InstanceIdentifier<?>) iid1), (InstanceIdentifier<?>) iid3));
+        assertThat(dataObjectUpdates.getDeletes().values(), hasItems(
+                ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid1, do1B, null)),
+                ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid3, do3B, 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, null, do3A)));
+
+        assertThat(dataObjectUpdates.getTypeIntersection().size(), is(3));
+    }
+
     private <D extends DataObject> D mockDataObject(final YangInstanceIdentifier yid1,
                                        final InstanceIdentifier iid1,
                                        final NormalizedNode nn1B,
index cd1816f..a740e01 100644 (file)
@@ -176,22 +176,22 @@ public class HoneycombWriteInfraTest extends AbstractInfraTest {
         // verify(complexAugmentWriter).update(eq(COMPLEX_AUGMENT_ID), eq(null), eq(getComplexAugment()), any(WriteContext.class));
         // 1
         inOrder.verify(complexAugmentContainerWriter)
-                .update(eq(Ids.COMPLEX_AUGMENT_CONTAINER_ID), eq(null), eq(getComplexAugmentContainer()), any(WriteContext.class));
+                .processModification(eq(Ids.COMPLEX_AUGMENT_CONTAINER_ID), eq(null), eq(getComplexAugmentContainer()), any(WriteContext.class));
         // 2
         inOrder.verify(c3Writer)
-                .update(eq(Ids.C3_ID), eq(null), eq(getC3()), any(WriteContext.class));
+                .processModification(eq(Ids.C3_ID), eq(null), eq(getC3()), any(WriteContext.class));
         // 2
         verify(simpleAugmentWriter)
-                .update(eq(Ids.SIMPLE_AUGMENT_ID), eq(null), eq(getSimpleAugment()), any(WriteContext.class));
+                .processModification(eq(Ids.SIMPLE_AUGMENT_ID), eq(null), eq(getSimpleAugment()), any(WriteContext.class));
         // 3
         inOrder.verify(simpleContainerWriter)
-                .update(eq(Ids.SIMPLE_CONTAINER_ID), eq(null), eq(getSimpleContainer()), any(WriteContext.class));
+                .processModification(eq(Ids.SIMPLE_CONTAINER_ID), eq(null), eq(getSimpleContainer()), any(WriteContext.class));
         // 4
         inOrder.verify(containerWithChoiceWriter)
-                .update(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(null), eq(getContainerWithChoiceWithComplexCase()), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(null), eq(getContainerWithChoiceWithComplexCase()), any(WriteContext.class));
         // 5
         inOrder.verify(containerFromGroupingWriter)
-                .update(eq(Ids.CONTAINER_FROM_GROUPING_ID), eq(null), eq(getContainerFromGrouping()), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_FROM_GROUPING_ID), eq(null), eq(getContainerFromGrouping()), any(WriteContext.class));
 
         final KeyedInstanceIdentifier<ListInContainer, ListInContainerKey> keyedListInContainer1 =
                 Ids.CONTAINER_WITH_LIST_ID.child(ListInContainer.class, new ListInContainerKey((long) 1));
@@ -204,21 +204,21 @@ public class HoneycombWriteInfraTest extends AbstractInfraTest {
 
         // 6 - two items
         inOrder.verify(nestedListWriter)
-                .update(eq(keyedNestedList1), eq(null), eq(getSingleNestedList("1")), any(WriteContext.class));
+                .processModification(eq(keyedNestedList1), eq(null), eq(getSingleNestedList("1")), any(WriteContext.class));
         verify(nestedListWriter)
-                .update(eq(keyedNestedList2), eq(null), eq(getSingleNestedList("2")), any(WriteContext.class));
+                .processModification(eq(keyedNestedList2), eq(null), eq(getSingleNestedList("2")), any(WriteContext.class));
 
         // 7 - two items
         inOrder.verify(listInContainerWriter)
-                .update(eq(keyedListInContainer1), eq(null), eq(getSingleListInContainer((long)1)), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer1), eq(null), eq(getSingleListInContainer((long)1)), any(WriteContext.class));
         verify(listInContainerWriter)
-                .update(eq(keyedListInContainer2), eq(null), eq(getSingleListInContainer((long)2)), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer2), eq(null), eq(getSingleListInContainer((long)2)), any(WriteContext.class));
 
         // 8
         inOrder.verify(containerInListWriter)
-                .update(eq(keyedListInContainer1.child(ContainerInList.class)), eq(null), eq(getContainerInList("1")), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer1.child(ContainerInList.class)), eq(null), eq(getContainerInList("1")), any(WriteContext.class));
         verify(containerInListWriter)
-                .update(eq(keyedListInContainer2.child(ContainerInList.class)), eq(null), eq(getContainerInList("2")), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer2.child(ContainerInList.class)), eq(null), eq(getContainerInList("2")), any(WriteContext.class));
 
         // 9 - Ignored because the container has no leaves, only complex child nodes
         // inOrder.verify(containerWithListWriter)
@@ -280,39 +280,39 @@ public class HoneycombWriteInfraTest extends AbstractInfraTest {
         // Deletes are handled in reverse order
         // 1
         inOrder.verify(containerInListWriter)
-                .update(eq(keyedListInContainer1.child(ContainerInList.class)), eq(getContainerInList("1")), eq(null), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer1.child(ContainerInList.class)), eq(getContainerInList("1")), eq(null), any(WriteContext.class));
         verify(containerInListWriter)
-                .update(eq(keyedListInContainer2.child(ContainerInList.class)), eq(getContainerInList("2")), eq(null), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer2.child(ContainerInList.class)), eq(getContainerInList("2")), eq(null), any(WriteContext.class));
 
         // 2
         inOrder.verify(listInContainerWriter)
-                .update(eq(keyedListInContainer1), eq(getSingleListInContainer((long)1)), eq(null),  any(WriteContext.class));
+                .processModification(eq(keyedListInContainer1), eq(getSingleListInContainer((long)1)), eq(null),  any(WriteContext.class));
         verify(listInContainerWriter)
-                .update(eq(keyedListInContainer2), eq(getSingleListInContainer((long)2)), eq(null), any(WriteContext.class));
+                .processModification(eq(keyedListInContainer2), eq(getSingleListInContainer((long)2)), eq(null), any(WriteContext.class));
 
         // 3
         inOrder.verify(nestedListWriter)
-                .update(eq(keyedNestedList1), eq(getSingleNestedList("1")), eq(null), any(WriteContext.class));
+                .processModification(eq(keyedNestedList1), eq(getSingleNestedList("1")), eq(null), any(WriteContext.class));
         verify(nestedListWriter)
-                .update(eq(keyedNestedList2), eq(getSingleNestedList("2")), eq(null), any(WriteContext.class));
+                .processModification(eq(keyedNestedList2), eq(getSingleNestedList("2")), eq(null), any(WriteContext.class));
         // 4
         inOrder.verify(containerFromGroupingWriter)
-                .update(eq(Ids.CONTAINER_FROM_GROUPING_ID), eq(getContainerFromGrouping()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_FROM_GROUPING_ID), eq(getContainerFromGrouping()), eq(null), any(WriteContext.class));
         // 5
         inOrder.verify(containerWithChoiceWriter)
-                .update(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(getContainerWithChoiceWithComplexCase()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(getContainerWithChoiceWithComplexCase()), eq(null), any(WriteContext.class));
         // 6
         inOrder.verify(simpleContainerWriter)
-                .update(eq(Ids.SIMPLE_CONTAINER_ID), eq(getSimpleContainer()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.SIMPLE_CONTAINER_ID), eq(getSimpleContainer()), eq(null), any(WriteContext.class));
         // 7
         verify(simpleAugmentWriter)
-                .update(eq(Ids.SIMPLE_AUGMENT_ID), eq(getSimpleAugment()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.SIMPLE_AUGMENT_ID), eq(getSimpleAugment()), eq(null), any(WriteContext.class));
         // 8
         inOrder.verify(c3Writer)
-                .update(eq(Ids.C3_ID), eq(getC3()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.C3_ID), eq(getC3()), eq(null), any(WriteContext.class));
         // 9
         inOrder.verify(complexAugmentContainerWriter)
-                .update(eq(Ids.COMPLEX_AUGMENT_CONTAINER_ID), eq(getComplexAugmentContainer()), eq(null), any(WriteContext.class));
+                .processModification(eq(Ids.COMPLEX_AUGMENT_CONTAINER_ID), eq(getComplexAugmentContainer()), eq(null), any(WriteContext.class));
 
         for (Writer<?> orderedWriter : orderedWriters) {
             verify(orderedWriter).getManagedDataObjectType();
@@ -493,7 +493,7 @@ public class HoneycombWriteInfraTest extends AbstractInfraTest {
 
         verify(containerWithChoiceWriter, atLeastOnce()).getManagedDataObjectType();
         verify(containerWithChoiceWriter)
-                .update(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(null), eq(containerWithChoice), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(null), eq(containerWithChoice), any(WriteContext.class));
         verifyNoMoreInteractions(containerWithChoiceWriter);
 
         // Test delete sub-node
@@ -504,7 +504,7 @@ public class HoneycombWriteInfraTest extends AbstractInfraTest {
 
         verify(containerWithChoiceWriter, atLeastOnce()).getManagedDataObjectType();
         verify(containerWithChoiceWriter)
-                .update(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(containerWithChoice), eq(containerWithChoiceEmpty), any(WriteContext.class));
+                .processModification(eq(Ids.CONTAINER_WITH_CHOICE_ID), eq(containerWithChoice), eq(containerWithChoiceEmpty), any(WriteContext.class));
         verifyNoMoreInteractions(containerWithChoiceWriter);
 
         // Test write with subtree node that's not handled by subtree writer
index 3b7d4a1..37dac0f 100644 (file)
@@ -162,7 +162,7 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
 
         dataModification.commit();
 
-        verify(simpleAugmentWriter).update(eq(SIMPLE_AUGMENT_ID), eq(null), eq(simpleAugment()), any(WriteContext.class));
+        verify(simpleAugmentWriter).processModification(eq(SIMPLE_AUGMENT_ID), eq(null), eq(simpleAugment()), any(WriteContext.class));
     }
 
     @Test
@@ -181,9 +181,9 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
 
         dataModification.commit();
 
-        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
-        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugmentSimple(augData)), any(WriteContext.class));
-        verify(simpleNestedAugmentWriter).update(eq(SIMPLE_NESTED_AUGMENT_ID), eq(null), eq(augData), any(WriteContext.class));
+        verify(augTargetWriter).processModification(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
+        verify(fromAugmentWriter).processModification(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugmentSimple(augData)), any(WriteContext.class));
+        verify(simpleNestedAugmentWriter).processModification(eq(SIMPLE_NESTED_AUGMENT_ID), eq(null), eq(augData), any(WriteContext.class));
     }
 
     private SimpleAugment simpleAugment() {
@@ -204,9 +204,9 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
 
         dataModification.commit();
 
-        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
-        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugment()), any(WriteContext.class));
-        verify(fromAugment2Writer).update(eq(FROM_AUGMENT2_ID), eq(null), eq(fromAugment2()), any(WriteContext.class));
+        verify(augTargetWriter).processModification(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
+        verify(fromAugmentWriter).processModification(eq(FROM_AUGMENT_ID), eq(null), eq(fromAugment()), any(WriteContext.class));
+        verify(fromAugment2Writer).processModification(eq(FROM_AUGMENT2_ID), eq(null), eq(fromAugment2()), any(WriteContext.class));
     }
 
     @Test
@@ -228,10 +228,10 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
         dataModification.commit();
 
         final ArgumentCaptor<DataObject> doCaptor = ArgumentCaptor.forClass(DataObject.class);
-        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
+        verify(augTargetWriter).processModification(eq(AUG_TARGET_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
         assertEquals(data.getSomeLeaf(), ((AugTarget)doCaptor.getValue()).getSomeLeaf());
 
-        verify(fromAugmentWriter).update(eq(FROM_AUGMENT_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
+        verify(fromAugmentWriter).processModification(eq(FROM_AUGMENT_ID), eq(null), doCaptor.capture(), any(WriteContext.class));
         assertEquals(fromAugment.getSomeLeaf(), ((FromAugment)doCaptor.getValue()).getSomeLeaf());
 
 
@@ -241,9 +241,9 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
             FROM_AUGMENT_LIST_AUGMENT_ID.child(FromAugmentEntry.class, new FromAugmentEntryKey("2"));
 
         verify(fromAugmentListWriter)
-            .update(eq(keyedNestedList1), eq(null), eq(entries.get(0)), any(WriteContext.class));
+            .processModification(eq(keyedNestedList1), eq(null), eq(entries.get(0)), any(WriteContext.class));
         verify(fromAugmentListWriter)
-            .update(eq(keyedNestedList2), eq(null), eq(entries.get(1)), any(WriteContext.class));
+            .processModification(eq(keyedNestedList2), eq(null), eq(entries.get(1)), any(WriteContext.class));
     }
 
     @Test
@@ -270,19 +270,19 @@ public class NestedAugmentationWriteTest extends AbstractInfraTest {
         dataModification.commit();
 
         // verify aug target update:
-        verify(augTargetWriter).update(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
+        verify(augTargetWriter).processModification(eq(AUG_TARGET_ID), eq(null), eq(data), any(WriteContext.class));
 
         // verify list customizer update:
         final KeyedInstanceIdentifier<ListFromAugment, ListFromAugmentKey> keyedNestedList =
             LIST_AUGMENT_ID.child(ListFromAugment.class, new ListFromAugmentKey("some-leaf-val"));
         final ArgumentCaptor<DataObject> doCaptor = ArgumentCaptor.forClass(DataObject.class);
         verify(listFromAugmentWriter)
-            .update(eq(keyedNestedList), eq(null), doCaptor.capture(), any(WriteContext.class));
+            .processModification(eq(keyedNestedList), eq(null), doCaptor.capture(), any(WriteContext.class));
         assertEquals(list.get(0).getSomeLeaf(), ((ListFromAugment) doCaptor.getValue()).getSomeLeaf());
 
         // verify list augmentation customizer update:
         verify(listFromAugmentAugmentWriter)
-            .update(eq(keyedNestedList.augmentation(ListFromAugmentAugment.class)), eq(null), doCaptor.capture(),
+            .processModification(eq(keyedNestedList.augmentation(ListFromAugmentAugment.class)), eq(null), doCaptor.capture(),
                 any(WriteContext.class));
         assertEquals(listAugmentation.getNewLeaf(), ((ListFromAugmentAugment) doCaptor.getValue()).getNewLeaf());
     }
index e653801..1a16b72 100644 (file)
@@ -33,7 +33,7 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 public interface Writer<D extends DataObject> extends SubtreeManager<D> {
 
     /**
-     * Handle update operation. U from CRUD.
+     * Process modifications and translate them as create/update/delete operations to lower level
      *
      * @param id         Identifier of data being written
      * @param dataBefore Old data
@@ -41,8 +41,14 @@ public interface Writer<D extends DataObject> extends SubtreeManager<D> {
      * @param ctx        Write context enabling writer to get information about candidate data as well as current data
      * @throws WriteFailedException if update failed
      */
-    void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-                @Nullable final DataObject dataBefore,
-                @Nullable final DataObject dataAfter,
-                @Nonnull final WriteContext ctx) throws WriteFailedException;
+    void processModification(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+                             @Nullable final DataObject dataBefore,
+                             @Nullable final DataObject dataAfter,
+                             @Nonnull final WriteContext ctx) throws WriteFailedException;
+
+    /**
+     * Indicates whether there is direct support for updating nodes handled by this writer,
+     * or they must be broken up to individual deletes and creates.
+     */
+    boolean supportsDirectUpdate();
 }
index 5520c00..a297b6d 100644 (file)
@@ -41,12 +41,19 @@ public interface WriterRegistry {
      * @throws BulkUpdateException in case bulk update fails
      * @throws TranslationException in case some other error occurs while processing update request
      */
-    void update(@Nonnull DataObjectUpdates updates,
-                @Nonnull WriteContext ctx) throws TranslationException;
+    void processModifications(@Nonnull DataObjectUpdates updates,
+                              @Nonnull WriteContext ctx) throws TranslationException;
 
     /**
-     * Simple DTO containing updates for {@link WriterRegistry}. Currently only deletes and updates (create + update)
-     * are distinguished.
+     * Indicates direct support for update operation on provided type
+     *
+     * @param type data object type
+     */
+    boolean writerSupportsUpdate(@Nonnull InstanceIdentifier<?> type);
+
+    /**
+     * Simple DTO containing updates for {@link WriterRegistry}. Currently only deletes and updates (create +
+     * update) are distinguished.
      */
     @Beta
     final class DataObjectUpdates {
index 4e05ce0..6fbef8e 100644 (file)
@@ -16,6 +16,8 @@
 
 package io.fd.honeycomb.translate.impl.write;
 
+import static io.fd.honeycomb.translate.impl.write.GenericWriter.isUpdateSupported;
+
 import io.fd.honeycomb.translate.spi.write.ListWriterCustomizer;
 import io.fd.honeycomb.translate.util.RWUtils;
 import io.fd.honeycomb.translate.util.write.AbstractGenericWriter;
@@ -39,7 +41,7 @@ public final class GenericListWriter<D extends DataObject & Identifiable<K>, K e
 
     public GenericListWriter(@Nonnull final InstanceIdentifier<D> type,
                              @Nonnull final ListWriterCustomizer<D, K> customizer) {
-        super(type);
+        super(type, isUpdateSupported(customizer));
         this.customizer = customizer;
     }
 
index 30d15e3..086936e 100644 (file)
@@ -23,18 +23,40 @@ import io.fd.honeycomb.translate.write.WriteFailedException;
 import javax.annotation.Nonnull;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Generic writer with customizable behavior thanks to injected customizer.
  */
 public final class GenericWriter<D extends DataObject> extends AbstractGenericWriter<D> {
 
+    private static final Logger LOG = LoggerFactory.getLogger(GenericWriter.class);
+    private static final String UPDATE_M = "updateCurrentAttributes";
     private final WriterCustomizer<D> customizer;
 
     public GenericWriter(@Nonnull final InstanceIdentifier<D> type,
                          @Nonnull final WriterCustomizer<D> customizer) {
-        super(type);
+        super(type, isUpdateSupported(customizer));
         this.customizer = customizer;
+
+    }
+
+    static boolean isUpdateSupported(final @Nonnull WriterCustomizer<?> customizer) {
+        try {
+            // if customizer overrides updateCurrentAttributes method, it will be used, otherwise updates will be broken into individual
+            // delete + create pairs
+            final Class<? extends WriterCustomizer> customizerClass = customizer.getClass();
+            final Class<?> updateDeclaringClass = customizerClass
+                    .getMethod(UPDATE_M, InstanceIdentifier.class, DataObject.class, DataObject.class, WriteContext.class)
+                    .getDeclaringClass();
+            final boolean supportsUpdate = !WriterCustomizer.class.equals(updateDeclaringClass);
+            LOG.debug("Customizer {} update support : {}|Update declaring class {}", customizerClass, supportsUpdate,
+                    updateDeclaringClass);
+            return supportsUpdate;
+        } catch (NoSuchMethodException e) {
+            throw new IllegalStateException("Unable to detect if customizer supports update", e);
+        }
     }
 
     @Override
index 418f4b4..e21297a 100644 (file)
@@ -92,8 +92,8 @@ final class FlatWriterRegistry implements WriterRegistry {
     }
 
     @Override
-    public void update(@Nonnull final DataObjectUpdates updates,
-                       @Nonnull final WriteContext ctx) throws TranslationException {
+    public void processModifications(@Nonnull final DataObjectUpdates updates,
+                                     @Nonnull final WriteContext ctx) throws TranslationException {
         if (updates.isEmpty()) {
             return;
         }
@@ -115,6 +115,17 @@ final class FlatWriterRegistry implements WriterRegistry {
         LOG.trace("Update successful for: {}", updates);
     }
 
+    @Override
+    public boolean writerSupportsUpdate(@Nonnull final InstanceIdentifier<?> type) {
+        Writer writer = getWriter(type);
+
+        if(writer == null){
+            writer = getSubtreeWriterResponsible(type);
+        }
+
+        return checkNotNull(writer, "Unable to find writer for %s", type).supportsDirectUpdate();
+    }
+
     private void singleUpdate(@Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
                               @Nonnull final WriteContext ctx) throws WriteFailedException {
         if (updates.isEmpty()) {
@@ -136,16 +147,17 @@ final class FlatWriterRegistry implements WriterRegistry {
 
         LOG.trace("Performing single type update with writer: {}", writer);
         for (DataObjectUpdate singleUpdate : singleTypeUpdates) {
-            writer.update(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
+            writer.processModification(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
         }
     }
 
+    @Nullable
     private Writer<?> getSubtreeWriterResponsible(final InstanceIdentifier<?> singleType) {
         return writers.values().stream()
                 .filter(w -> w instanceof SubtreeWriter)
                 .filter(w -> ((SubtreeWriter<?>) w).getHandledChildTypes().contains(singleType))
                 .findFirst()
-                .get();
+                .orElse(null);
     }
 
     private Collection<DataObjectUpdate> getParentDataObjectUpdate(final WriteContext ctx,
@@ -208,7 +220,7 @@ final class FlatWriterRegistry implements WriterRegistry {
 
             for (DataObjectUpdate singleUpdate : writersData) {
                 try {
-                    writer.update(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
+                    writer.processModification(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
                     processedNodes.add(singleUpdate.getId());
                     LOG.trace("Update successful for type: {}", writerType);
                     LOG.debug("Update successful for: {}", singleUpdate);
index fc6ecc6..b2a571b 100644 (file)
@@ -62,11 +62,16 @@ final class SubtreeWriter<D extends DataObject> implements Writer<D> {
     }
 
     @Override
-    public void update(
+    public void processModification(
             @Nonnull final InstanceIdentifier<? extends DataObject> id,
             @Nullable final DataObject dataBefore,
             @Nullable final DataObject dataAfter, @Nonnull final WriteContext ctx) throws WriteFailedException {
-        delegate.update(id, dataBefore, dataAfter, ctx);
+        delegate.processModification(id, dataBefore, dataAfter, ctx);
+    }
+
+    @Override
+    public boolean supportsDirectUpdate() {
+        return delegate.supportsDirectUpdate();
     }
 
     @Override
index 2f50ece..91785b2 100644 (file)
@@ -71,13 +71,13 @@ public class GenericListWriterTest {
                 (InstanceIdentifier<IdentifiableDataObject>) InstanceIdentifier.create(Collections
                         .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, keyAfter)));
 
-        writer.update(DATA_OBJECT_ID, before, after, ctx);
+        writer.processModification(DATA_OBJECT_ID, before, after, ctx);
         verify(customizer).updateCurrentAttributes(keyedIdBefore, before, after, ctx);
 
-        writer.update(DATA_OBJECT_ID, before, null, ctx);
+        writer.processModification(DATA_OBJECT_ID, before, null, ctx);
         verify(customizer).deleteCurrentAttributes(keyedIdBefore, before, ctx);
 
-        writer.update(DATA_OBJECT_ID, null, after, ctx);
+        writer.processModification(DATA_OBJECT_ID, null, after, ctx);
         verify(customizer).writeCurrentAttributes(keyedIdAfter, after, ctx);
     }
 
index 3caea57..c9d381a 100644 (file)
@@ -17,6 +17,8 @@
 package io.fd.honeycomb.translate.impl.write;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 
@@ -53,13 +55,13 @@ public class GenericWriterTest {
     @Test
     public void testUpdate() throws Exception {
         assertEquals(DATA_OBJECT_ID, writer.getManagedDataObjectType());
-        writer.update(DATA_OBJECT_ID, before, after, ctx);
+        writer.processModification(DATA_OBJECT_ID, before, after, ctx);
         verify(customizer).updateCurrentAttributes(DATA_OBJECT_ID, before, after, ctx);
 
-        writer.update(DATA_OBJECT_ID, before, null, ctx);
+        writer.processModification(DATA_OBJECT_ID, before, null, ctx);
         verify(customizer).deleteCurrentAttributes(DATA_OBJECT_ID, before, ctx);
 
-        writer.update(DATA_OBJECT_ID, null, after, ctx);
+        writer.processModification(DATA_OBJECT_ID, null, after, ctx);
         verify(customizer).writeCurrentAttributes(DATA_OBJECT_ID, after, ctx);
     }
 
@@ -85,4 +87,11 @@ public class GenericWriterTest {
         writer = new GenericWriter<>(DATA_OBJECT_ID, customizer);
         writer.deleteCurrentAttributes(DATA_OBJECT_ID, before, ctx);
     }
+
+    @Test
+    public void testUpdateSupported() {
+        assertFalse(GenericWriter.isUpdateSupported(new NoopWriters.NonDirectUpdateWriterCustomizer()));
+        assertTrue(GenericWriter.isUpdateSupported(new NoopWriters.DirectUpdateWriterCustomizer()));
+        assertTrue(GenericWriter.isUpdateSupported(new NoopWriters.ParentImplDirectUpdateWriterCustomizer()));
+    }
 }
\ No newline at end of file
diff --git a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/NoopWriters.java b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/NoopWriters.java
new file mode 100644 (file)
index 0000000..8ea91d2
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import io.fd.honeycomb.translate.spi.write.WriterCustomizer;
+import io.fd.honeycomb.translate.write.WriteContext;
+import io.fd.honeycomb.translate.write.WriteFailedException;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public final class NoopWriters {
+
+    private NoopWriters() {
+    }
+
+    public static class NonDirectUpdateWriterCustomizer implements WriterCustomizer<DataObject> {
+
+        @Override
+        public void writeCurrentAttributes(@Nonnull final InstanceIdentifier<DataObject> id,
+                                           @Nonnull final DataObject dataAfter,
+                                           @Nonnull final WriteContext writeContext)
+                throws WriteFailedException {
+            // NOOP
+        }
+
+        @Override
+        public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<DataObject> id,
+                                            @Nonnull final DataObject dataBefore,
+                                            @Nonnull final WriteContext writeContext)
+                throws WriteFailedException {
+            // NOOP
+        }
+    }
+
+    public static class DirectUpdateWriterCustomizer extends NonDirectUpdateWriterCustomizer {
+
+        @Override
+        public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<DataObject> id,
+                                            @Nonnull final DataObject dataBefore, @Nonnull final DataObject dataAfter,
+                                            @Nonnull final WriteContext writeContext) throws WriteFailedException {
+            // is direct support
+        }
+    }
+
+    public static class ParentImplDirectUpdateWriterCustomizer extends DirectUpdateWriterCustomizer {
+        // parent impls directly
+    }
+}
index feaba72..dd1adb3 100644 (file)
@@ -87,9 +87,9 @@ public class FlatWriterRegistryBuilderTest {
                 Multimaps.forMap(Collections.singletonMap(id, update)),
                 Multimaps.forMap(Collections.emptyMap()));
         final WriteContext ctx = mock(WriteContext.class);
-        build.update(updates, ctx);
+        build.processModifications(updates, ctx);
 
-        verify(writer).update(id, before, after, ctx);
+        verify(writer).processModification(id, before, after, ctx);
     }
 
     @Test(expected = IllegalArgumentException.class)
@@ -104,7 +104,7 @@ public class FlatWriterRegistryBuilderTest {
         final WriterRegistry.DataObjectUpdates updates = new WriterRegistry.DataObjectUpdates(
                 Multimaps.forMap(Collections.singletonMap(id2, update2)),
                 Multimaps.forMap(Collections.emptyMap()));
-        build.update(updates, mock(WriteContext.class));
+        build.processModifications(updates, mock(WriteContext.class));
     }
 
     @Test
index f66ab8f..72a91cb 100644 (file)
@@ -84,10 +84,10 @@ public class FlatWriterRegistryTest {
         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);
+        flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
 
-        verify(writer1).update(iid, dataObject, dataObject, ctx);
-        verify(writer1).update(iid2, dataObject, dataObject, ctx);
+        verify(writer1).processModification(iid, dataObject, dataObject, ctx);
+        verify(writer1).processModification(iid2, dataObject, dataObject, ctx);
         // Invoked when registry is being created
         verifyNoMoreInteractions(writer1);
         verifyZeroInteractions(writer2);
@@ -105,11 +105,11 @@ public class FlatWriterRegistryTest {
         final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
         final DataObject2 dataObject2 = mock(DataObject2.class);
         updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
-        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+        flatWriterRegistry.processModifications(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);
+        inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
+        inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
 
         verifyNoMoreInteractions(writer1);
         verifyNoMoreInteractions(writer2);
@@ -128,12 +128,12 @@ public class FlatWriterRegistryTest {
         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);
+        flatWriterRegistry.processModifications(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);
+        inOrder.verify(writer2).processModification(iid2, dataObject2, null, ctx);
+        inOrder.verify(writer1).processModification(iid, dataObject, null, ctx);
 
         verifyNoMoreInteractions(writer1);
         verifyNoMoreInteractions(writer2);
@@ -159,15 +159,15 @@ public class FlatWriterRegistryTest {
                 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);
+        flatWriterRegistry.processModifications(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);
+        inOrder.verify(writer2).processModification(iid2, dataObject2, null, ctx);
+        inOrder.verify(writer1).processModification(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);
+        inOrder.verify(writer1).processModification(iid, dataObject, dataObject, ctx);
+        inOrder.verify(writer2).processModification(iid2, dataObject2, dataObject2, ctx);
 
         verifyNoMoreInteractions(writer1);
         verifyNoMoreInteractions(writer2);
@@ -181,7 +181,7 @@ public class FlatWriterRegistryTest {
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
         addUpdate(updates, DataObject1.class);
         addUpdate(updates, DataObject2.class);
-        flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+        flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
     }
 
     @Test
@@ -191,14 +191,14 @@ public class FlatWriterRegistryTest {
 
         // Writer1 always fails
         doThrow(new RuntimeException()).when(writer1)
-                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
         addUpdate(updates, DataObject1.class);
         addUpdate(updates, DataObject2.class);
 
         try {
-            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
             fail("Bulk update should have failed on writer1");
         } catch (WriterRegistry.BulkUpdateException e) {
             assertThat(e.getUnrevertedSubtrees(), hasSize(2));
@@ -215,7 +215,7 @@ public class FlatWriterRegistryTest {
 
         // Writer1 always fails
         doThrow(new RuntimeException()).when(writer3)
-                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
         addUpdate(updates, DataObject1.class);
@@ -226,26 +226,26 @@ public class FlatWriterRegistryTest {
         updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, before2, after2));
 
         try {
-            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            flatWriterRegistry.processModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
             fail("Bulk update should have failed on writer1");
         } catch (WriterRegistry.BulkUpdateException e) {
             assertThat(e.getUnrevertedSubtrees().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));
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
             inOrder.verify(writer2)
-                .update(iid2, before2, after2, ctx);
+                .processModification(iid2, before2, after2, ctx);
             inOrder.verify(writer3)
-                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
 
             e.revertChanges(revertWriteContext);
             // Revert changes. Successful updates are iterated in reverse
             // also binding other write context,to verify if update context is not reused
             inOrder.verify(writer2)
-                    .update(iid2, after2, before2, revertWriteContext);
+                    .processModification(iid2, after2, before2, revertWriteContext);
             inOrder.verify(writer1)
-                    .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), eq(revertWriteContext));
+                    .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), eq(revertWriteContext));
             verifyNoMoreInteractions(writer3);
         }
     }
@@ -258,7 +258,7 @@ public class FlatWriterRegistryTest {
 
         // Writer1 always fails
         doThrow(new RuntimeException()).when(writer3)
-                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
         addUpdate(updates, DataObject1.class);
@@ -266,12 +266,12 @@ public class FlatWriterRegistryTest {
         addUpdate(updates, DataObjects.DataObject3.class);
 
         try {
-            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            flatWriterRegistry.processModifications(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));
+                    .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class), any(WriteContext.class));
             try {
                 e.revertChanges(revertWriteContext);
             } catch (WriterRegistry.Reverter.RevertFailedException e1) {
@@ -293,19 +293,19 @@ public class FlatWriterRegistryTest {
 
         // Writer1 always fails
         doThrow(new RuntimeException()).when(writer1)
-                .update(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class),
+                .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class),
                         any(WriteContext.class));
 
         final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
         addKeyedUpdate(updates,DataObjects.DataObject1ChildK.class);
         addUpdate(updates, DataObject1.class);
         try {
-            flatWriterRegistry.update(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+            flatWriterRegistry.processModifications(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),
+                    .processModification(any(InstanceIdentifier.class), any(DataObject.class), any(DataObject.class),
                             any(WriteContext.class));
             try {
                 e.revertChanges(revertWriteContext);
index ff2f831..65b2f37 100644 (file)
 
 package io.fd.honeycomb.translate.impl.write.registry;
 
-import static org.hamcrest.CoreMatchers.hasItem;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
 import com.google.common.collect.Sets;
+import io.fd.honeycomb.translate.impl.write.GenericWriter;
+import io.fd.honeycomb.translate.impl.write.NoopWriters.DirectUpdateWriterCustomizer;
+import io.fd.honeycomb.translate.impl.write.NoopWriters.NonDirectUpdateWriterCustomizer;
+import io.fd.honeycomb.translate.impl.write.NoopWriters.ParentImplDirectUpdateWriterCustomizer;
 import io.fd.honeycomb.translate.util.DataObjects;
 import io.fd.honeycomb.translate.write.Writer;
 import java.util.Collections;
+import java.util.Set;
 import org.hamcrest.CoreMatchers;
 import org.junit.Before;
 import org.junit.Test;
@@ -79,7 +85,28 @@ public class SubtreeWriterTest {
 
         assertEquals(writer.getManagedDataObjectType(), forWriter.getManagedDataObjectType());
         assertEquals(1, forWriter.getHandledChildTypes().size());
-        assertThat(forWriter.getHandledChildTypes(), CoreMatchers.hasItem(DataObjects.DataObject4.DataObject41.DataObject411.IID));
+        assertThat(forWriter.getHandledChildTypes(),
+                CoreMatchers.hasItem(DataObjects.DataObject4.DataObject41.DataObject411.IID));
+    }
+
+    @Test
+    public void testUpdateSupported() {
+        // test supportsDirectUpdate(), because subtree writer overrides this method and delegate call on delegate writer
+        final InstanceIdentifier<DataObject> fakeIID = InstanceIdentifier.create(DataObject.class);
+        final Set<InstanceIdentifier<?>> handledChildren = Collections.emptySet();
+
+        final NonDirectUpdateWriterCustomizer nonDirectCustomizer = new NonDirectUpdateWriterCustomizer();
+        final DirectUpdateWriterCustomizer directCustomizer = new DirectUpdateWriterCustomizer();
+        final ParentImplDirectUpdateWriterCustomizer parentImplCustomizer =
+                new ParentImplDirectUpdateWriterCustomizer();
+
+        final GenericWriter<DataObject> nonDirectWriter = new GenericWriter<>(fakeIID, nonDirectCustomizer);
+        final GenericWriter<DataObject> directWriter = new GenericWriter<>(fakeIID, directCustomizer);
+        final GenericWriter<DataObject> parentImplWriter = new GenericWriter<>(fakeIID, parentImplCustomizer);
+
+        assertFalse(SubtreeWriter.createForWriter(handledChildren, nonDirectWriter).supportsDirectUpdate());
+        assertTrue(SubtreeWriter.createForWriter(handledChildren, directWriter).supportsDirectUpdate());
+        assertTrue(SubtreeWriter.createForWriter(handledChildren, parentImplWriter).supportsDirectUpdate());
     }
 
 }
\ No newline at end of file
index 8148657..308713e 100644 (file)
@@ -46,6 +46,8 @@ public interface WriterCustomizer<D extends DataObject> {
 
     /**
      * Handle update operation. U from CRUD.
+     * By default, updates will be broken into delete + create.
+     * Override this if there is a direct support for updates on lower level
      *
      * @param id Identifier(from root) of data being written
      * @param dataBefore Old data
@@ -54,10 +56,14 @@ public interface WriterCustomizer<D extends DataObject> {
      *
      * @throws WriteFailedException if update was unsuccessful
      */
-    void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id,
-                                 @Nonnull final D dataBefore,
-                                 @Nonnull final D dataAfter,
-                                 @Nonnull final WriteContext writeContext) throws WriteFailedException;
+    default void updateCurrentAttributes(@Nonnull final InstanceIdentifier<D> id,
+                                         @Nonnull final D dataBefore,
+                                         @Nonnull final D dataAfter,
+                                         @Nonnull final WriteContext writeContext) throws WriteFailedException {
+        throw new UnsupportedOperationException(
+                "Default implementation of updateCurrentAttributes should not be invoked." +
+                        "Either override this method or do not invoke it directly");
+    }
 
     /**
      * Handle delete operation. D from CRUD.
index ff2174e..e2ea115 100644 (file)
@@ -18,8 +18,8 @@ package io.fd.honeycomb.translate.util.write;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import io.fd.honeycomb.translate.write.WriteContext;
 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;
@@ -34,9 +34,11 @@ public abstract class AbstractGenericWriter<D extends DataObject> implements Wri
     private static final Logger LOG = LoggerFactory.getLogger(AbstractGenericWriter.class);
 
     private final InstanceIdentifier<D> instanceIdentifier;
+    private final boolean supportsUpdate;
 
-    protected AbstractGenericWriter(final InstanceIdentifier<D> type) {
+    protected AbstractGenericWriter(final InstanceIdentifier<D> type, final boolean supportsUpdate) {
         this.instanceIdentifier = RWUtils.makeIidWildcarded(type);
+        this.supportsUpdate = supportsUpdate;
     }
 
     protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
@@ -67,10 +69,10 @@ public abstract class AbstractGenericWriter<D extends DataObject> implements Wri
 
     @SuppressWarnings("unchecked")
     @Override
-    public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-                       @Nullable final DataObject dataBefore,
-                       @Nullable final DataObject dataAfter,
-                       @Nonnull final WriteContext ctx) throws WriteFailedException {
+    public void processModification(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+                                    @Nullable final DataObject dataBefore,
+                                    @Nullable final DataObject dataAfter,
+                                    @Nonnull final WriteContext ctx) throws WriteFailedException {
         LOG.debug("{}: Updating : {}", this, id);
         LOG.trace("{}: Updating : {}, from: {} to: {}", this, id, dataBefore, dataAfter);
 
@@ -134,4 +136,9 @@ public abstract class AbstractGenericWriter<D extends DataObject> implements Wri
     public String toString() {
         return String.format("Writer[%s]", getManagedDataObjectType().getTargetType().getSimpleName());
     }
+
+    @Override
+    public boolean supportsDirectUpdate() {
+        return supportsUpdate;
+    }
 }
index f06e6ae..7b68376 100644 (file)
@@ -49,9 +49,9 @@ public final class BindingBrokerWriter<D extends DataObject> implements Writer<D
     }
 
     @Override
-    public void update(@Nonnull final InstanceIdentifier<? extends DataObject> id,
-                       @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
-                       @Nonnull final WriteContext ctx) throws WriteFailedException {
+    public void processModification(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+                                    @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
+                                    @Nonnull final WriteContext ctx) throws WriteFailedException {
         final WriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
         writeTransaction.put(CONFIGURATION, (InstanceIdentifier<DataObject>) id, dataAfter);
         final CheckedFuture<Void, TransactionCommitFailedException> result = writeTransaction.submit();
@@ -61,4 +61,9 @@ public final class BindingBrokerWriter<D extends DataObject> implements Writer<D
             throw new WriteFailedException(id, e);
         }
     }
+
+    @Override
+    public boolean supportsDirectUpdate() {
+        return false;
+    }
 }
index 8f15c6f..13ae12f 100644 (file)
@@ -20,6 +20,7 @@ import io.fd.honeycomb.translate.TranslationException;
 import io.fd.honeycomb.translate.write.WriteContext;
 import io.fd.honeycomb.translate.write.registry.WriterRegistry;
 import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 
 /**
  * Empty registry that does not perform any changes. Can be used in data layer, if we want to disable passing data to
@@ -28,11 +29,17 @@ import javax.annotation.Nonnull;
 public class NoopWriterRegistry implements WriterRegistry, AutoCloseable {
 
     @Override
-    public void update(@Nonnull final DataObjectUpdates updates,
-                       @Nonnull final WriteContext ctx) throws TranslationException {
+    public void processModifications(@Nonnull final DataObjectUpdates updates,
+                                     @Nonnull final WriteContext ctx) throws TranslationException {
         // NOOP
     }
 
+    @Override
+    public boolean writerSupportsUpdate(@Nonnull final InstanceIdentifier<?> type) {
+        // returns true to make higher level performance better(does not have to break updates to delete+create pairs)
+        return true;
+    }
+
     @Override
     public void close() throws Exception {
         // NOOP
index 14c9c60..9b95f76 100644 (file)
@@ -51,7 +51,7 @@ public class AbstractGenericWriterTest {
     @Test
     public void testDelete() throws Exception {
         before = mock(DataObject.class);
-        t.update(id, before, null, ctx);
+        t.processModification(id, before, null, ctx);
 
         verify(t).deleteCurrentAttributes(id, before, ctx);
     }
@@ -59,7 +59,7 @@ public class AbstractGenericWriterTest {
     @Test
     public void testUpdate() throws Exception {
         before = mock(DataObject.class);
-        t.update(id, before, after, ctx);
+        t.processModification(id, before, after, ctx);
 
         verify(t).updateCurrentAttributes(id, before, after, ctx);
     }
@@ -67,7 +67,7 @@ public class AbstractGenericWriterTest {
     @Test
     public void testNoUpdate() throws Exception {
         before = mock(DataObject.class);
-        t.update(id, before, before, ctx);
+        t.processModification(id, before, before, ctx);
 
         verify(t, times(0)).updateCurrentAttributes(id, before, after, ctx);
     }
@@ -75,7 +75,7 @@ public class AbstractGenericWriterTest {
     @Test
     public void testCreate() throws Exception {
         before = mock(DataObject.class);
-        t.update(id, null, after, ctx);
+        t.processModification(id, null, after, ctx);
 
         verify(t).writeCurrentAttributes(id, after, ctx);
     }
@@ -83,7 +83,7 @@ public class AbstractGenericWriterTest {
     private static class TestingWriter extends AbstractGenericWriter<DataObject> {
 
         TestingWriter() {
-            super(InstanceIdentifier.create(DataObject.class));
+            super(InstanceIdentifier.create(DataObject.class), false);
         }
 
         @Override
index 84dbb7b..9e09112 100644 (file)
@@ -58,7 +58,7 @@ public class BindingBrokerWriterTest {
     public void testWrite() throws Exception {
         assertEquals(id, bbWriter.getManagedDataObjectType());
 
-        bbWriter.update(id, data, data, ctx);
+        bbWriter.processModification(id, data, data, ctx);
         verify(broker).newWriteOnlyTransaction();
         verify(tx).put(LogicalDatastoreType.CONFIGURATION, id, data);
         verify(tx).submit();
@@ -67,6 +67,6 @@ public class BindingBrokerWriterTest {
     @Test(expected = io.fd.honeycomb.translate.write.WriteFailedException.class)
     public void testFailedWrite() throws Exception {
         when(tx.submit()).thenReturn(Futures.immediateFailedCheckedFuture(new TransactionCommitFailedException("failing")));
-        bbWriter.update(id, data, data, ctx);
+        bbWriter.processModification(id, data, data, ctx);
     }
 }
\ No newline at end of file
index 411f24b..642e9f8 100644 (file)
@@ -23,7 +23,7 @@ public class NoopWriterRegistryTest {
     @Test
     public void testNoop() throws Exception {
         final NoopWriterRegistry noopWriterRegistry = new NoopWriterRegistry();
-        noopWriterRegistry.update(null, null);
+        noopWriterRegistry.processModifications(null, null);
         noopWriterRegistry.close();
     }
 }
\ No newline at end of file
index af185d8..01d2900 100644 (file)
@@ -54,15 +54,15 @@ public class InterfaceWriterCustomizer implements ListWriterCustomizer<Interface
         }
     }
 
+    /*
+     * For nodes that does not have support for update operation directly, there's no need to override
+     * updateCurrentAttributes method. Updates will be handlers as combination of delete + create. If you want to
+     * implement update directly, override method updateCurrentAttributes
     @Override
     public void updateCurrentAttributes(@Nonnull final InstanceIdentifier<Interface> id,
                                         @Nonnull final Interface dataBefore, @Nonnull final Interface dataAfter,
                                         @Nonnull final WriteContext writeContext) throws WriteFailedException {
-        // There are cases when lower layer does not support all of the CRUD operations, in which case, the handler
-        // should look like this (This will reject configuration from upper layers, returning error/rpc-error):
-        throw new WriteFailedException.UpdateFailedException(id, dataBefore, dataAfter,
-                new UnsupportedOperationException("Unable to update interface data, unsupported at lower layer"));
-    }
+    }*/
 
     @Override
     public void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<Interface> id,