summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/DataValidationFailedException.java74
-rw-r--r--infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Validator.java71
-rw-r--r--infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java14
-rw-r--r--infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java21
-rw-r--r--infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java9
-rw-r--r--infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java69
-rw-r--r--infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java8
-rw-r--r--infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java29
-rw-r--r--infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java21
-rw-r--r--infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java65
-rw-r--r--infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java1
-rw-r--r--infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java54
12 files changed, 404 insertions, 32 deletions
diff --git a/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/DataValidationFailedException.java b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/DataValidationFailedException.java
index 3c0392e..d728bee 100644
--- a/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/DataValidationFailedException.java
+++ b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/DataValidationFailedException.java
@@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
import io.fd.honeycomb.translate.ValidationFailedException;
import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
/**
@@ -76,4 +77,77 @@ public class DataValidationFailedException extends ValidationFailedException {
public InstanceIdentifier<?> getFailedId() {
return failedId;
}
+
+ /**
+ * Create specific validated failed exception.
+ */
+ public static class CreateValidationFailedException extends DataValidationFailedException {
+ private static final long serialVersionUID = 1;
+
+ private final transient DataObject data;
+
+ public CreateValidationFailedException(@Nonnull final InstanceIdentifier<?> failedId,
+ @Nonnull final DataObject data,
+ @Nonnull final Throwable cause) {
+ super(failedId, getMsg(failedId, data), cause);
+ this.data = checkNotNull(data, "data");
+ }
+
+ private static String getMsg(final @Nonnull InstanceIdentifier<?> failedId,
+ final @Nonnull DataObject data) {
+ return String.format("Failed to validate create request for: %s at: %s.", data, failedId);
+ }
+
+ public DataObject getData() {
+ return data;
+ }
+ }
+
+ /**
+ * Update specific validated failed exception.
+ */
+ public static class UpdateValidationFailedException extends DataValidationFailedException {
+ private static final long serialVersionUID = 1;
+
+ private final transient DataObject dataBefore;
+ private final transient DataObject dataAfter;
+
+ public UpdateValidationFailedException(@Nonnull final InstanceIdentifier<?> failedId,
+ @Nonnull final DataObject dataBefore,
+ @Nonnull final DataObject dataAfter,
+ @Nonnull final Throwable cause) {
+ super(failedId, getMsg(failedId, dataBefore, dataAfter), cause);
+ this.dataBefore = checkNotNull(dataBefore, "dataBefore");
+ this.dataAfter = checkNotNull(dataAfter, "dataAfter");
+ }
+
+ private static String getMsg(final @Nonnull InstanceIdentifier<?> failedId, final DataObject dataBefore,
+ final @Nonnull DataObject dataAfter) {
+ return String
+ .format("Failed to validate update request from: %s to: %s, at: %s.", dataBefore, dataAfter, failedId);
+ }
+
+ public DataObject getDataBefore() {
+ return dataBefore;
+ }
+
+ public DataObject getDataAfter() {
+ return dataAfter;
+ }
+ }
+
+ /**
+ * Delete specific validated failed exception.
+ */
+ public static class DeleteValidationFailedException extends DataValidationFailedException {
+ private static final long serialVersionUID = 1;
+
+ public DeleteValidationFailedException(@Nonnull final InstanceIdentifier<?> failedId, @Nonnull final Throwable cause) {
+ super(failedId, getMsg(failedId), cause);
+ }
+
+ private static String getMsg(@Nonnull final InstanceIdentifier<?> failedId) {
+ return String.format("Failed to validate delete request: %s.", failedId);
+ }
+ }
}
diff --git a/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Validator.java b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Validator.java
new file mode 100644
index 0000000..2044757
--- /dev/null
+++ b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Validator.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018 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.write;
+
+import com.google.common.annotations.Beta;
+import io.fd.honeycomb.translate.write.DataValidationFailedException.CreateValidationFailedException;
+import io.fd.honeycomb.translate.write.DataValidationFailedException.DeleteValidationFailedException;
+import io.fd.honeycomb.translate.write.DataValidationFailedException.UpdateValidationFailedException;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Responsible for validation of DataObjects.
+ * Handles all update operations (create, update, delete).
+ *
+ * @param <D> Specific DataObject derived type, that is handled by this writer
+ * @see Writer#validate(InstanceIdentifier, DataObject, DataObject, WriteContext)
+ */
+@Beta
+public interface Validator<D extends DataObject> {
+ /**
+ * Validates write operation.
+ *
+ * @param id Identifier(from root) of data being written
+ * @param dataAfter New data to be written
+ * @param writeContext Write context that provides information about current state of DataTree.
+ * @throws CreateValidationFailedException if write validation failed
+ */
+ void validateWrite(
+ @Nonnull final InstanceIdentifier<D> id,
+ @Nonnull final D dataAfter,
+ @Nonnull final WriteContext writeContext) throws CreateValidationFailedException;
+
+ /**
+ * Validates update operation.
+ *
+ * @param id Identifier(from root) of data being updated
+ * @param dataBefore Old data
+ * @param dataAfter New, updated data
+ * @param writeContext Write context that provides information about current state of DataTree.
+ * @throws UpdateValidationFailedException if update validation failed
+ */
+ void validateUpdate(InstanceIdentifier<D> id, D dataBefore, D dataAfter, WriteContext writeContext)
+ throws UpdateValidationFailedException;
+
+ /**
+ * Validates delete operation.
+ *
+ * @param id Identifier(from root) of data being written
+ * @param dataBefore Old data being deleted
+ * @param writeContext Write context that provides information about current state of DataTree.
+ * @throws DeleteValidationFailedException if delete validation failed
+ */
+ void validateDelete(InstanceIdentifier<D> id, D dataBefore, WriteContext writeContext)
+ throws DeleteValidationFailedException;
+}
diff --git a/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java
index 18573e5..f3265a3 100644
--- a/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java
+++ b/infra/translate-api/src/main/java/io/fd/honeycomb/translate/write/Writer.java
@@ -33,6 +33,20 @@ import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
public interface Writer<D extends DataObject> extends SubtreeManager<D> {
/**
+ * Validates data modification.
+ *
+ * @param id Identifier of data being validated
+ * @param dataBefore Old data
+ * @param dataAfter New, updated data
+ * @param ctx Write context enabling writer to get information about candidate data as well as current data
+ */
+ default void validate(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+ @Nullable final DataObject dataBefore,
+ @Nullable final DataObject dataAfter,
+ @Nonnull final WriteContext ctx) throws DataValidationFailedException {
+ }
+
+ /**
* Process modifications and translate them as create/update/delete operations to lower level
*
* @param id Identifier of data being written
diff --git a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java
index 92467a8..5121eb7 100644
--- a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java
+++ b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericListWriter.java
@@ -19,12 +19,13 @@ 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.spi.write.WriterCustomizer;
import io.fd.honeycomb.translate.util.RWUtils;
import io.fd.honeycomb.translate.util.write.AbstractGenericWriter;
import io.fd.honeycomb.translate.write.ListWriter;
+import io.fd.honeycomb.translate.write.Validator;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
-import io.fd.honeycomb.translate.spi.write.WriterCustomizer;
import javax.annotation.Nonnull;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.Identifiable;
@@ -45,6 +46,13 @@ public final class GenericListWriter<D extends DataObject & Identifiable<K>, K e
this.customizer = customizer;
}
+ public GenericListWriter(@Nonnull final InstanceIdentifier<D> type,
+ @Nonnull final ListWriterCustomizer<D, K> customizer,
+ @Nonnull final Validator<D> validator) {
+ super(type, isUpdateSupported(customizer), validator);
+ this.customizer = customizer;
+ }
+
@Override
protected void writeCurrentAttributes(@Nonnull final InstanceIdentifier<D> id, @Nonnull final D data,
@Nonnull final WriteContext ctx) throws WriteFailedException {
@@ -79,22 +87,25 @@ public final class GenericListWriter<D extends DataObject & Identifiable<K>, K e
@Override
protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
throws WriteFailedException {
- super.writeCurrent(getId(id, data), data, ctx);
+ super.writeCurrent(getManagedId(id, data), data, ctx);
}
@Override
protected void updateCurrent(final InstanceIdentifier<D> id, final D dataBefore, final D dataAfter,
final WriteContext ctx) throws WriteFailedException {
- super.updateCurrent(getId(id, dataBefore), dataBefore, dataAfter, ctx);
+ super.updateCurrent(getManagedId(id, dataBefore), dataBefore, dataAfter, ctx);
}
@Override
protected void deleteCurrent(final InstanceIdentifier<D> id, final D dataBefore, final WriteContext ctx)
throws WriteFailedException {
- super.deleteCurrent(getId(id, dataBefore), dataBefore, ctx);
+ super.deleteCurrent(getManagedId(id, dataBefore), dataBefore, ctx);
}
- private InstanceIdentifier<D> getId(final InstanceIdentifier<D> id, final D current) {
+ @Override
+ protected InstanceIdentifier<D> getManagedId(@Nonnull final InstanceIdentifier<? extends DataObject> currentId,
+ @Nonnull final D current) {
+ final InstanceIdentifier<D> id = (InstanceIdentifier<D>) currentId;
// Make sure the key is present
if (isWildcarded(id)) {
return getSpecificId(id, current);
diff --git a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java
index 086936e..bfdf072 100644
--- a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java
+++ b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/GenericWriter.java
@@ -16,9 +16,10 @@
package io.fd.honeycomb.translate.impl.write;
-import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.spi.write.WriterCustomizer;
import io.fd.honeycomb.translate.util.write.AbstractGenericWriter;
+import io.fd.honeycomb.translate.write.Validator;
+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;
@@ -39,7 +40,13 @@ public final class GenericWriter<D extends DataObject> extends AbstractGenericWr
@Nonnull final WriterCustomizer<D> customizer) {
super(type, isUpdateSupported(customizer));
this.customizer = customizer;
+ }
+ public GenericWriter(@Nonnull final InstanceIdentifier<D> type,
+ @Nonnull final WriterCustomizer<D> customizer,
+ @Nonnull final Validator<D> validator) {
+ super(type, isUpdateSupported(customizer), validator);
+ this.customizer = customizer;
}
static boolean isUpdateSupported(final @Nonnull WriterCustomizer<?> customizer) {
diff --git a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java
index 67a5da3..9e36e59 100644
--- a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java
+++ b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistry.java
@@ -28,12 +28,14 @@ import com.google.common.collect.Sets;
import io.fd.honeycomb.translate.TranslationException;
import io.fd.honeycomb.translate.util.RWUtils;
import io.fd.honeycomb.translate.write.DataObjectUpdate;
+import io.fd.honeycomb.translate.write.DataValidationFailedException;
import io.fd.honeycomb.translate.write.WriteContext;
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.LinkedList;
import java.util.List;
import java.util.Map;
@@ -76,6 +78,33 @@ final class FlatWriterRegistry implements WriterRegistry {
}
@Override
+ public void validateModifications(@Nonnull final DataObjectUpdates updates, @Nonnull final WriteContext ctx)
+ throws DataValidationFailedException {
+ // Optimization: validation order is not relevant, so do not merge deletes and updates.
+ validateModifications(updates.getDeletes(), ctx);
+ validateModifications(updates.getUpdates(), ctx);
+ }
+
+ private void validateModifications(
+ @Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates,
+ @Nonnull final WriteContext ctx) throws DataValidationFailedException {
+ if (updates.isEmpty()) {
+ return;
+ }
+ // Fail early if some handlers are missing.
+ checkAllTypesCanBeHandled(updates);
+
+ // Validators do not modify anything, so order of validators is not important.
+ // We iterate over writersOrder instead of modifications for consistent handling of subtree writers case.
+ for (InstanceIdentifier<?> writerType : writersOrder) {
+ final Writer<?> writer = getWriter(writerType);
+ for (DataObjectUpdate singleUpdate : getWritersData(updates, writer, writerType, ctx)) {
+ writer.validate(singleUpdate.getId(), singleUpdate.getDataBefore(), singleUpdate.getDataAfter(), ctx);
+ }
+ }
+ }
+
+ @Override
public void processModifications(@Nonnull final DataObjectUpdates updates,
@Nonnull final WriteContext ctx) throws TranslationException {
if (updates.isEmpty()) {
@@ -207,26 +236,11 @@ final class FlatWriterRegistry implements WriterRegistry {
LOG.debug("Performing bulk update for: {}", updates.keySet());
// Iterate over all writers and call update if there are any related updates
for (InstanceIdentifier<?> writerType : writersOrder) {
- Collection<? extends DataObjectUpdate> writersData = updates.get(writerType);
final Writer<?> writer = getWriter(writerType);
-
- if (writersData.isEmpty()) {
- // If there are no data for current writer, but it is a SubtreeWriter and there are updates to
- // its children, still invoke it with its root data
- if (writer instanceof SubtreeWriter<?> && isAffected((SubtreeWriter<?>) writer, updates)) {
- // Provide parent data for SubtreeWriter for further processing
- writersData = getParentDataObjectUpdate(ctx, updates, writer);
- } else {
- // Skipping unaffected writer
- // Alternative to this would be modification sort according to the order of writers
- continue;
- }
- }
-
LOG.debug("Performing update for: {}", writerType);
LOG.trace("Performing update with writer: {}", writer);
- for (DataObjectUpdate singleUpdate : writersData) {
+ for (DataObjectUpdate singleUpdate : getWritersData(updates, writer, writerType, ctx)) {
try {
writer.processModification(singleUpdate.getId(), singleUpdate.getDataBefore(),
singleUpdate.getDataAfter(), ctx);
@@ -240,6 +254,29 @@ final class FlatWriterRegistry implements WriterRegistry {
}
}
+ private Collection<? extends DataObjectUpdate> getWritersData(
+ final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates, final Writer<?> writer,
+ final InstanceIdentifier<?> writerType, final WriteContext ctx) {
+ Collection<? extends DataObjectUpdate> writersData = updates.get(writerType);
+
+ if (writersData.isEmpty()) {
+ // If there are no data for current writer, but it is a SubtreeWriter and there are updates to
+ // its children, still invoke it with its root data.
+ // Notice that child updates will be ignored if the set of all modifications
+ // contain both parent update and child updates. But this is ok, since all changes are already expressed in
+ // the parent update.
+ if (writer instanceof SubtreeWriter<?> && isAffected((SubtreeWriter<?>) writer, updates)) {
+ // Provide parent data for SubtreeWriter for further processing
+ writersData = getParentDataObjectUpdate(ctx, updates, writer);
+ } else {
+ // Skipping unaffected writer
+ // Alternative to this would be modification sort according to the order of writers
+ return Collections.emptyList();
+ }
+ }
+ return writersData;
+ }
+
private void checkAllTypesCanBeHandled(
@Nonnull final Multimap<InstanceIdentifier<?>, ? extends DataObjectUpdate> updates) {
diff --git a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java
index a1a5f3f..e510bc3 100644
--- a/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java
+++ b/infra/translate-impl/src/main/java/io/fd/honeycomb/translate/impl/write/registry/SubtreeWriter.java
@@ -19,6 +19,7 @@ package io.fd.honeycomb.translate.impl.write.registry;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.Iterables;
+import io.fd.honeycomb.translate.write.DataValidationFailedException;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
import io.fd.honeycomb.translate.write.Writer;
@@ -68,6 +69,13 @@ final class SubtreeWriter<D extends DataObject> implements Writer<D> {
}
@Override
+ public void validate(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+ @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
+ @Nonnull final WriteContext ctx) throws DataValidationFailedException {
+ delegate.validate(id, dataBefore, dataAfter, ctx);
+ }
+
+ @Override
public void processModification(
@Nonnull final InstanceIdentifier<? extends DataObject> id,
@Nullable final DataObject dataBefore,
diff --git a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java
index 91785b2..20cabc5 100644
--- a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java
+++ b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericListWriterTest.java
@@ -22,6 +22,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import io.fd.honeycomb.translate.spi.write.ListWriterCustomizer;
+import io.fd.honeycomb.translate.write.Validator;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
import java.util.Collections;
@@ -42,7 +43,6 @@ public class GenericListWriterTest {
private ListWriterCustomizer<IdentifiableDataObject, DataObjectIdentifier> customizer;
@Mock
private WriteContext ctx;
- private GenericListWriter<IdentifiableDataObject, DataObjectIdentifier> writer;
@Mock
private IdentifiableDataObject before;
@Mock
@@ -51,11 +51,15 @@ public class GenericListWriterTest {
private IdentifiableDataObject after;
@Mock
private DataObjectIdentifier keyAfter;
+ @Mock
+ private Validator<IdentifiableDataObject> validator;
+
+ private GenericListWriter<IdentifiableDataObject, DataObjectIdentifier> writer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- writer = new GenericListWriter<>(DATA_OBJECT_ID, customizer);
+ writer = new GenericListWriter<>(DATA_OBJECT_ID, customizer, validator);
when(before.getKey()).thenReturn(beforeKey);
when(after.getKey()).thenReturn(keyAfter);
}
@@ -106,4 +110,25 @@ public class GenericListWriterTest {
writer = new GenericListWriter<>(DATA_OBJECT_ID, customizer);
writer.deleteCurrentAttributes(DATA_OBJECT_ID, before, ctx);
}
+
+ @Test
+ public void testValidate() throws Exception {
+ assertEquals(DATA_OBJECT_ID, writer.getManagedDataObjectType());
+
+ final InstanceIdentifier<IdentifiableDataObject> keyedIdBefore =
+ (InstanceIdentifier<IdentifiableDataObject>) InstanceIdentifier.create(Collections
+ .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, beforeKey)));
+ final InstanceIdentifier<IdentifiableDataObject> keyedIdAfter =
+ (InstanceIdentifier<IdentifiableDataObject>) InstanceIdentifier.create(Collections
+ .singleton(new InstanceIdentifier.IdentifiableItem<>(IdentifiableDataObject.class, keyAfter)));
+
+ writer.validate(DATA_OBJECT_ID, before, after, ctx);
+ verify(validator).validateUpdate(keyedIdBefore, before, after, ctx);
+
+ writer.validate(DATA_OBJECT_ID, before, null, ctx);
+ verify(validator).validateDelete(keyedIdBefore, before, ctx);
+
+ writer.validate(DATA_OBJECT_ID, null, after, ctx);
+ verify(validator).validateWrite(keyedIdAfter, after, ctx);
+ }
} \ No newline at end of file
diff --git a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java
index c9d381a..1c0927b 100644
--- a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java
+++ b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/GenericWriterTest.java
@@ -23,6 +23,7 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.verify;
import io.fd.honeycomb.translate.spi.write.WriterCustomizer;
+import io.fd.honeycomb.translate.write.Validator;
import io.fd.honeycomb.translate.write.WriteContext;
import io.fd.honeycomb.translate.write.WriteFailedException;
import org.junit.Before;
@@ -40,16 +41,19 @@ public class GenericWriterTest {
private WriterCustomizer<DataObject> customizer;
@Mock
private WriteContext ctx;
- private GenericWriter<DataObject> writer;
@Mock
private DataObject before;
@Mock
private DataObject after;
+ @Mock
+ private Validator<DataObject> validator;
+
+ private GenericWriter<DataObject> writer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- writer = new GenericWriter<>(DATA_OBJECT_ID, customizer);
+ writer = new GenericWriter<>(DATA_OBJECT_ID, customizer, validator);
}
@Test
@@ -94,4 +98,17 @@ public class GenericWriterTest {
assertTrue(GenericWriter.isUpdateSupported(new NoopWriters.DirectUpdateWriterCustomizer()));
assertTrue(GenericWriter.isUpdateSupported(new NoopWriters.ParentImplDirectUpdateWriterCustomizer()));
}
+
+ @Test
+ public void testValidate() throws Exception {
+ assertEquals(DATA_OBJECT_ID, writer.getManagedDataObjectType());
+ writer.validate(DATA_OBJECT_ID, before, after, ctx);
+ verify(validator).validateUpdate(DATA_OBJECT_ID, before, after, ctx);
+
+ writer.validate(DATA_OBJECT_ID, before, null, ctx);
+ verify(validator).validateDelete(DATA_OBJECT_ID, before, ctx);
+
+ writer.validate(DATA_OBJECT_ID, null, after, ctx);
+ verify(validator).validateWrite(DATA_OBJECT_ID, after, ctx);
+ }
} \ No newline at end of file
diff --git a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java
index 7f4b93e..e06197f 100644
--- a/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java
+++ b/infra/translate-impl/src/test/java/io/fd/honeycomb/translate/impl/write/registry/FlatWriterRegistryTest.java
@@ -365,6 +365,71 @@ public class FlatWriterRegistryTest {
}
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testValidateMissingWriter() throws Exception {
+ final FlatWriterRegistry flatWriterRegistry =
+ new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1));
+
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+ addUpdate(updates, DataObject1.class);
+ addUpdate(updates, DataObject2.class);
+ flatWriterRegistry.validateModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+ }
+
+ @Test
+ public void testValidateSingleWriter() throws Exception {
+ final FlatWriterRegistry flatWriterRegistry =
+ new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+ final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+ final InstanceIdentifier<DataObject1> iid2 = InstanceIdentifier.create(DataObject1.class);
+ final DataObject1 dataObject = mock(DataObject1.class);
+ updates.put(DataObject1.IID, DataObjectUpdate.create(iid, dataObject, dataObject));
+ updates.put(DataObject1.IID, DataObjectUpdate.create(iid2, dataObject, dataObject));
+ flatWriterRegistry
+ .validateModifications(new WriterRegistry.DataObjectUpdates(updates, ImmutableMultimap.of()), ctx);
+
+ verify(writer1).validate(iid, dataObject, dataObject, ctx);
+ verify(writer1).validate(iid2, dataObject, dataObject, ctx);
+ // Invoked when registry is being created
+ verifyNoMoreInteractions(writer1);
+ verifyZeroInteractions(writer2);
+ }
+
+ @Test
+ public void testValidateMultipleWriters() throws Exception {
+ final FlatWriterRegistry flatWriterRegistry =
+ new FlatWriterRegistry(ImmutableMap.of(DataObject1.IID, writer1, DataObject2.IID, writer2));
+
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate.DataObjectDelete> deletes = HashMultimap.create();
+ final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates = HashMultimap.create();
+ final InstanceIdentifier<DataObject1> iid = InstanceIdentifier.create(DataObject1.class);
+ final DataObject1 dataObject = mock(DataObject1.class);
+ // Writer 1 delete
+ deletes.put(DataObject1.IID,
+ ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid, dataObject, null)));
+ // Writer 1 create
+ updates.put(DataObject1.IID, DataObjectUpdate.create(iid, null, dataObject));
+ final InstanceIdentifier<DataObject2> iid2 = InstanceIdentifier.create(DataObject2.class);
+ final DataObject2 dataObject2 = mock(DataObject2.class);
+ // Writer 2 delete
+ deletes.put(DataObject2.IID,
+ ((DataObjectUpdate.DataObjectDelete) DataObjectUpdate.create(iid2, dataObject2, null)));
+ // Writer 2 update
+ updates.put(DataObject2.IID, DataObjectUpdate.create(iid2, dataObject2, dataObject2));
+ flatWriterRegistry.validateModifications(new WriterRegistry.DataObjectUpdates(updates, deletes), ctx);
+
+ // Ignore order
+ verify(writer1).validate(iid, dataObject, null, ctx);
+ verify(writer1).validate(iid, null, dataObject, ctx);
+ verify(writer2).validate(iid2, dataObject2, null, ctx);
+ verify(writer2).validate(iid2, dataObject2, dataObject2, ctx);
+
+ verifyNoMoreInteractions(writer1);
+ verifyNoMoreInteractions(writer2);
+ }
+
private <D extends DataObject> void addKeyedUpdate(final Multimap<InstanceIdentifier<?>, DataObjectUpdate> updates,
final Class<D> type) throws Exception {
final InstanceIdentifier<D> iid = (InstanceIdentifier<D>) type.getDeclaredField("IID").get(null);
diff --git a/infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java b/infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java
index 721e2be..67bbc3d 100644
--- a/infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java
+++ b/infra/translate-spi/src/main/java/io/fd/honeycomb/translate/spi/write/WriterCustomizer.java
@@ -77,5 +77,4 @@ public interface WriterCustomizer<D extends DataObject> {
void deleteCurrentAttributes(@Nonnull final InstanceIdentifier<D> id,
@Nonnull final D dataBefore,
@Nonnull final WriteContext writeContext) throws WriteFailedException;
-
}
diff --git a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java
index 4f1b696..ba8f1d9 100644
--- a/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java
+++ b/infra/translate-utils/src/main/java/io/fd/honeycomb/translate/util/write/AbstractGenericWriter.java
@@ -16,32 +16,40 @@
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.DataValidationFailedException;
+import io.fd.honeycomb.translate.write.Validator;
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);
private final InstanceIdentifier<D> instanceIdentifier;
private final boolean supportsUpdate;
+ private Validator<D> validator;
protected AbstractGenericWriter(final InstanceIdentifier<D> type, final boolean supportsUpdate) {
this.instanceIdentifier = RWUtils.makeIidWildcarded(type);
this.supportsUpdate = supportsUpdate;
}
+ protected AbstractGenericWriter(final InstanceIdentifier<D> type, final boolean supportsUpdate,
+ final Validator<D> validator) {
+ this(type, supportsUpdate);
+ this.validator = validator;
+ }
+
protected void writeCurrent(final InstanceIdentifier<D> id, final D data, final WriteContext ctx)
throws WriteFailedException {
LOG.debug("{}: Writing current: {} data: {}", this, id, data);
@@ -90,6 +98,42 @@ public abstract class AbstractGenericWriter<D extends DataObject> implements Wri
}
}
+ @Override
+ public void validate(@Nonnull final InstanceIdentifier<? extends DataObject> id,
+ @Nullable final DataObject dataBefore, @Nullable final DataObject dataAfter,
+ @Nonnull final WriteContext ctx) throws DataValidationFailedException {
+ if (validator == null) {
+ LOG.trace("{}: validator is not defined. Skipping validation for {}", this, id);
+ return;
+ }
+ LOG.trace("{}: Validating : {}, before: {} after: {}", this, id, dataBefore, dataAfter);
+
+ checkArgument(idPointsToCurrent(id), "Cannot handle data: %s. Only: %s can be handled by writer: %s",
+ id, getManagedDataObjectType(), this);
+
+ if (isWrite(dataBefore, dataAfter)) {
+ final D after = castToManaged(dataAfter);
+ validator.validateWrite(getManagedId(id, after), after, ctx);
+ } else if (isDelete(dataBefore, dataAfter)) {
+ final D before = castToManaged(dataBefore);
+ validator.validateDelete(getManagedId(id, before), before, ctx);
+ } else {
+ checkArgument(dataBefore != null && dataAfter != null, "No data to process");
+ if (dataBefore.equals(dataAfter)) {
+ LOG.debug("{}: Skipping validation (no update) for: {}", this, id);
+ // No change, ignore
+ return;
+ }
+ final D before = castToManaged(dataBefore);
+ validator.validateUpdate(getManagedId(id, before), before, castToManaged(dataAfter), ctx);
+ }
+ }
+
+ protected InstanceIdentifier<D> getManagedId(@Nonnull final InstanceIdentifier<? extends DataObject> currentId,
+ @Nonnull final D current) {
+ return (InstanceIdentifier<D>) currentId;
+ }
+
private void checkDataType(@Nonnull final DataObject dataAfter) {
checkArgument(getManagedDataObjectType().getTargetType().isAssignableFrom(dataAfter.getClass()));
}