--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2018 Ericsson India Global Services Pvt Ltd. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>binding-parent</artifactId>
+ <version>0.5.0-SNAPSHOT</version>
+ <relativePath>../../commons/binding-parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>tools-api</artifactId>
+ <version>0.5.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+ <!-- <name> formatting is used by autorelease to parse and notify projects on
+ build failure. Please do not modify this unless you have a good reason. -->
+ <name>ODL :: genius :: ${project.artifactId}</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>util</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>inject</artifactId>
+ <version>${genius.infrautils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>metrics-api</artifactId>
+ <version>${genius.infrautils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>infrautils-util</artifactId>
+ <version>${genius.infrautils.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.immutables</groupId>
+ <artifactId>value</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.cdi</groupId>
+ <artifactId>pax-cdi-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <!-- Dependencies with <scope>test -->
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>testutils</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.mdsal</groupId>
+ <artifactId>mdsal-binding-test-utils</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>infrautils-testutils</artifactId>
+ <version>${genius.infrautils.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava-testlib</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <scope>test</scope>
+ <type>test-jar</type>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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.genius.tools.mdsal.listener;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+import org.opendaylight.yangtools.util.concurrent.SpecialExecutors;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Abstract class providing some common functionality to specific listeners. This listener launches the received
+ * notifications by using an {@link ExecutorService}.
+ *
+ * <p>The {@link ExecutorService} passed to the constructor will depend on the use case. Here we have some examples:
+ *
+ * <p>- If the listener is fast enough and non-blocking: MoreExecutors.directExecutor might be used, or even better,
+ * just use the {@link AbstractSyncDataTreeChangeListener}.
+ *
+ * <p>- If the listener is heavy or could be blocked: use a multi-threaded executor.
+ * We recommend using one of the factory methods in {@link SpecialExecutors}.
+ * You could and probably should share such an Executor among several listeners in your project.
+ *
+ * <p>- If the listener needs to preserve the order of notifications, then (only) use a single thread executor typically
+ * an {@link org.opendaylight.infrautils.utils.concurrent.Executors#newSingleThreadExecutor(String, org.slf4j.Logger)}.
+ *
+ * <p>- If there are multiple listeners: they could even share an Executor as the ones in {@link SpecialExecutors},
+ *
+ * <p>Subclasses are also encouraged to, in addition to passing the ExecutorService for use in
+ * production (by Blueprint wiring) based on above via super(), expose a public constructor letting tests specify
+ * an alternative ExecutorService; this is useful e.g. to inject infrautils' AwaitableExecutorService for testing.
+ *
+ * @param <T> type of the data object the listener is registered to.
+ *
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+public abstract class AbstractAsyncDataTreeChangeListener<T extends DataObject> extends
+ AbstractDataTreeChangeListener<T> {
+
+ private final ExecutorService executorService;
+
+ @Inject
+ public AbstractAsyncDataTreeChangeListener(DataBroker dataBroker, DataTreeIdentifier<T> dataTreeIdentifier,
+ ExecutorService executorService) {
+ super(dataBroker, dataTreeIdentifier);
+ this.executorService = executorService;
+ }
+
+ @Inject
+ public AbstractAsyncDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ ExecutorService executorService) {
+ super(dataBroker, datastoreType, instanceIdentifier);
+ this.executorService = executorService;
+ }
+
+ @Inject
+ public AbstractAsyncDataTreeChangeListener(DataBroker dataBroker,
+ LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ ExecutorService executorService,
+ MetricProvider metricProvider) {
+ super(dataBroker, datastoreType, instanceIdentifier, metricProvider);
+ this.executorService = executorService;
+ }
+
+ @Override
+ public final void onDataTreeChanged(@Nonnull Collection<DataTreeModification<T>> collection) {
+ executorService.execute(() -> super.onDataTreeChanged(collection,
+ getDataStoreMetrics()));
+ }
+
+ /**
+ * Returns the ExecutorService provided when constructing this instance. If the subclass owns the
+ * ExecutorService, it should be shut down when closing the listener using this getter.
+ *
+ * @return executor service
+ */
+ protected ExecutorService getExecutorService() {
+ return executorService;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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.genius.tools.mdsal.listener;
+
+import java.util.Collection;
+import java.util.concurrent.ExecutorService;
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Abstract class providing some common functionality to specific listeners. This is the clustered version of the
+ * {@link AbstractAsyncDataTreeChangeListener}. Please carefully consider whether you really need an async listener,
+ * or if {@link AbstractClusteredSyncDataTreeChangeListener} wouldn't work just fine for your use case.
+ *
+ * @param <T> type of the data object the listener is registered to.
+ *
+ * @see AbstractAsyncDataTreeChangeListener
+ *
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+public abstract class AbstractClusteredAsyncDataTreeChangeListener<T extends DataObject> extends
+ AbstractDataTreeChangeListener<T> implements ClusteredDataTreeChangeListener<T> {
+
+ private final ExecutorService executorService;
+
+ @Inject
+ public AbstractClusteredAsyncDataTreeChangeListener(DataBroker dataBroker, DataTreeIdentifier<T> dataTreeIdentifier,
+ ExecutorService executorService) {
+ super(dataBroker, dataTreeIdentifier);
+ this.executorService = executorService;
+ }
+
+ @Inject
+ public AbstractClusteredAsyncDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ ExecutorService executorService) {
+ super(dataBroker, datastoreType, instanceIdentifier);
+ this.executorService = executorService;
+ }
+
+ @Inject
+ public AbstractClusteredAsyncDataTreeChangeListener(DataBroker dataBroker,
+ LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ ExecutorService executorService,
+ MetricProvider metricProvider) {
+ super(dataBroker, datastoreType, instanceIdentifier, metricProvider);
+ this.executorService = executorService;
+ }
+
+ @Override
+ public final void onDataTreeChanged(@Nonnull Collection<DataTreeModification<T>> collection) {
+ executorService.execute(() -> super.onDataTreeChanged(collection,
+ getDataStoreMetrics()));
+ }
+
+ /**
+ * Returns the ExecutorService provided when constructing this instance. If the subclass owns the
+ * ExecutorService, it should be shut down when closing the listener using this getter.
+ *
+ * @return executor service
+ */
+ protected ExecutorService getExecutorService() {
+ return executorService;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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.genius.tools.mdsal.listener;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+
+import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Abstract class providing some common functionality to specific listeners. This is the clustered version of the
+ * {@link AbstractSyncDataTreeChangeListener}.
+ *
+ * @param <T> type of the data object the listener is registered to.
+ *
+ * @see AbstractSyncDataTreeChangeListener
+ *
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+public abstract class AbstractClusteredSyncDataTreeChangeListener<T extends DataObject> extends
+ AbstractDataTreeChangeListener<T> implements ClusteredDataTreeChangeListener<T> {
+
+ @Inject
+ public AbstractClusteredSyncDataTreeChangeListener(DataBroker dataBroker,
+ DataTreeIdentifier<T> dataTreeIdentifier) {
+ super(dataBroker, dataTreeIdentifier);
+ }
+
+ @Inject
+ public AbstractClusteredSyncDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier) {
+ super(dataBroker, datastoreType, instanceIdentifier);
+ }
+
+ @Inject
+ public AbstractClusteredSyncDataTreeChangeListener(DataBroker dataBroker,
+ LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ MetricProvider metricProvider) {
+ super(dataBroker, datastoreType, instanceIdentifier, metricProvider);
+ }
+
+ @Override
+ public final void onDataTreeChanged(@Nonnull Collection<DataTreeModification<T>> collection) {
+ super.onDataTreeChanged(collection, getDataStoreMetrics());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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.genius.tools.mdsal.listener;
+
+import javax.annotation.Nonnull;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Inject;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.genius.tools.mdsal.metrics.DataStoreMetrics;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Abstract class providing some common functionality to abstract listeners. This class is not designed to be
+ * extended by the specific listeners, that's why it is package-private. It provides subclasses with access to the
+ * {@link DataBroker} passed as constructor argument, listener registration/de-registration and a shutdown method to
+ * be implemented if needed by the subclasses (e.g. shutting down services, closing resources, etc.)
+ *
+ * @param <T> type of the data object the listener is registered to.
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+abstract class AbstractDataTreeChangeListener<T extends DataObject> implements DataTreeChangeListener<T>,
+ DataTreeChangeListenerActions<T>, AutoCloseable {
+
+ private final DataBroker dataBroker;
+ private final DataTreeIdentifier<T> dataTreeIdentifier;
+ private ListenerRegistration<AbstractDataTreeChangeListener<T>> dataChangeListenerRegistration;
+ private DataStoreMetrics dataStoreMetrics;
+
+ @Inject
+ AbstractDataTreeChangeListener(DataBroker dataBroker, DataTreeIdentifier<T> dataTreeIdentifier) {
+ this.dataBroker = dataBroker;
+ this.dataTreeIdentifier = dataTreeIdentifier;
+ }
+
+ @Inject
+ AbstractDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier) {
+ this(dataBroker, new DataTreeIdentifier<>(datastoreType, instanceIdentifier));
+ }
+
+ @Inject
+ AbstractDataTreeChangeListener(DataBroker dataBroker,
+ LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ MetricProvider metricProvider) {
+ this(dataBroker, new DataTreeIdentifier<>(datastoreType, instanceIdentifier));
+ this.dataStoreMetrics = new DataStoreMetrics(metricProvider, getClass());
+ }
+
+ @PostConstruct
+ public void register() {
+ this.dataChangeListenerRegistration = dataBroker.registerDataTreeChangeListener(dataTreeIdentifier, this);
+ }
+
+ protected DataBroker getDataBroker() {
+ return dataBroker;
+ }
+
+ protected DataStoreMetrics getDataStoreMetrics() {
+ return dataStoreMetrics;
+ }
+
+ @Override
+ @PreDestroy
+ public void close() {
+ if (dataChangeListenerRegistration != null) {
+ dataChangeListenerRegistration.close();
+ }
+ }
+
+ @Override
+ @Deprecated
+ public void add(@Nonnull T newDataObject) {
+ // TODO: to be removed after all listeners migrated to use the new methods
+ }
+
+ @Override
+ @Deprecated
+ public void remove(@Nonnull T removedDataObject) {
+ // TODO: to be removed after all listeners migrated to use the new methods
+ }
+
+ @Override
+ @Deprecated
+ public void update(@Nonnull T originalDataObject, @Nonnull T updatedDataObject) {
+ // TODO: to be removed after all listeners migrated to use the new methods
+ }
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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.genius.tools.mdsal.listener;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import javax.inject.Inject;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Abstract class providing some common functionality to specific listeners. This should be the most common parent
+ * class for any listener that does not need to be either cluster-aware (see
+ * {@link AbstractClusteredSyncDataTreeChangeListener}) or to process notifications asynchronously
+ * (see {@link AbstractAsyncDataTreeChangeListener}) or even both characteristics (see
+ * {@link AbstractClusteredAsyncDataTreeChangeListener}).
+ *
+ * @param <T> type of the data object the listener is registered to.
+ *
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+public abstract class AbstractSyncDataTreeChangeListener<T extends DataObject> extends
+ AbstractDataTreeChangeListener<T> {
+
+ @Inject
+ public AbstractSyncDataTreeChangeListener(DataBroker dataBroker, DataTreeIdentifier<T> dataTreeIdentifier) {
+ super(dataBroker, dataTreeIdentifier);
+ }
+
+ @Inject
+ public AbstractSyncDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier) {
+ super(dataBroker, datastoreType, instanceIdentifier);
+ }
+
+ @Inject
+ public AbstractSyncDataTreeChangeListener(DataBroker dataBroker, LogicalDatastoreType datastoreType,
+ InstanceIdentifier<T> instanceIdentifier,
+ MetricProvider metricProvider) {
+ super(dataBroker, datastoreType, instanceIdentifier, metricProvider);
+ }
+
+ @Override
+ public final void onDataTreeChanged(@Nonnull Collection<DataTreeModification<T>> collection) {
+ super.onDataTreeChanged(collection, getDataStoreMetrics());
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson, S.A. 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.genius.tools.mdsal.listener;
+
+import java.util.Collection;
+import javax.annotation.Nonnull;
+import javax.inject.Singleton;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.genius.tools.mdsal.metrics.DataStoreMetrics;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+/**
+ * Interface to be implemented by classes interested in receiving notifications
+ * about data tree changes. It implements a default method to handle the data
+ * tree modifications. Those notifications will be forwarded to the appropriate
+ * methods (add, update, remove) depending on their action type. The listeners
+ * implementing this interface will need to be annotated as {@link Singleton}.
+ *
+ * @param <T> type of the data object the listener is registered to.
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+interface DataTreeChangeListenerActions<T extends DataObject> {
+
+ /**
+ * Default method invoked upon data tree change, in turn it calls the
+ * appropriate method (add, update, remove) depending on the type of change.
+ *
+ * @param changes collection of changes
+ * @param dataStoreMetrics data store metrics
+ */
+ default void onDataTreeChanged(@Nonnull Collection<DataTreeModification<T>> changes,
+ DataStoreMetrics dataStoreMetrics) {
+ // This code is also in DataTreeEventCallbackRegistrarImpl and any changes should be applied there as well
+ for (final DataTreeModification<T> dataTreeModification : changes) {
+ final InstanceIdentifier<T> instanceIdentifier = dataTreeModification.getRootPath().getRootIdentifier();
+ final DataObjectModification<T> dataObjectModification = dataTreeModification.getRootNode();
+ final T dataBefore = dataObjectModification.getDataBefore();
+ final T dataAfter = dataObjectModification.getDataAfter();
+
+ switch (dataObjectModification.getModificationType()) {
+ case SUBTREE_MODIFIED:
+ if (dataStoreMetrics != null) {
+ dataStoreMetrics.incrementUpdated();
+ }
+ update(instanceIdentifier, dataBefore, dataAfter);
+ break;
+ case DELETE:
+ if (dataStoreMetrics != null) {
+ dataStoreMetrics.incrementDeleted();
+ }
+ remove(instanceIdentifier, dataBefore);
+ break;
+ case WRITE:
+ if (dataBefore == null) {
+ if (dataStoreMetrics != null) {
+ dataStoreMetrics.incrementAdded();
+ }
+ add(instanceIdentifier, dataAfter);
+ } else {
+ if (dataStoreMetrics != null) {
+ dataStoreMetrics.incrementUpdated();
+ }
+ update(instanceIdentifier, dataBefore, dataAfter);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /**
+ * Invoked when a new data object is added.
+ *
+ * @param instanceIdentifier instance id for this data object
+ * @param newDataObject newly added object
+ */
+ default void add(@Nonnull InstanceIdentifier<T> instanceIdentifier, @Nonnull T newDataObject) {
+ add(newDataObject);
+ }
+
+ /**
+ * Invoked when a new data object added.
+ *
+ * @param newDataObject newly added object
+ */
+ @Deprecated
+ void add(@Nonnull T newDataObject);
+
+ /**
+ * Invoked when the data object has been removed.
+ *
+ * @param instanceIdentifier instance id for this data object
+ * @param removedDataObject existing object being removed
+ */
+ default void remove(@Nonnull InstanceIdentifier<T> instanceIdentifier, @Nonnull T removedDataObject) {
+ remove(removedDataObject);
+ }
+
+ /**
+ * Invoked when the data object has been removed.
+ *
+ * @param removedDataObject existing object being removed
+ */
+ @Deprecated
+ void remove(@Nonnull T removedDataObject);
+
+ /**
+ * Invoked when there is a change in the data object.
+ *
+ * @param instanceIdentifier instance id for this data object
+ * @param originalDataObject existing object being modified
+ * @param updatedDataObject modified data object
+ */
+ default void update(@Nonnull InstanceIdentifier<T> instanceIdentifier, @Nonnull T originalDataObject,
+ @Nonnull T updatedDataObject) {
+ update(originalDataObject, updatedDataObject);
+ }
+
+ /**
+ * Invoked when there is a change in the data object.
+ *
+ * @param originalDataObject existing object being modified
+ * @param updatedDataObject modified data object
+ */
+ @Deprecated
+ void update(@Nonnull T originalDataObject, @Nonnull T updatedDataObject);
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Ericsson S.A. 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
+ */
+/**
+ * Incubator package for a listeners framework. This code might be proposed into
+ * upstream projects once merged and proven to be useful here for a while.
+ *
+ * @author David Suárez (david.suarez.fuentes@gmail.com)
+ */
+package org.opendaylight.genius.tools.mdsal.listener;
--- /dev/null
+/*
+ * Copyright © 2018 Ericsson India Global Services Pvt Ltd. 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.genius.tools.mdsal.metrics;
+
+import org.opendaylight.infrautils.metrics.Meter;
+import org.opendaylight.infrautils.metrics.MetricDescriptor;
+import org.opendaylight.infrautils.metrics.MetricProvider;
+
+public class DataStoreMetrics {
+
+ private final MetricProvider metricProvider;
+ private final Class<?> clazz;
+ private final Meter added;
+ private final Meter updated;
+ private final Meter deleted;
+
+ public DataStoreMetrics(MetricProvider metricProvider, Class<?> clazz) {
+ this.metricProvider = metricProvider;
+ this.clazz = clazz;
+ this.added = initCounter("_added");
+ this.updated = initCounter("_updated");
+ this.deleted = initCounter("_deleted");
+ }
+
+ public void incrementAdded() {
+ added.mark();
+ }
+
+ public void incrementUpdated() {
+ updated.mark();
+ }
+
+ public void incrementDeleted() {
+ deleted.mark();
+ }
+
+ private Meter initCounter(String type) {
+ String className = clazz.getSimpleName();
+ // expects the form org.opendaylight.project.module
+ String project = clazz.getName().split("\\.")[2];
+ String module = clazz.getName().split("\\.")[3];
+ return metricProvider.newMeter(new MetricDescriptor() {
+ @Override
+ public Object anchor() {
+ return this;
+ }
+
+ @Override
+ public String project() {
+ return project;
+ }
+
+ @Override
+ public String module() {
+ return module;
+ }
+
+ @Override
+ public String id() {
+ return className + type;
+ }
+ });
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Red Hat, Inc. 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.genius.tools.mdsal.rpc;
+
+import static org.opendaylight.yangtools.yang.common.RpcError.ErrorType.APPLICATION;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import edu.umd.cs.findbugs.annotations.SuppressWarnings;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.opendaylight.infrautils.utils.StackTraces;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.yang.common.OperationFailedException;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.slf4j.Logger;
+
+/**
+ * Utility to simplify correctly handling transformation of Future of RpcResult to return.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Beta
+public final class FutureRpcResults {
+
+ // NB: The FutureRpcResultsTest unit test for this util is in mdsalutil-testutils's src/test, not this project's
+
+ // TODO Once matured in genius, this class could be proposed to org.opendaylight.yangtools.yang.common
+ // (This was proposed in Oct on yangtools-dev list, but there little interest due to plans to change RpcResult.)
+
+ private FutureRpcResults() {}
+
+ /**
+ * Create a Builder for a ListenableFuture to Future<RpcResult<O>> transformer. By default, the future
+ * will log success or failure, with configurable log levels; the caller can also add handlers for success and/or
+ * failure.
+ *
+ * <p>The RPC's method name is automatically obtained using {@link StackTraces}. This has some cost, which in
+ * the overall scheme of a typical RPC is typically negligible, but on a highly optimized fast path could
+ * theoretically be an issue; if you see this method as a hot spot in a profiler, then (only) use the
+ * alternative signature where you manually pass the String rpcMethodName.
+ *
+ * @param logger the slf4j Logger of the caller
+ * @param input the RPC input DataObject of the caller (may be null)
+ * @param callable the Callable (typically lambda) creating a ListenableFuture. Note that the
+ * functional interface Callable's call() method declares throws Exception, so your lambda
+ * does not have to do any exception handling (specifically it does NOT have to catch and
+ * wrap any exception into a failed Future); this utility does that for you.
+ *
+ * @return a new Builder
+ */
+ @CheckReturnValue
+ public static <I, O> FutureRpcResultBuilder<I, O> fromListenableFuture(Logger logger,
+ @Nullable I input, Callable<ListenableFuture<O>> callable) {
+ return new FutureRpcResultBuilder<>(logger, StackTraces.getCallersCallerMethodName(), input, callable);
+ }
+
+ /**
+ * Create a Builder for a ListenableFuture to Future<RpcResult<O>> transformer. By default, the future
+ * will log success or failure, with configurable log levels; the caller can also add handlers for success and/or
+ * failure.
+ *
+ * @param logger the slf4j Logger of the caller
+ * @param rpcMethodName Java method name (without "()") of the RPC operation, used for logging
+ * @param input the RPC input DataObject of the caller (may be null)
+ * @param callable the Callable (typically lambda) creating a ListenableFuture. Note that the
+ * functional interface Callable's call() method declares throws Exception, so your lambda
+ * does not have to do any exception handling (specifically it does NOT have to catch and
+ * wrap any exception into a failed Future); this utility does that for you.
+ *
+ * @return a new FutureRpcResultBuilder
+ */
+ @CheckReturnValue
+ public static <I, O> FutureRpcResultBuilder<I, O> fromListenableFuture(Logger logger, String rpcMethodName,
+ @Nullable I input, Callable<ListenableFuture<O>> callable) {
+ return new FutureRpcResultBuilder<>(logger, rpcMethodName, input, callable);
+ }
+
+ public enum LogLevel {
+ ERROR, WARN, INFO, DEBUG, TRACE,
+ /**
+ * Note that when using LogLevel NONE for failures, then you should set a
+ * {@link FutureRpcResultBuilder#onFailure(Consumer)} which does better logging,
+ * or be 100% sure that all callers of the RPC check the returned Future RpcResult appropriately;
+ * otherwise you will lose error messages.
+ */
+ NONE;
+ @SuppressWarnings({"SLF4J_UNKNOWN_ARRAY","SLF4J_FORMAT_SHOULD_BE_CONST"})
+ public void log(Logger logger, String format, Object... arguments) {
+ switch (this) {
+ case NONE:
+ break;
+ case TRACE:
+ logger.trace(format, arguments);
+ break;
+ case DEBUG:
+ logger.debug(format, arguments);
+ break;
+ case INFO:
+ logger.info(format, arguments);
+ break;
+ case WARN:
+ logger.warn(format, arguments);
+ break;
+ default: // including ERROR
+ logger.error(format, arguments);
+ break;
+ }
+ }
+ }
+
+ @CheckReturnValue
+ public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, String rpcMethodName,
+ @Nullable I input, Callable<Builder<O>> builder) {
+ Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
+ return fromListenableFuture(logger, rpcMethodName, input, callable);
+ }
+
+ @CheckReturnValue
+ public static <I, O> FutureRpcResultBuilder<I, O> fromBuilder(Logger logger, @Nullable I input,
+ Callable<Builder<O>> builder) {
+ Callable<ListenableFuture<O>> callable = () -> Futures.immediateFuture(builder.call().build());
+ return fromListenableFuture(logger, StackTraces.getCallersCallerMethodName(), input, callable);
+ }
+
+ @NotThreadSafe
+ public static final class FutureRpcResultBuilder<I, O> implements Builder<Future<RpcResult<O>>> {
+
+ private static final Function<Throwable, String> DEFAULT_ERROR_MESSAGE_FUNCTION = Throwable::getMessage;
+ private static final Consumer<Throwable> DEFAULT_ON_FAILURE = throwable -> { };
+ private final Consumer<O> defaultOnSuccess = result -> { };
+
+ // fixed (final) builder values
+ private final Logger logger;
+ private final String rpcMethodName;
+ @Nullable private final I input;
+ private final Callable<ListenableFuture<O>> callable;
+
+ // optional builder values, which can be overridden by users
+ private Function<Throwable, String> rpcErrorMessageFunction = DEFAULT_ERROR_MESSAGE_FUNCTION;
+ private Consumer<O> onSuccessConsumer = defaultOnSuccess;
+ private Consumer<Throwable> onFailureConsumer = DEFAULT_ON_FAILURE;
+
+ // defaulted builder values, which can be overridden by users
+ private LogLevel onEnterLogLevel = LogLevel.TRACE;
+ private LogLevel onSuccessLogLevel = LogLevel.DEBUG;
+ private LogLevel onFailureLogLevel = LogLevel.ERROR;
+
+ private FutureRpcResultBuilder(Logger logger, String rpcMethodName, @Nullable I input,
+ Callable<ListenableFuture<O>> callable) {
+ this.logger = logger;
+ this.rpcMethodName = rpcMethodName;
+ this.input = input;
+ this.callable = callable;
+ }
+
+ /**
+ * Builds the Future RpcResult.
+ *
+ * @return Future RpcResult. Note that this will NEVER be a failed Future; any
+ * errors are reported as !{@link RpcResult#isSuccessful()}, with
+ * details in {@link RpcResult#getErrors()}, and not the Future itself.
+ */
+ @Override
+ @CheckReturnValue
+ @SuppressWarnings("checkstyle:IllegalCatch")
+ public Future<RpcResult<O>> build() {
+ SettableFuture<RpcResult<O>> futureRpcResult = SettableFuture.create();
+ FutureCallback<O> callback = new FutureCallback<O>() {
+ @Override
+ public void onSuccess(O result) {
+ onSuccessLogLevel.log(logger, "RPC {}() successful; input = {}, output = {}", rpcMethodName,
+ input, result);
+ onSuccessConsumer.accept(result);
+ futureRpcResult.set(RpcResultBuilder.success(result).build());
+ }
+
+ @Override
+ public void onFailure(Throwable cause) {
+ onFailureLogLevel.log(logger, "RPC {}() failed; input = {}", rpcMethodName, input, cause);
+ onFailureConsumer.accept(cause);
+ RpcResultBuilder<O> rpcResultBuilder = RpcResultBuilder.failed();
+ if (cause instanceof OperationFailedException) {
+ // NB: This looses (not not propagate) the cause, and only preserves the error list
+ // But we did log the cause above, so it can still be found.
+ rpcResultBuilder.withRpcErrors(((OperationFailedException) cause).getErrorList());
+ } else {
+ rpcResultBuilder.withError(APPLICATION, rpcErrorMessageFunction.apply(cause), cause);
+ }
+ futureRpcResult.set(rpcResultBuilder.build());
+ }
+ };
+ try {
+ onEnterLogLevel.log(logger, "RPC {}() entered; input = {}", rpcMethodName, input);
+ Futures.addCallback(callable.call(), callback, MoreExecutors.directExecutor());
+ } catch (Exception cause) {
+ callback.onFailure(cause);
+ }
+ return futureRpcResult;
+ }
+
+ /**
+ * Sets a custom on-failure action, for a given exception.
+ */
+ public FutureRpcResultBuilder<I,O> onFailure(Consumer<Throwable> newOnFailureConsumer) {
+ if (onFailureConsumer != DEFAULT_ON_FAILURE) {
+ throw new IllegalStateException("onFailure can only be set once");
+ }
+ this.onFailureConsumer = newOnFailureConsumer;
+ return this;
+ }
+
+ /**
+ * Sets a custom on-failure SLF4J logging level, in case of an exception. The log message mentions the RPC
+ * method name, the provided input, the exception and its stack trace (depending on logger settings).
+ * By default, it is {@code LOG.error}. Setting {@code NONE} will disable this logging.
+ */
+ public FutureRpcResultBuilder<I,O> onFailureLogLevel(LogLevel level) {
+ this.onFailureLogLevel = level;
+ return this;
+ }
+
+ /**
+ * Sets a custom on-success SLF4J logging level. The log message mentions the RPC method name, the provided
+ * input, and the resulting output.
+ * By default, it is {@code LOG.debug}. Setting {@code NONE} will disable this logging.
+ */
+ public FutureRpcResultBuilder<I,O> onSuccessLogLevel(LogLevel level) {
+ this.onSuccessLogLevel = level;
+ return this;
+ }
+
+ /**
+ * Sets a custom on-enter SLF4J logging level. The log message mentions the RPC method name and the provided
+ * input.
+ * By default, it is {@code LOG.trace}. Setting {@code NONE} will disable this logging.
+ */
+ public FutureRpcResultBuilder<I,O> onEnterLogLevel(LogLevel level) {
+ this.onEnterLogLevel = level;
+ return this;
+ }
+
+ /**
+ * Set a custom {@link RpcError} message function, for a given exception.
+ * By default, the message is just {@link Throwable#getMessage()}.
+ */
+ public FutureRpcResultBuilder<I,O> withRpcErrorMessage(Function<Throwable, String> newRpcErrorMessageFunction) {
+ if (rpcErrorMessageFunction != DEFAULT_ERROR_MESSAGE_FUNCTION) {
+ throw new IllegalStateException("rpcErrorMessage can only be set once");
+ }
+ this.rpcErrorMessageFunction = newRpcErrorMessageFunction;
+ return this;
+ }
+
+ /**
+ * Sets a custom on-success action, for a given output.
+ */
+ public FutureRpcResultBuilder<I,O> onSuccess(Consumer<O> newOnSuccessFunction) {
+ if (onSuccessConsumer != defaultOnSuccess) {
+ throw new IllegalStateException("onSuccess can only be set once");
+ }
+ this.onSuccessConsumer = newOnSuccessFunction;
+ return this;
+ }
+
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2018 Ericsson India Global Services Pvt Ltd. 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 INTERNAL
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.odlparent</groupId>
+ <artifactId>odlparent-lite</artifactId>
+ <version>3.0.2</version>
+ <relativePath/>
+ </parent>
+
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>tools-aggregator</artifactId>
+ <version>0.5.0-SNAPSHOT</version>
+ <!-- <name> formatting is used by autorelease to parse and notify projects on
+ build failure. Please do not modify this unless you have a good reason. -->
+ <name>ODL :: genius :: ${project.artifactId}</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>api</module>
+ <module>testutils</module>
+ </modules>
+
+ <!-- DO NOT install or deploy the repo root pom as it's only needed to initiate a build -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (c) 2018 Red Hat, Inc. 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>binding-parent</artifactId>
+ <version>0.5.0-SNAPSHOT</version>
+ <relativePath>../../commons/binding-parent</relativePath>
+ </parent>
+
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>tools-testutils</artifactId>
+ <!-- <name> formatting is used by autorelease to parse and notify projects on
+ build failure. Please do not modify this unless you have a good reason. -->
+ <name>ODL :: genius :: ${project.artifactId}</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.genius</groupId>
+ <artifactId>tools-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <!-- mdsalutil-api (test-jar) needs this, but cannot transitively inherit it (because it is and has to be <scope>test there) -->
+ <artifactId>testutils</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.inject</groupId>
+ <artifactId>guice</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.mycila.guice.extensions</groupId>
+ <artifactId>mycila-guice-jsr250</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.google.truth</groupId>
+ <artifactId>truth</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <type>test-jar</type>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.cdi</groupId>
+ <artifactId>pax-cdi-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.infrautils</groupId>
+ <artifactId>infrautils-testutils</artifactId>
+ <version>1.4.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2017 - 2018 Red Hat, Inc. 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.genius.tools.mdsal.testutils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Collection;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import org.opendaylight.genius.tools.mdsal.rpc.FutureRpcResults;
+import org.opendaylight.yangtools.yang.common.RpcError;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+
+/**
+ * Assertion utilities for {@link FutureRpcResults}.
+ *
+ * @author Michael Vorburger.ch
+ */
+@SuppressFBWarnings("BC_UNCONFIRMED_CAST") // see https://wiki.opendaylight.org/view/BestPractices/Coding_Guidelines#Unchecked.2Funconfirmed_cast_from_com.google.common.truth.Subject_to_com.google.common.truth.BooleanSubject_etc.
+public final class TestFutureRpcResults {
+
+ private TestFutureRpcResults() { }
+
+ private static <T> T getResult(RpcResult<T> rpcResult) {
+ assertThat(rpcResult.isSuccessful()).named("rpcResult.isSuccessful").isTrue();
+ T result = rpcResult.getResult();
+ assertThat(result).named("result").isNotNull();
+ return result;
+ }
+
+ public static <T> T getResult(Future<RpcResult<T>> futureRpcResult)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return getResult(futureRpcResult.get(1, MINUTES));
+ }
+
+ public static void assertVoidRpcSuccess(Future<RpcResult<Void>> futureRpcResult)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ RpcResult<Void> rpcResult = futureRpcResult.get(1, MINUTES);
+ assertThat(rpcResult.isSuccessful()).isTrue();
+ assertThat(rpcResult.getErrors()).isEmpty();
+ }
+
+ public static <T> void assertRpcErrorWithoutCausesOrMessages(Future<RpcResult<T>> futureRpcResult)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ RpcResult<T> rpcResult = futureRpcResult.get(1, MINUTES);
+ assertThat(rpcResult.isSuccessful()).named("rpcResult.isSuccessful").isFalse();
+ assertThat(rpcResult.getErrors()).named("rpcResult.errors").isEmpty();
+ }
+
+ public static <T> void assertRpcErrorCause(Future<RpcResult<T>> futureRpcResult, Class<?> expectedExceptionClass,
+ String expectedRpcErrorMessage) throws InterruptedException, ExecutionException, TimeoutException {
+ assertRpcErrorCause(futureRpcResult.get(1, MINUTES), expectedExceptionClass, expectedRpcErrorMessage);
+ }
+
+ private static <T> void assertRpcErrorCause(RpcResult<T> rpcResult, Class<?> expected1stExceptionClass,
+ String expected1stRpcErrorMessage) {
+ assertThat(rpcResult.isSuccessful()).named("rpcResult.isSuccessful").isFalse();
+ Collection<RpcError> errors = rpcResult.getErrors();
+ assertThat(errors).named("rpcResult.errors").hasSize(1);
+ RpcError error1 = errors.iterator().next();
+ assertThat(error1.getErrorType()).named("rpcResult.errors[0].errorType").isEqualTo(ErrorType.APPLICATION);
+ assertThat(error1.getMessage()).named("rpcResult.errors[0].message").isEqualTo(expected1stRpcErrorMessage);
+ if (error1.getCause() != null) {
+ // Check needed because FutureRpcResults does not propagate cause if OperationFailedException
+ assertThat(error1.getCause()).named("rpcResult.errors[0].cause").isInstanceOf(expected1stExceptionClass);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Red Hat, Inc. 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.genius.tools.mdsal.rpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.Futures.immediateFailedFuture;
+import static com.google.common.util.concurrent.Futures.immediateFuture;
+import static org.opendaylight.genius.tools.mdsal.rpc.FutureRpcResults.LogLevel.NONE;
+
+import com.google.common.truth.Truth;
+import com.google.common.util.concurrent.Futures;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Rule;
+import org.junit.Test;
+import org.opendaylight.genius.tools.mdsal.rpc.FutureRpcResults.LogLevel;
+import org.opendaylight.genius.tools.mdsal.testutils.TestFutureRpcResults;
+import org.opendaylight.infrautils.testutils.LogCaptureRule;
+import org.opendaylight.infrautils.testutils.LogRule;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Unit Test for {@link FutureRpcResults}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class FutureRpcResultsTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(FutureRpcResultsTest.class);
+
+ public @Rule LogRule logRule = new LogRule();
+ public @Rule LogCaptureRule logCaptureRule = new LogCaptureRule();
+
+ @Test
+ public void testListenableFutureSuccess() throws Exception {
+ Future<RpcResult<String>> future = FutureRpcResults.fromListenableFuture(
+ LOG, null, () -> immediateFuture("hello, world")).build();
+ Truth.assertThat(TestFutureRpcResults.getResult(future)).isEqualTo("hello, world");
+ }
+
+ @Test
+ public void testFailedListenableFuture() throws Exception {
+ logCaptureRule.expectError("RPC testFailedListenableFuture() failed; input = null");
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(LOG, null, () ->
+ immediateFailedFuture(new IllegalArgumentException("boum"))).build(),
+ IllegalArgumentException.class, "boum");
+ }
+
+ @Test
+ public void testFromListenableFutureException() throws Exception {
+ logCaptureRule.expectError("RPC testFromListenableFutureException() failed; input = null");
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(
+ LOG, null, () -> {
+ throw new IllegalArgumentException("bam");
+ }).build(), IllegalArgumentException.class, "bam");
+ }
+
+ @Test
+ public void testFromListenableFutureExceptionWarnInsteadError() throws Exception {
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(
+ LOG, "testFromListenableFutureException", null, () -> {
+ throw new IllegalArgumentException("bam");
+ }).onFailureLogLevel(LogLevel.WARN).build(), IllegalArgumentException.class, "bam");
+ }
+
+ @Test
+ public void testFromListenableFutureExceptionNoLog() throws Exception {
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(
+ LOG, "testFromListenableFutureException", null, () -> {
+ throw new IllegalArgumentException("bam");
+ }).onFailureLogLevel(NONE).build(), IllegalArgumentException.class, "bam");
+ }
+
+ @Test
+ public void testFromListenableFutureExceptionAlsoLog() throws Exception {
+ final AtomicBoolean afterLogActionCalled = new AtomicBoolean(false);
+ logCaptureRule.expectError("RPC testFromListenableFutureException() failed; input = null");
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(
+ LOG, "testFromListenableFutureException", null, () -> {
+ throw new IllegalArgumentException("bam");
+ }).onFailure(e -> afterLogActionCalled.set(true)).build(), IllegalArgumentException.class, "bam");
+ assertThat(afterLogActionCalled.get()).isTrue();
+ }
+
+ @Test
+ public void testFromListenableFutureExceptionCustomMessage() throws Exception {
+ logCaptureRule.expectError("RPC testFromListenableFutureExceptionCustomMessage() failed; input = null");
+ TestFutureRpcResults.assertRpcErrorCause(FutureRpcResults.fromListenableFuture(LOG, null, () -> {
+ throw new IllegalArgumentException("bam");
+ }).withRpcErrorMessage(e -> "tra la la").build(), IllegalArgumentException.class, "tra la la");
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExtraOnFailureThrowsException() throws Exception {
+ FutureRpcResults.fromListenableFuture(LOG, null, () -> Futures.immediateFuture(null)).onFailure(failure -> {
+ }).onFailure(failure -> {
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExtraOnSuccessThrowsException() throws Exception {
+ FutureRpcResults.fromListenableFuture(LOG, null, () -> Futures.immediateFuture(null)).onSuccess(result -> {
+ }).onSuccess(result -> {
+ });
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testExtraWithRpcErrorMessageThrowsException() throws Exception {
+ FutureRpcResults.fromListenableFuture(LOG, null, () -> Futures.immediateFuture(null)).withRpcErrorMessage(
+ error -> null).withRpcErrorMessage(error -> null);
+ }
+
+}