Proxy DOMRpcRouter to the mdsal implementation 76/73376/3
authorTom Pantelis <tompantelis@gmail.com>
Sat, 23 Jun 2018 03:30:17 +0000 (23:30 -0400)
committerTom Pantelis <tompantelis@gmail.com>
Mon, 25 Jun 2018 13:28:16 +0000 (09:28 -0400)
Proxying to the dom mdsal RPC service(s) will allow
an easier migration path where both can co-exist.

Change-Id: Ib31efc2985b24e83106e6a25d2c94c9c29850eb2
Signed-off-by: Tom Pantelis <tompantelis@gmail.com>
opendaylight/md-sal/sal-binding-broker/src/test/java/org/opendaylight/controller/sal/binding/test/util/BindingTestContext.java
opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcException.java
opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcImplementationNotAvailableException.java
opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DOMRpcResult.java
opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouter.java
opendaylight/md-sal/sal-dom-broker/src/main/resources/org/opendaylight/blueprint/dom-broker.xml
opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java [new file with mode: 0644]
opendaylight/md-sal/sal-dom-broker/src/test/resources/odl-datastore-test.yang

index a5b55a3..29f7cd3 100644 (file)
@@ -208,8 +208,9 @@ public class BindingTestContext implements AutoCloseable {
     private void startDomBroker() {
         checkState(this.executor != null);
 
-        this.domRouter = new DOMRpcRouter();
-        this.mockSchemaService.registerSchemaContextListener(this.domRouter);
+        org.opendaylight.mdsal.dom.broker.DOMRpcRouter delegate =
+                org.opendaylight.mdsal.dom.broker.DOMRpcRouter.newInstance(mockSchemaService);
+        this.domRouter = new DOMRpcRouter(delegate, delegate);
 
         final ClassToInstanceMap<BrokerService> services = MutableClassToInstanceMap.create();
         services.put(DOMRpcService.class, this.domRouter);
index 7ea4f4c..e4c30aa 100644 (file)
@@ -7,11 +7,14 @@
  */
 package org.opendaylight.controller.md.sal.dom.api;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
 /**
  * Base class for failures that can occur during RPC invocation. This covers
  * transport and protocol-level failures.
  */
-public abstract class DOMRpcException extends Exception {
+@SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_SUPERCLASS", justification = "Migration")
+public abstract class DOMRpcException extends org.opendaylight.mdsal.dom.api.DOMRpcException {
     private static final long serialVersionUID = 1L;
 
     /**
index 7168528..2858255 100644 (file)
@@ -24,4 +24,8 @@ public class DOMRpcImplementationNotAvailableException extends DOMRpcException {
             final Object... args) {
         super(String.format(format, args), Preconditions.checkNotNull(cause));
     }
+
+    public DOMRpcImplementationNotAvailableException(final String message, final Throwable cause) {
+        super(message, cause);
+    }
 }
index 5893688..ef7c670 100644 (file)
@@ -7,30 +7,11 @@
  */
 package org.opendaylight.controller.md.sal.dom.api;
 
-import java.util.Collection;
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import org.opendaylight.yangtools.yang.common.RpcError;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
  * Interface defining a result of an RPC call.
  */
-public interface DOMRpcResult {
-    /**
-     * Returns a set of errors and warnings which occurred during processing
-     * the call.
-     *
-     * @return a Collection of {@link RpcError}, guaranteed to be non-null. In case
-     *         no errors are reported, an empty collection is returned.
-     */
-    @Nonnull Collection<RpcError> getErrors();
-
-    /**
-     * Returns the value result of the call or null if no result is available.
-     *
-     * @return Invocation result, null if the operation has not produced a result. This might
-     *         be the case if the operation does not produce a result, or if it failed.
-     */
-    @Nullable NormalizedNode<?, ?> getResult();
+@SuppressFBWarnings(value = "NM_SAME_SIMPLE_NAME_AS_INTERFACE", justification = "Migration")
+public interface DOMRpcResult extends org.opendaylight.mdsal.dom.api.DOMRpcResult {
 }
diff --git a/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java b/opendaylight/md-sal/sal-dom-api/src/main/java/org/opendaylight/controller/md/sal/dom/api/DefaultDOMRpcException.java
new file mode 100644 (file)
index 0000000..c07b31a
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2018 Inocybe Technologies 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.controller.md.sal.dom.api;
+
+/**
+ * Default implementation of DOMRpcException.
+ *
+ * @author Thomas Pantelis
+ */
+public class DefaultDOMRpcException extends DOMRpcException {
+    private static final long serialVersionUID = 1L;
+
+    public DefaultDOMRpcException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
index f8e47bb..3d57aac 100644 (file)
@@ -7,61 +7,81 @@
  */
 package org.opendaylight.controller.md.sal.dom.broker.impl;
 
-import com.google.common.base.Preconditions;
-import com.google.common.base.Verify;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableList.Builder;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.MapDifference;
-import com.google.common.collect.MapDifference.ValueDifference;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.CheckedFuture;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import java.util.ArrayList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import javax.annotation.concurrent.GuardedBy;
+import java.util.WeakHashMap;
+import java.util.stream.Collectors;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcProviderService;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
+import org.opendaylight.controller.md.sal.dom.api.DefaultDOMRpcException;
 import org.opendaylight.controller.md.sal.dom.spi.AbstractDOMRpcImplementationRegistration;
-import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.mdsal.common.api.MappingCheckedFuture;
 import org.opendaylight.yangtools.concepts.AbstractListenerRegistration;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.util.concurrent.ExceptionMapper;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaContextListener;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 
 public final class DOMRpcRouter implements AutoCloseable, DOMRpcService, DOMRpcProviderService, SchemaContextListener {
-    private static final ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
-            .setNameFormat("DOMRpcRouter-listener-%s").setDaemon(true).build();
+    private static final ExceptionMapper<org.opendaylight.mdsal.dom.api.DOMRpcException> MDSAL_DOM_RPC_EX_MAPPER =
+            new ExceptionMapper<org.opendaylight.mdsal.dom.api.DOMRpcException>(
+                    "rpc", org.opendaylight.mdsal.dom.api.DOMRpcException.class) {
+        @Override
+        protected org.opendaylight.mdsal.dom.api.DOMRpcException newWithCause(String message, Throwable cause) {
+            return cause instanceof org.opendaylight.mdsal.dom.api.DOMRpcException
+                    ? (org.opendaylight.mdsal.dom.api.DOMRpcException)cause
+                            : new org.opendaylight.mdsal.dom.api.DefaultDOMRpcException("RPC failed", cause);
+        }
+    };
+
+    private static final ExceptionMapper<DOMRpcException> LEGACY_DOM_RPC_EX_MAPPER =
+            new ExceptionMapper<DOMRpcException>("rpc", DOMRpcException.class) {
+        @Override
+        protected DOMRpcException newWithCause(String message, Throwable cause) {
+            return cause instanceof DOMRpcException ? (DOMRpcException)cause
+                : cause instanceof org.opendaylight.mdsal.dom.api.DOMRpcImplementationNotAvailableException
+                    ? new DOMRpcImplementationNotAvailableException(cause.getMessage(), cause.getCause())
+                        : new DefaultDOMRpcException("RPC failed", cause);
+        }
+    };
 
-    private final ExecutorService listenerNotifier = Executors.newSingleThreadExecutor(THREAD_FACTORY);
+    // This mapping is used to translate mdsal DOMRpcImplementations to their corresponding legacy
+    // DOMRpcImplementations registered thru this interface when invoking a DOMRpcAvailabilityListener.
+    private final Map<org.opendaylight.mdsal.dom.api.DOMRpcImplementation, DOMRpcImplementation> implMapping =
+            Collections.synchronizedMap(new WeakHashMap<>());
 
-    @GuardedBy("this")
-    private Collection<Registration<?>> listeners = Collections.emptyList();
+    private final org.opendaylight.mdsal.dom.api.DOMRpcService delegateRpcService;
+    private final org.opendaylight.mdsal.dom.api.DOMRpcProviderService delegateRpcProviderService;
 
-    private volatile DOMRpcRoutingTable routingTable = DOMRpcRoutingTable.EMPTY;
+    @VisibleForTesting
+    public DOMRpcRouter() {
+        org.opendaylight.mdsal.dom.broker.DOMRpcRouter delegate = new org.opendaylight.mdsal.dom.broker.DOMRpcRouter();
+        this.delegateRpcService = delegate;
+        this.delegateRpcProviderService = delegate;
+    }
 
-    public static DOMRpcRouter newInstance(final SchemaService schemaService) {
-        final DOMRpcRouter rpcRouter = new DOMRpcRouter();
-        schemaService.registerSchemaContextListener(rpcRouter);
-        return rpcRouter;
+    public DOMRpcRouter(final org.opendaylight.mdsal.dom.api.DOMRpcService delegateRpcService,
+            final org.opendaylight.mdsal.dom.api.DOMRpcProviderService delegateRpcProviderService) {
+        this.delegateRpcService = delegateRpcService;
+        this.delegateRpcProviderService = delegateRpcProviderService;
     }
 
     @Override
@@ -70,160 +90,112 @@ public final class DOMRpcRouter implements AutoCloseable, DOMRpcService, DOMRpcP
         return registerRpcImplementation(implementation, ImmutableSet.copyOf(rpcs));
     }
 
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
     public synchronized <T extends DOMRpcImplementation> DOMRpcImplementationRegistration<T> registerRpcImplementation(
             final T implementation, final Set<DOMRpcIdentifier> rpcs) {
-        final DOMRpcRoutingTable oldTable = routingTable;
-        final DOMRpcRoutingTable newTable = oldTable.add(implementation, rpcs);
-        routingTable = newTable;
+        org.opendaylight.mdsal.dom.api.DOMRpcImplementation delegateImpl =
+            new org.opendaylight.mdsal.dom.api.DOMRpcImplementation() {
+                @Override
+                public CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+                        org.opendaylight.mdsal.dom.api.DOMRpcException> invokeRpc(
+                        org.opendaylight.mdsal.dom.api.DOMRpcIdentifier rpc, NormalizedNode<?, ?> input) {
+                    final ListenableFuture future = implementation.invokeRpc(convert(rpc), input);
+                    return MappingCheckedFuture.create(future, MDSAL_DOM_RPC_EX_MAPPER);
+                }
+
+                @Override
+                public long invocationCost() {
+                    return implementation.invocationCost();
+                }
+            };
+
+        implMapping.put(delegateImpl, implementation);
 
-        listenerNotifier.execute(() -> notifyAdded(newTable, implementation));
+        final org.opendaylight.mdsal.dom.api.DOMRpcImplementationRegistration
+            <org.opendaylight.mdsal.dom.api.DOMRpcImplementation> reg = delegateRpcProviderService
+                .registerRpcImplementation(delegateImpl,
+                        rpcs.stream().map(DOMRpcRouter::convert).collect(Collectors.toSet()));
 
         return new AbstractDOMRpcImplementationRegistration<T>(implementation) {
             @Override
             protected void removeRegistration() {
-                removeRpcImplementation(getInstance(), rpcs);
+                reg.close();
+                implMapping.remove(delegateImpl);
             }
         };
     }
 
-    private synchronized void removeRpcImplementation(final DOMRpcImplementation implementation,
-                                                      final Set<DOMRpcIdentifier> rpcs) {
-        final DOMRpcRoutingTable oldTable = routingTable;
-        final DOMRpcRoutingTable newTable = oldTable.remove(implementation, rpcs);
-        routingTable = newTable;
-
-        listenerNotifier.execute(() -> notifyRemoved(newTable, implementation));
+    private static org.opendaylight.mdsal.dom.api.DOMRpcIdentifier convert(DOMRpcIdentifier from) {
+        return org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create(from.getType(), from.getContextReference());
     }
 
-    @Override
-    public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type,
-                                                                  final NormalizedNode<?, ?> input) {
-        return routingTable.invokeRpc(type, input);
+    private static DOMRpcIdentifier convert(org.opendaylight.mdsal.dom.api.DOMRpcIdentifier from) {
+        return DOMRpcIdentifier.create(from.getType(), from.getContextReference());
     }
 
-    private synchronized void removeListener(final ListenerRegistration<? extends DOMRpcAvailabilityListener> reg) {
-        listeners = ImmutableList.copyOf(Collections2.filter(listeners, i -> !reg.equals(i)));
+    private static ListenableFuture<DOMRpcResult> convert(org.opendaylight.mdsal.dom.api.DOMRpcResult from) {
+        return from == null ? Futures.immediateFuture(null)
+                : from instanceof DOMRpcResult ? Futures.immediateFuture((DOMRpcResult)from)
+                        : Futures.immediateFuture(new DefaultDOMRpcResult(from.getResult(), from.getErrors()));
     }
 
-    private synchronized void notifyAdded(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) {
-        for (Registration<?> l : listeners) {
-            l.addRpc(newTable, impl);
-        }
+    private static Collection<DOMRpcIdentifier> convert(
+            Collection<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> from) {
+        return from.stream().map(DOMRpcRouter::convert).collect(Collectors.toList());
     }
 
-    private synchronized void notifyRemoved(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) {
-        for (Registration<?> l : listeners) {
-            l.removeRpc(newTable, impl);
-        }
+    @Override
+    public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type,
+                                                                  final NormalizedNode<?, ?> input) {
+        final ListenableFuture<DOMRpcResult> future = Futures.transformAsync(delegateRpcService.invokeRpc(type, input),
+                DOMRpcRouter::convert, MoreExecutors.directExecutor());
+        return MappingCheckedFuture.create(future, LEGACY_DOM_RPC_EX_MAPPER);
     }
 
     @Override
     public synchronized <T extends DOMRpcAvailabilityListener> ListenerRegistration<T> registerRpcListener(
             final T listener) {
-        final Registration<T> ret = new Registration<>(this, listener, routingTable.getRpcs(listener));
-        final Builder<Registration<?>> b = ImmutableList.builder();
-        b.addAll(listeners);
-        b.add(ret);
-        listeners = b.build();
-
-        listenerNotifier.execute(ret::initialTable);
-        return ret;
-    }
-
-    @Override
-    public synchronized void onGlobalContextUpdated(final SchemaContext context) {
-        final DOMRpcRoutingTable oldTable = routingTable;
-        final DOMRpcRoutingTable newTable = oldTable.setSchemaContext(context);
-        routingTable = newTable;
-    }
-
-    @Override
-    public void close() {
-        listenerNotifier.shutdown();
-    }
-
-    private static final class Registration<T extends DOMRpcAvailabilityListener> extends
-            AbstractListenerRegistration<T> {
-
-        private final DOMRpcRouter router;
-
-        private Map<SchemaPath, Set<YangInstanceIdentifier>> prevRpcs;
-
-        Registration(final DOMRpcRouter router, final T listener,
-                     final Map<SchemaPath, Set<YangInstanceIdentifier>> rpcs) {
-            super(Preconditions.checkNotNull(listener));
-            this.router = Preconditions.checkNotNull(router);
-            this.prevRpcs = Preconditions.checkNotNull(rpcs);
-        }
-
-        @Override
-        protected void removeRegistration() {
-            router.removeListener(this);
-        }
-
-        void initialTable() {
-            final Collection<DOMRpcIdentifier> added = new ArrayList<>();
-            for (Entry<SchemaPath, Set<YangInstanceIdentifier>> e : prevRpcs.entrySet()) {
-                added.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i)));
-            }
-
-            if (!added.isEmpty()) {
-                final T l = getInstance();
-                l.onRpcAvailable(added);
-            }
-        }
-
-        void addRpc(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) {
-            final T l = getInstance();
-            if (!l.acceptsImplementation(impl)) {
-                return;
-            }
-
-            final Map<SchemaPath, Set<YangInstanceIdentifier>> rpcs = Verify.verifyNotNull(newTable.getRpcs(l));
-            final MapDifference<SchemaPath, Set<YangInstanceIdentifier>> diff = Maps.difference(prevRpcs, rpcs);
+        final ListenerRegistration<org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener> reg =
+            delegateRpcService.registerRpcListener(new org.opendaylight.mdsal.dom.api.DOMRpcAvailabilityListener() {
+                @Override
+                public void onRpcAvailable(Collection<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> rpcs) {
+                    listener.onRpcAvailable(convert(rpcs));
+                }
 
-            final Collection<DOMRpcIdentifier> added = new ArrayList<>();
-            for (Entry<SchemaPath, Set<YangInstanceIdentifier>> e : diff.entriesOnlyOnRight().entrySet()) {
-                added.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i)));
-            }
-            for (Entry<SchemaPath, ValueDifference<Set<YangInstanceIdentifier>>> e : diff.entriesDiffering()
-                    .entrySet()) {
-                for (YangInstanceIdentifier i : Sets.difference(e.getValue().rightValue(), e.getValue().leftValue())) {
-                    added.add(DOMRpcIdentifier.create(e.getKey(), i));
+                @Override
+                public void onRpcUnavailable(Collection<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier> rpcs) {
+                    listener.onRpcUnavailable(convert(rpcs));
                 }
-            }
 
-            prevRpcs = rpcs;
-            if (!added.isEmpty()) {
-                l.onRpcAvailable(added);
-            }
-        }
+                @Override
+                public boolean acceptsImplementation(final org.opendaylight.mdsal.dom.api.DOMRpcImplementation impl) {
+                    // If the DOMRpcImplementation wasn't registered thru this interface then the mapping won't be
+                    // present - in this we can't call the listener so just assume acceptance which is the default
+                    // behavior. This should be fine since a legacy listener would not be aware of implementation types
+                    // registered via the new mdsal API.
+                    final DOMRpcImplementation legacyImpl = implMapping.get(impl);
+                    return legacyImpl != null ? listener.acceptsImplementation(legacyImpl) : true;
+                }
+            });
 
-        void removeRpc(final DOMRpcRoutingTable newTable, final DOMRpcImplementation impl) {
-            final T l = getInstance();
-            if (!l.acceptsImplementation(impl)) {
-                return;
+        return new AbstractListenerRegistration<T>(listener) {
+            @Override
+            protected void removeRegistration() {
+                reg.close();
             }
+        };
+    }
 
-            final Map<SchemaPath, Set<YangInstanceIdentifier>> rpcs = Verify.verifyNotNull(newTable.getRpcs(l));
-            final MapDifference<SchemaPath, Set<YangInstanceIdentifier>> diff = Maps.difference(prevRpcs, rpcs);
-
-            final Collection<DOMRpcIdentifier> removed = new ArrayList<>();
-            for (Entry<SchemaPath, Set<YangInstanceIdentifier>> e : diff.entriesOnlyOnLeft().entrySet()) {
-                removed.addAll(Collections2.transform(e.getValue(), i -> DOMRpcIdentifier.create(e.getKey(), i)));
-            }
-            for (Entry<SchemaPath, ValueDifference<Set<YangInstanceIdentifier>>> e : diff.entriesDiffering()
-                    .entrySet()) {
-                for (YangInstanceIdentifier i : Sets.difference(e.getValue().leftValue(), e.getValue().rightValue())) {
-                    removed.add(DOMRpcIdentifier.create(e.getKey(), i));
-                }
-            }
+    @Override
+    public void close() {
+    }
 
-            prevRpcs = rpcs;
-            if (!removed.isEmpty()) {
-                l.onRpcUnavailable(removed);
-            }
+    @Override
+    @VisibleForTesting
+    public void onGlobalContextUpdated(final SchemaContext context) {
+        if (delegateRpcService instanceof SchemaContextListener) {
+            ((SchemaContextListener)delegateRpcService).onGlobalContextUpdated(context);
         }
     }
 }
index d86c823..4cc8529 100644 (file)
 
   <!-- DOM RPC Service -->
 
-  <bean id="domRpcRouter" class="org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRouter"
-          factory-method="newInstance">
-    <argument ref="schemaService"/>
+  <reference id="domRpcService" interface="org.opendaylight.mdsal.dom.api.DOMRpcService"
+      odl:type="default"/>
+  <reference id="domRpcProviderService" interface="org.opendaylight.mdsal.dom.api.DOMRpcProviderService"
+      odl:type="default"/>
+
+  <bean id="domRpcRouter" class="org.opendaylight.controller.md.sal.dom.broker.impl.DOMRpcRouter">
+    <argument ref="domRpcService"/>
+    <argument ref="domRpcProviderService"/>
   </bean>
 
   <service ref="domRpcRouter" odl:type="default">
diff --git a/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java b/opendaylight/md-sal/sal-dom-broker/src/test/java/org/opendaylight/controller/md/sal/dom/broker/impl/DOMRpcRouterTest.java
new file mode 100644 (file)
index 0000000..e2356e3
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2018 Inocybe Technologies 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.controller.md.sal.dom.broker.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcAvailabilityListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcIdentifier;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementation;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationNotAvailableException;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcImplementationRegistration;
+import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.spi.DefaultDOMRpcResult;
+import org.opendaylight.controller.md.sal.dom.store.impl.TestModel;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.RpcError.ErrorType;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.opendaylight.yangtools.yang.model.api.Module;
+import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+
+/**
+ * Unit tests for DOMRpcRouter.
+ *
+ * @author Thomas Pantelis
+ */
+public class DOMRpcRouterTest {
+
+    private static final NormalizedNode<?, ?> RPC_INPUT = ImmutableNodes.leafNode(
+            QName.create(TestModel.TEST_QNAME.getModule(), "input-leaf"), "foo");
+    private static final NormalizedNode<?, ?> RPC_OUTPUT = ImmutableNodes.leafNode(
+            QName.create(TestModel.TEST_QNAME.getModule(), "output-leaf"), "bar");
+    private final TestLegacyDOMRpcImplementation testLegacyRpcImpl = new TestLegacyDOMRpcImplementation();
+    private final TestMdsalDOMRpcImplementation testMdsalRpcImpl = new TestMdsalDOMRpcImplementation();
+    private org.opendaylight.mdsal.dom.broker.DOMRpcRouter mdsalRpcRouter;
+    private DOMRpcRouter legacyRpcRouter;
+    private DOMRpcIdentifier legacyTestRpcIdentifier;
+    private DOMRpcIdentifier legacyTestRpcNoInputIdentifier;
+    private org.opendaylight.mdsal.dom.api.DOMRpcIdentifier mdsalTestRpcIdentifier;
+    private org.opendaylight.mdsal.dom.api.DOMRpcIdentifier mdsalTestRpcNoInputIdentifier;
+
+    @Before
+    public void setup() {
+        mdsalRpcRouter = new org.opendaylight.mdsal.dom.broker.DOMRpcRouter();
+        final SchemaContext schemaContext = TestModel.createTestContext();
+        mdsalRpcRouter.onGlobalContextUpdated(schemaContext);
+        legacyRpcRouter = new DOMRpcRouter(mdsalRpcRouter, mdsalRpcRouter);
+
+        legacyTestRpcIdentifier = DOMRpcIdentifier.create(findRpc(schemaContext, "test-rpc"));
+        legacyTestRpcNoInputIdentifier = DOMRpcIdentifier.create(findRpc(schemaContext, "test-rpc-no-input"));
+        mdsalTestRpcIdentifier = org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create(
+                findRpc(schemaContext, "test-rpc"));
+        mdsalTestRpcNoInputIdentifier = org.opendaylight.mdsal.dom.api.DOMRpcIdentifier.create(
+                findRpc(schemaContext, "test-rpc-no-input"));
+    }
+
+    @Test
+    public void testLegacyRegistrationAndInvocation() throws InterruptedException, ExecutionException {
+        final DOMRpcImplementationRegistration<TestLegacyDOMRpcImplementation> reg =
+            legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier,
+                    legacyTestRpcNoInputIdentifier);
+
+        // Test success
+
+        DefaultDOMRpcResult result = new DefaultDOMRpcResult(RPC_OUTPUT);
+        testLegacyRpcImpl.init(Futures.immediateCheckedFuture(result));
+
+        ListenableFuture<DOMRpcResult> future = legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT);
+
+        assertSame(result, future.get());
+        testLegacyRpcImpl.verifyInput(legacyTestRpcIdentifier, RPC_INPUT);
+
+        // Test exception returned
+
+        TestLegacyDOMRpcException rpcEx = new TestLegacyDOMRpcException();
+        testLegacyRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx));
+
+        try {
+            legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get();
+            fail("Expected exception");
+        } catch (ExecutionException e) {
+            assertEquals(rpcEx, e.getCause());
+        }
+
+        // Test no input or output
+
+        testLegacyRpcImpl.init(Futures.immediateCheckedFuture(null));
+
+        future = legacyRpcRouter.invokeRpc(legacyTestRpcNoInputIdentifier.getType(), null);
+
+        assertNull(future.get());
+        testLegacyRpcImpl.verifyInput(legacyTestRpcNoInputIdentifier, null);
+
+        // Test close
+
+        reg.close();
+
+        try {
+            legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get();
+            fail("Expected exception");
+        } catch (ExecutionException e) {
+            assertTrue(e.getCause() instanceof DOMRpcImplementationNotAvailableException);
+        }
+    }
+
+    @Test
+    public void testLegacyRegistrationAndMdsalInvocation() throws InterruptedException, ExecutionException {
+        legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier,
+                legacyTestRpcNoInputIdentifier);
+
+        // Test success
+
+        DefaultDOMRpcResult result = new DefaultDOMRpcResult(RPC_OUTPUT,
+                Collections.singleton(RpcResultBuilder.newError(ErrorType.RPC, "tag", "message")));
+        testLegacyRpcImpl.init(Futures.immediateCheckedFuture(result));
+
+        ListenableFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult> future =
+                mdsalRpcRouter.invokeRpc(mdsalTestRpcIdentifier.getType(), RPC_INPUT);
+
+        assertEquals(RPC_OUTPUT, future.get().getResult());
+        assertEquals(1, future.get().getErrors().size());
+        assertEquals(ErrorType.RPC, future.get().getErrors().iterator().next().getErrorType());
+        assertEquals("tag", future.get().getErrors().iterator().next().getTag());
+        assertEquals("message", future.get().getErrors().iterator().next().getMessage());
+        testLegacyRpcImpl.verifyInput(legacyTestRpcIdentifier, RPC_INPUT);
+
+        // Test exception returned
+
+        TestLegacyDOMRpcException rpcEx = new TestLegacyDOMRpcException();
+        testLegacyRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx));
+
+        try {
+            mdsalRpcRouter.invokeRpc(mdsalTestRpcIdentifier.getType(), RPC_INPUT).get();
+            fail("Expected exception");
+        } catch (ExecutionException e) {
+            assertEquals(rpcEx, e.getCause());
+        }
+
+        // Test no input or output
+
+        testLegacyRpcImpl.init(Futures.immediateCheckedFuture(null));
+
+        future = mdsalRpcRouter.invokeRpc(mdsalTestRpcNoInputIdentifier.getType(), null);
+
+        assertNull(future.get());
+        testLegacyRpcImpl.verifyInput(legacyTestRpcNoInputIdentifier, null);
+    }
+
+    @Test
+    public void testMdsalRegistrationAndLegacyInvocation() throws InterruptedException, ExecutionException {
+        mdsalRpcRouter.registerRpcImplementation(testMdsalRpcImpl, mdsalTestRpcIdentifier,
+                mdsalTestRpcNoInputIdentifier);
+
+        // Test success
+
+        org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult result =
+            new org.opendaylight.mdsal.dom.spi.DefaultDOMRpcResult(RPC_OUTPUT,
+                Collections.singleton(RpcResultBuilder.newError(ErrorType.RPC, "tag", "message")));
+        testMdsalRpcImpl.init(Futures.immediateCheckedFuture(result));
+
+        ListenableFuture<DOMRpcResult> future = legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT);
+
+        assertEquals(RPC_OUTPUT, future.get().getResult());
+        assertEquals(1, future.get().getErrors().size());
+        assertEquals(ErrorType.RPC, future.get().getErrors().iterator().next().getErrorType());
+        assertEquals("tag", future.get().getErrors().iterator().next().getTag());
+        assertEquals("message", future.get().getErrors().iterator().next().getMessage());
+        testMdsalRpcImpl.verifyInput(mdsalTestRpcIdentifier, RPC_INPUT);
+
+        // Test exception returned
+
+        TestMdsalDOMRpcException rpcEx = new TestMdsalDOMRpcException();
+        testMdsalRpcImpl.init(Futures.immediateFailedCheckedFuture(rpcEx));
+
+        try {
+            legacyRpcRouter.invokeRpc(legacyTestRpcIdentifier.getType(), RPC_INPUT).get();
+            fail("Expected exception");
+        } catch (ExecutionException e) {
+            assertTrue("Unexpected exception " + e.getCause(), e.getCause() instanceof DOMRpcException);
+            assertEquals(rpcEx, e.getCause().getCause());
+        }
+
+        // Test no input or output
+
+        testMdsalRpcImpl.init(Futures.immediateCheckedFuture(null));
+
+        future = legacyRpcRouter.invokeRpc(legacyTestRpcNoInputIdentifier.getType(), null);
+
+        assertNull(future.get());
+        testMdsalRpcImpl.verifyInput(mdsalTestRpcNoInputIdentifier, null);
+    }
+
+    @Test
+    public void testRegisterRpcListener() {
+        final TestLegacyDOMRpcImplementation2 testRpcImpl2 = new TestLegacyDOMRpcImplementation2();
+
+        DOMRpcAvailabilityListener listener = mock(DOMRpcAvailabilityListener.class);
+        doNothing().when(listener).onRpcAvailable(any());
+        doNothing().when(listener).onRpcUnavailable(any());
+        doReturn(true).when(listener).acceptsImplementation(any());
+        final ListenerRegistration<?> listenerReg = legacyRpcRouter.registerRpcListener(listener);
+
+        DOMRpcAvailabilityListener filteredListener = mock(DOMRpcAvailabilityListener.class);
+        doNothing().when(filteredListener).onRpcAvailable(any());
+        doNothing().when(filteredListener).onRpcUnavailable(any());
+        doReturn(true).when(filteredListener).acceptsImplementation(testLegacyRpcImpl);
+        doReturn(false).when(filteredListener).acceptsImplementation(testRpcImpl2);
+        final ListenerRegistration<?> filteredListenerReg = legacyRpcRouter.registerRpcListener(filteredListener);
+
+        final DOMRpcImplementationRegistration<?> testRpcReg =
+                legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier);
+
+        verify(listener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier));
+        verify(filteredListener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier));
+
+        final DOMRpcImplementationRegistration<?> testRpcNoInputReg =
+                legacyRpcRouter.registerRpcImplementation(testRpcImpl2, legacyTestRpcNoInputIdentifier);
+
+        verify(listener, timeout(5000)).onRpcAvailable(ImmutableList.of(legacyTestRpcNoInputIdentifier));
+        verify(filteredListener, after(200).never()).onRpcAvailable(ImmutableList.of(legacyTestRpcNoInputIdentifier));
+
+        testRpcReg.close();
+
+        verify(listener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcIdentifier));
+        verify(filteredListener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcIdentifier));
+
+        testRpcNoInputReg.close();
+
+        verify(listener, timeout(5000)).onRpcUnavailable(ImmutableList.of(legacyTestRpcNoInputIdentifier));
+        verify(filteredListener, after(200).never()).onRpcUnavailable(ImmutableList.of(legacyTestRpcNoInputIdentifier));
+
+        reset(listener, filteredListener);
+
+        listenerReg.close();
+        filteredListenerReg.close();
+
+        legacyRpcRouter.registerRpcImplementation(testLegacyRpcImpl, legacyTestRpcIdentifier);
+
+        verify(listener, after(200).never()).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier));
+        verify(filteredListener, never()).onRpcAvailable(ImmutableList.of(legacyTestRpcIdentifier));
+    }
+
+    private static SchemaPath findRpc(SchemaContext schemaContext, String name) {
+        Module testModule = schemaContext.findModule("odl-datastore-test", TestModel.TEST_QNAME.getRevision()).get();
+        RpcDefinition rpcDefinition = null;
+        for (RpcDefinition def: testModule.getRpcs()) {
+            if (def.getQName().getLocalName().equals(name)) {
+                rpcDefinition = def;
+                break;
+            }
+        }
+
+        assertNotNull(name + " rpc not found in " + testModule.getRpcs(), rpcDefinition);
+        return rpcDefinition.getPath();
+    }
+
+    private abstract static class AbstractDOMRpcImplementation<T> {
+        Entry<T, NormalizedNode<?, ?>> rpcInput;
+
+        void verifyInput(T expRpc, NormalizedNode<?, ?> expInput) {
+            assertNotNull(rpcInput);
+            assertEquals(expRpc, rpcInput.getKey());
+            assertEquals(expInput, rpcInput.getValue());
+        }
+    }
+
+    private static class TestLegacyDOMRpcImplementation extends AbstractDOMRpcImplementation<DOMRpcIdentifier>
+            implements DOMRpcImplementation {
+        CheckedFuture<DOMRpcResult, DOMRpcException> returnFuture;
+
+        @Override
+        public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(
+                final DOMRpcIdentifier rpc, final NormalizedNode<?, ?> input) {
+            rpcInput = new SimpleEntry<>(rpc, input);
+            return returnFuture;
+        }
+
+        void init(CheckedFuture<DOMRpcResult, DOMRpcException> retFuture) {
+            this.returnFuture = retFuture;
+            rpcInput = null;
+        }
+    }
+
+    private static class TestMdsalDOMRpcImplementation
+            extends AbstractDOMRpcImplementation<org.opendaylight.mdsal.dom.api.DOMRpcIdentifier>
+            implements org.opendaylight.mdsal.dom.api.DOMRpcImplementation {
+        CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+                org.opendaylight.mdsal.dom.api.DOMRpcException> returnFuture;
+
+        @Override
+        public CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+                org.opendaylight.mdsal.dom.api.DOMRpcException> invokeRpc(
+                    final org.opendaylight.mdsal.dom.api.DOMRpcIdentifier rpc, final NormalizedNode<?, ?> input) {
+            rpcInput = new SimpleEntry<>(rpc, input);
+            return returnFuture;
+        }
+
+        void init(CheckedFuture<org.opendaylight.mdsal.dom.api.DOMRpcResult,
+                org.opendaylight.mdsal.dom.api.DOMRpcException> retFuture) {
+            this.returnFuture = retFuture;
+            rpcInput = null;
+        }
+    }
+
+    private static class TestLegacyDOMRpcImplementation2 implements DOMRpcImplementation {
+        @Override
+        public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(
+                final DOMRpcIdentifier rpc, final NormalizedNode<?, ?> input) {
+            return null;
+        }
+    }
+
+    private static class TestLegacyDOMRpcException extends DOMRpcException {
+        private static final long serialVersionUID = 1L;
+
+        TestLegacyDOMRpcException() {
+            super("test");
+        }
+    }
+
+    private static class TestMdsalDOMRpcException extends org.opendaylight.mdsal.dom.api.DOMRpcException {
+        private static final long serialVersionUID = 1L;
+
+        TestMdsalDOMRpcException() {
+            super("test");
+        }
+    }
+}
index a0bf157..f7b960e 100644 (file)
@@ -2,7 +2,7 @@ module odl-datastore-test {
     yang-version 1;
     namespace "urn:opendaylight:params:xml:ns:yang:controller:md:sal:dom:store:test";
     prefix "store-test";
-    
+
     revision "2014-03-13" {
         description "Initial revision.";
     }
@@ -44,4 +44,27 @@ module odl-datastore-test {
 
     container test2 {
     }
+
+    rpc test-rpc {
+        input {
+            leaf input-leaf {
+                type string;
+            }
+        }
+
+        output {
+            leaf output-leaf {
+                type string;
+            }
+        }
+    }
+
+    rpc test-rpc-no-input {
+    }
+
+    notification test-notification {
+        leaf value-leaf {
+            type string;
+        }
+    }
 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.