From: Oleksandr Panasiuk Date: Tue, 28 Mar 2023 09:36:14 +0000 (+0300) Subject: Add simple data listeners X-Git-Tag: v12.0.0~45 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=9caba1f025515a74e4f2120ad441619ddf0b4050;p=mdsal.git Add simple data listeners DataTreeChangeListener is a rather complex contract, where users sometimes want to receive only the latest state or the delta of the state. Add DataListener, which reports the current value and DataChangeListener, which reports changes. JIRA: MDSAL-813 Change-Id: I7db024f76709b9d4afcc0db5cbca3f1d35218e3f Signed-off-by: Oleksandr Panasiuk Signed-off-by: Robert Varga --- diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListener.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListener.java new file mode 100644 index 0000000000..1692f4eab5 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Interface implemented by classes interested in receiving data changes. + * It provides a comparison on before-value and after-value. + */ +@Beta +@FunctionalInterface +public interface DataChangeListener { + /** + * Invoked when there was data change for the supplied path, which was used + * to register the listener. + * + *

+ * Note: When invoking the method {@link DataTreeChangeListener#onInitialData} initial data == null, null. + * @param previousValue data before. + * @param currentValue data after. + */ + void dataChanged(@Nullable T previousValue, @Nullable T currentValue); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListenerAdapter.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListenerAdapter.java new file mode 100644 index 0000000000..6ed578d0bc --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataChangeListenerAdapter.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ForwardingObject; +import com.google.common.collect.Iterables; +import java.util.Collection; +import org.opendaylight.yangtools.yang.binding.DataObject; + +final class DataChangeListenerAdapter extends ForwardingObject + implements ClusteredDataTreeChangeListener { + private final DataChangeListener delegate; + + DataChangeListenerAdapter(final DataChangeListener delegate) { + this.delegate = requireNonNull(delegate); + } + + @Override + public void onDataTreeChanged(final Collection> changes) { + delegate.dataChanged(changes.iterator().next().getRootNode().getDataBefore(), + Iterables.getLast(changes).getRootNode().getDataAfter()); + } + + @Override + public void onInitialData() { + delegate.dataChanged(null, null); + } + + @Override + protected DataChangeListener delegate() { + return delegate; + } +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListener.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListener.java new file mode 100644 index 0000000000..3a72f64f64 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListener.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api; + +import com.google.common.annotations.Beta; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.yang.binding.DataObject; + +/** + * Interface implemented by classes interested in receiving the last data change. + */ +@Beta +@FunctionalInterface +public interface DataListener { + + /** + * Invoked when there was data change for the supplied path, which was used to register listener. + * @param data last state. + */ + void dataChangedTo(@Nullable T data); +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListenerAdapter.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListenerAdapter.java new file mode 100644 index 0000000000..1cae3184a5 --- /dev/null +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataListenerAdapter.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api; + +import static java.util.Objects.requireNonNull; + +import com.google.common.collect.ForwardingObject; +import com.google.common.collect.Iterables; +import java.util.Collection; +import org.opendaylight.yangtools.yang.binding.DataObject; + +final class DataListenerAdapter extends ForwardingObject + implements ClusteredDataTreeChangeListener { + private final DataListener delegate; + + DataListenerAdapter(final DataListener delegate) { + this.delegate = requireNonNull(delegate); + } + + @Override + public void onDataTreeChanged(final Collection> changes) { + delegate.dataChangedTo(Iterables.getLast(changes).getRootNode().getDataAfter()); + } + + @Override + public void onInitialData() { + delegate.dataChangedTo(null); + } + + @Override + protected DataListener delegate() { + return delegate; + } +} diff --git a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeChangeService.java b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeChangeService.java index 6558ec8b27..7803e87876 100644 --- a/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeChangeService.java +++ b/binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/DataTreeChangeService.java @@ -9,7 +9,9 @@ package org.opendaylight.mdsal.binding.api; import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; /** * A {@link BindingService} which allows users to register for changes to a subtree. @@ -55,4 +57,81 @@ public interface DataTreeChangeService extends BindingService { */ > @NonNull ListenerRegistration registerDataTreeChangeListener(@NonNull DataTreeIdentifier treeId, @NonNull L listener); + + /** + * Registers a {@link DataTreeChangeListener} to receive + * notifications when data changes under a given path in the conceptual data tree. + * + *

+ * You are able to register for notifications for any node or subtree + * which can be represented using {@link DataTreeIdentifier}. + * + *

+ * This method returns a {@link Registration} object. To + * "unregister" your listener for changes call the {@link Registration#close()} + * method on the returned object. + * + *

+ * You MUST explicitly unregister your listener when you no longer want to receive + * notifications. This is especially true in OSGi environments, where failure to + * do so during bundle shutdown can lead to stale listeners being still registered. + * @implSpec This method provides {@link DataListenerAdapter} as listener during + * the registration of {@link DataTreeChangeListener}. This would allow users + * to know the last state of data instead of getting details about what changed + * in the entire tree. + * @param treeId Data tree identifier of the subtree which should be watched for + * changes. + * @param listener Listener instance which is being registered + * @return Listener registration object, which may be used to unregister + * your listener using {@link Registration#close()} to stop + * delivery of change events. + */ + default @NonNull Registration registerDataListener( + final @NonNull DataTreeIdentifier treeId, final @NonNull DataListener listener) { + return registerDataTreeChangeListener(checkNotWildcard(treeId), new DataListenerAdapter<>(listener)); + } + + /** + * Registers a {@link DataTreeChangeListener} to receive + * notifications about the last data state when it changes under a given path in the conceptual data + * tree. + * + *

+ * You are able to register for notifications for any node or subtree + * which can be represented using {@link DataTreeIdentifier}. + * + *

+ * This method returns a {@link Registration} object. To + * "unregister" your listener for changes call the {@link Registration#close()} + * method on the returned object. + * + *

+ * You MUST explicitly unregister your listener when you no longer want to receive + * notifications. This is especially true in OSGi environments, where failure to + * do so during bundle shutdown can lead to stale listeners being still registered. + * + * @implSpec This method provides {@link DataChangeListenerAdapter} as listener during + * the registration of {@link DataTreeChangeListener}, which provides a comparison + * of before-value and after-value. + * + * @param treeId Data tree identifier of the subtree which should be watched for + * changes. + * @param listener Listener instance which is being registered + * @return Listener registration object, which may be used to unregister + * your listener using {@link Registration#close()} to stop + * delivery of change events. + */ + default @NonNull Registration registerDataChangeListener( + final @NonNull DataTreeIdentifier treeId, final @NonNull DataChangeListener listener) { + return registerDataTreeChangeListener(checkNotWildcard(treeId), new DataChangeListenerAdapter<>(listener)); + } + + private static @NonNull DataTreeIdentifier checkNotWildcard( + final DataTreeIdentifier treeId) { + final @NonNull InstanceIdentifier instanceIdentifier = treeId.getRootIdentifier(); + if (instanceIdentifier.isWildcarded()) { + throw new IllegalArgumentException("Cannot register listener for wildcard " + instanceIdentifier); + } + return treeId; + } } \ No newline at end of file diff --git a/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/DataTreeChangeServiceWildcardedTest.java b/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/DataTreeChangeServiceWildcardedTest.java new file mode 100644 index 0000000000..55ad0a4d39 --- /dev/null +++ b/binding/mdsal-binding-api/src/test/java/org/opendaylight/mdsal/binding/api/DataTreeChangeServiceWildcardedTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.api; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.mdsal813.norev.RegisterListenerTest; +import org.opendaylight.yang.gen.v1.mdsal813.norev.register.listener.test.Item; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; + +public class DataTreeChangeServiceWildcardedTest { + + DataBroker dataBroker; + + DataListener listener; + + DataChangeListener changeListener; + + @Before + public void setUp() { + dataBroker = mock(DataBroker.class); + listener = mock(DataListener.class); + changeListener = mock(DataChangeListener.class); + } + + @Test + public void testThrowExceptionOnRegister() { + final InstanceIdentifier instanceIdentifier = InstanceIdentifier.builder(RegisterListenerTest.class) + .child(Item.class).build(); + final DataTreeIdentifier itemsDataTreeIdentifier = DataTreeIdentifier.create( + LogicalDatastoreType.OPERATIONAL, instanceIdentifier); + + doCallRealMethod().when(dataBroker).registerDataListener(any(), any()); + final var dataListenerException = assertThrows(IllegalArgumentException.class, + () -> dataBroker.registerDataListener(itemsDataTreeIdentifier, listener)); + assertTrue(dataListenerException.getMessage().contains("Cannot register listener for wildcard")); + + doCallRealMethod().when(dataBroker).registerDataChangeListener(any(), any()); + final var dataListenerChangeException = assertThrows(IllegalArgumentException.class, + () -> dataBroker.registerDataChangeListener(itemsDataTreeIdentifier, changeListener)); + assertTrue(dataListenerChangeException.getMessage().contains("Cannot register listener for wildcard")); + } + +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataChangeListenerAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataChangeListenerAdapter.java new file mode 100644 index 0000000000..371a56a7a1 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataChangeListenerAdapter.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.dom.adapter; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.mdsal.binding.api.DataChangeListener; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingAugmentationCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.api.CommonDataObjectCodecTreeNode; +import org.opendaylight.mdsal.dom.api.ClusteredDOMDataTreeChangeListener; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate; + +final class BindingDOMDataChangeListenerAdapter implements ClusteredDOMDataTreeChangeListener { + private final AdapterContext adapterContext; + private final DataChangeListener listener; + + BindingDOMDataChangeListenerAdapter(final AdapterContext adapterContext, final DataChangeListener listener) { + this.adapterContext = requireNonNull(adapterContext); + this.listener = requireNonNull(listener); + } + + @Override + public void onDataTreeChanged(final List changes) { + final var first = changes.get(0); + final var serializer = adapterContext.currentSerializer(); + final var codec = serializer.getSubtreeCodec(serializer.coerceInstanceIdentifier(first.getRootPath())); + + listener.dataChanged(deserialize(codec, first.getRootNode().dataBefore()), + deserialize(codec, changes.get(changes.size() - 1).getRootNode().dataAfter())); + } + + @Override + public void onInitialData() { + listener.dataChanged(null, null); + } + + @SuppressWarnings("unchecked") + private @Nullable T deserialize(final CommonDataObjectCodecTreeNode codec, final NormalizedNode data) { + if (data == null) { + return null; + } else if (codec instanceof BindingDataObjectCodecTreeNode dataObject) { + return (T) dataObject.deserialize(data); + } else if (codec instanceof BindingAugmentationCodecTreeNode augmentation) { + return (T) augmentation.filterFrom(data); + } else { + throw new IllegalStateException("Unhandled codec " + codec); + } + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataListenerAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataListenerAdapter.java new file mode 100644 index 0000000000..03f716ce4a --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataListenerAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.dom.adapter; + +import static java.util.Objects.requireNonNull; + +import java.util.List; +import org.eclipse.jdt.annotation.NonNull; +import org.opendaylight.mdsal.binding.api.DataListener; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingAugmentationCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode; +import org.opendaylight.mdsal.dom.api.ClusteredDOMDataTreeChangeListener; +import org.opendaylight.yangtools.yang.binding.DataObject; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; +import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate; + +final class BindingDOMDataListenerAdapter implements ClusteredDOMDataTreeChangeListener { + private final AdapterContext adapterContext; + private final DataListener listener; + + BindingDOMDataListenerAdapter(final AdapterContext adapterContext, final DataListener listener) { + this.adapterContext = requireNonNull(adapterContext); + this.listener = requireNonNull(listener); + } + + @Override + public void onDataTreeChanged(final List changes) { + final var last = changes.get(changes.size() - 1); + final var after = last.getRootNode().dataAfter(); + listener.dataChangedTo(after == null ? null : deserialize(last.getRootPath(), after)); + } + + @Override + public void onInitialData() { + listener.dataChangedTo(null); + } + + @SuppressWarnings("unchecked") + private T deserialize(final YangInstanceIdentifier path, final @NonNull NormalizedNode data) { + final var serializer = adapterContext.currentSerializer(); + final var codec = serializer.getSubtreeCodec(serializer.coerceInstanceIdentifier(path)); + if (codec instanceof BindingDataObjectCodecTreeNode dataObject) { + return (T) dataObject.deserialize(data); + } else if (codec instanceof BindingAugmentationCodecTreeNode augmentation) { + return (T) augmentation.filterFrom(data); + } else { + throw new IllegalStateException("Unhandled codec " + codec); + } + } +} diff --git a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeChangeServiceAdapter.java b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeChangeServiceAdapter.java index ea84fdc514..7326a0501f 100644 --- a/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeChangeServiceAdapter.java +++ b/binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMDataTreeChangeServiceAdapter.java @@ -7,7 +7,10 @@ */ package org.opendaylight.mdsal.binding.dom.adapter; +import org.eclipse.jdt.annotation.NonNull; import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener; +import org.opendaylight.mdsal.binding.api.DataChangeListener; +import org.opendaylight.mdsal.binding.api.DataListener; import org.opendaylight.mdsal.binding.api.DataTreeChangeListener; import org.opendaylight.mdsal.binding.api.DataTreeChangeService; import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; @@ -15,6 +18,7 @@ import org.opendaylight.mdsal.common.api.LogicalDatastoreType; import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService; import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier; import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.concepts.Registration; import org.opendaylight.yangtools.yang.binding.Augmentation; import org.opendaylight.yangtools.yang.binding.DataObject; @@ -51,8 +55,30 @@ final class BindingDOMDataTreeChangeServiceAdapter extends AbstractBindingAdapte return new BindingDataTreeChangeListenerRegistration<>(listener, domReg); } - private DOMDataTreeIdentifier toDomTreeIdentifier(final DataTreeIdentifier treeId) { + @Override + public Registration registerDataListener(final DataTreeIdentifier treeId, + final DataListener listener) { + return getDelegate().registerDataTreeChangeListener(toDomTreeInstance(treeId), + new BindingDOMDataListenerAdapter<>(adapterContext(), listener)); + } + + @Override + public Registration registerDataChangeListener(final DataTreeIdentifier treeId, + final DataChangeListener listener) { + return getDelegate().registerDataTreeChangeListener(toDomTreeInstance(treeId), + new BindingDOMDataChangeListenerAdapter<>(adapterContext(), listener)); + } + + private @NonNull DOMDataTreeIdentifier toDomTreeIdentifier(final DataTreeIdentifier treeId) { return new DOMDataTreeIdentifier(treeId.getDatastoreType(), currentSerializer().toYangInstanceIdentifier(treeId.getRootIdentifier())); } + + private @NonNull DOMDataTreeIdentifier toDomTreeInstance(final DataTreeIdentifier treeId) { + final var instanceIdentifier = treeId.getRootIdentifier(); + if (instanceIdentifier.isWildcarded()) { + throw new IllegalArgumentException("Cannot register listener for wildcard " + instanceIdentifier); + } + return toDomTreeIdentifier(treeId); + } } diff --git a/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/DataListenerTest.java b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/DataListenerTest.java new file mode 100644 index 0000000000..a347b047c5 --- /dev/null +++ b/binding/mdsal-binding-dom-adapter/src/test/java/org/opendaylight/mdsal/binding/dom/adapter/DataListenerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2023 PANTHEON.tech, s.r.o. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.mdsal.binding.dom.adapter; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.mdsal.binding.api.DataBroker; +import org.opendaylight.mdsal.binding.api.DataChangeListener; +import org.opendaylight.mdsal.binding.api.DataListener; +import org.opendaylight.mdsal.binding.api.DataTreeIdentifier; +import org.opendaylight.mdsal.binding.api.WriteTransaction; +import org.opendaylight.mdsal.binding.dom.adapter.test.AbstractDataBrokerTest; +import org.opendaylight.mdsal.common.api.LogicalDatastoreType; +import org.opendaylight.yang.gen.v1.mdsal813.norev.RegisterListenerTest; +import org.opendaylight.yang.gen.v1.mdsal813.norev.RegisterListenerTestBuilder; +import org.opendaylight.yang.gen.v1.mdsal813.norev.register.listener.test.Item; +import org.opendaylight.yang.gen.v1.mdsal813.norev.register.listener.test.ItemBuilder; +import org.opendaylight.yang.gen.v1.mdsal813.norev.register.listener.test.ItemKey; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.util.BindingMap; +import org.opendaylight.yangtools.yang.common.Uint32; + +public class DataListenerTest extends AbstractDataBrokerTest { + DataBroker dataBroker; + + DataListener listener; + + DataChangeListener changeListener; + + @Before + public void setUp() { + dataBroker = getDataBroker(); + listener = mock(DataListener.class); + changeListener = mock(DataChangeListener.class); + } + + @Test + public void testThrowExceptionOnRegister() { + final InstanceIdentifier instanceIdentifier = InstanceIdentifier.builder(RegisterListenerTest.class) + .child(Item.class).build(); + final DataTreeIdentifier itemsDataTreeIdentifier = DataTreeIdentifier.create( + LogicalDatastoreType.OPERATIONAL, + instanceIdentifier); + + final Throwable dataListenerException = assertThrows(IllegalArgumentException.class, + () -> dataBroker.registerDataListener(itemsDataTreeIdentifier, listener)); + assertTrue(dataListenerException.getMessage().contains("Cannot register listener for wildcard")); + + final Throwable dataListenerChangeException = assertThrows(IllegalArgumentException.class, + () -> dataBroker.registerDataChangeListener(itemsDataTreeIdentifier, changeListener)); + assertTrue(dataListenerChangeException.getMessage().contains("Cannot register listener for wildcard")); + } + + @Test + public void testRegisterDataListener() { + final Item item = writeItems(); + final InstanceIdentifier instanceIdentifier = InstanceIdentifier.builder(RegisterListenerTest.class) + .child(Item.class, new ItemKey(item.key())).build(); + + dataBroker.registerDataListener( + DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, instanceIdentifier), listener); + + verify(listener, timeout(100)).dataChangedTo(item); + } + + @Test + public void testRegisterDataChangeListener() { + final Item item = writeItems(); + final InstanceIdentifier instanceIdentifier = InstanceIdentifier.builder(RegisterListenerTest.class) + .child(Item.class, new ItemKey(item.key())).build(); + + dataBroker.registerDataChangeListener( + DataTreeIdentifier.create(LogicalDatastoreType.CONFIGURATION, instanceIdentifier), changeListener); + + verify(changeListener, timeout(100)).dataChanged(null, item); + } + + private Item writeItems() { + final WriteTransaction writeTransaction = getDataBroker().newWriteOnlyTransaction(); + final Item wildcardItem = new ItemBuilder().setText("name").setNumber(Uint32.valueOf(43)).build(); + final RegisterListenerTestBuilder builder = new RegisterListenerTestBuilder().setItem( + BindingMap.of(wildcardItem)); + writeTransaction.put(LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.builder( + RegisterListenerTest.class).build(), builder.build()); + assertCommit(writeTransaction.commit()); + return wildcardItem; + } +} diff --git a/binding/mdsal-binding-test-model/src/main/yang/mdsal813.yang b/binding/mdsal-binding-test-model/src/main/yang/mdsal813.yang new file mode 100644 index 0000000000..618d92364b --- /dev/null +++ b/binding/mdsal-binding-test-model/src/main/yang/mdsal813.yang @@ -0,0 +1,17 @@ +module mdsal813 { + namespace mdsal813; + prefix mdsal813; + + container register-listener-test { + list item { + key "text number"; + leaf text { + + type string; + } + leaf number { + type uint32; + } + } + } +} \ No newline at end of file