Fixed bug in cross-broker RPC routing
[controller.git] / opendaylight / md-sal / sal-binding-broker / src / main / java / org / opendaylight / controller / sal / binding / impl / connect / dom / BindingIndependentConnector.java
@@ -1,42 +1,78 @@
 package org.opendaylight.controller.sal.binding.impl.connect.dom;
 
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 
+
 import org.opendaylight.controller.md.sal.common.api.RegistrationListener;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler;
 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandler.DataCommitTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.DataCommitHandlerRegistration;
 import org.opendaylight.controller.md.sal.common.api.data.DataModification;
+import org.opendaylight.controller.md.sal.common.api.routing.RouteChange;
+import org.opendaylight.controller.md.sal.common.api.routing.RouteChangeListener;
+import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry;
 import org.opendaylight.controller.sal.binding.api.data.DataProviderService;
 import org.opendaylight.controller.sal.binding.api.data.RuntimeDataProvider;
+import org.opendaylight.controller.sal.binding.impl.RpcProviderRegistryImpl;
+import org.opendaylight.controller.sal.binding.spi.RpcContextIdentifier;
+import org.opendaylight.controller.sal.binding.spi.RpcRouter;
 import org.opendaylight.controller.sal.common.util.Rpcs;
 import org.opendaylight.controller.sal.core.api.Provider;
 import org.opendaylight.controller.sal.core.api.Broker.ProviderSession;
+import org.opendaylight.controller.sal.core.api.RpcImplementation;
+import org.opendaylight.controller.sal.core.api.RpcProvisionRegistry;
 import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
 import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.concepts.util.ClassLoaderUtils;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
+import org.opendaylight.yangtools.yang.binding.BaseIdentity;
+import org.opendaylight.yangtools.yang.binding.BindingMapping;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.RpcInput;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class BindingIndependentDataServiceConnector implements //
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableSet;
+
+import static com.google.common.base.Preconditions.*;
+import static org.opendaylight.yangtools.concepts.util.ClassLoaderUtils.*;
+
+public class BindingIndependentConnector implements //
         RuntimeDataProvider, //
-        Provider, AutoCloseable {
+        Provider, //
+        AutoCloseable {
 
-    private final Logger LOG = LoggerFactory.getLogger(BindingIndependentDataServiceConnector.class);
+    private final Logger LOG = LoggerFactory.getLogger(BindingIndependentConnector.class);
 
     private static final InstanceIdentifier<? extends DataObject> ROOT = InstanceIdentifier.builder().toInstance();
 
@@ -59,26 +95,41 @@ public class BindingIndependentDataServiceConnector implements //
 
     private Registration<DataCommitHandler<org.opendaylight.yangtools.yang.data.api.InstanceIdentifier, CompositeNode>> biCommitHandlerRegistration;
 
+    private RpcProvisionRegistry biRpcRegistry;
+    private RpcProviderRegistryImpl baRpcRegistry;
+
+    private ListenerRegistration<DomToBindingRpcForwardingManager> domToBindingRpcManager;
+    // private ListenerRegistration<BindingToDomRpcForwardingManager>
+    // bindingToDomRpcManager;
+
+    private Function<InstanceIdentifier<?>, org.opendaylight.yangtools.yang.data.api.InstanceIdentifier> toDOMInstanceIdentifier = new Function<InstanceIdentifier<?>, org.opendaylight.yangtools.yang.data.api.InstanceIdentifier>() {
+
+        @Override
+        public org.opendaylight.yangtools.yang.data.api.InstanceIdentifier apply(InstanceIdentifier<?> input) {
+            return mappingService.toDataDom(input);
+        }
+
+    };
+
     @Override
     public DataObject readOperationalData(InstanceIdentifier<? extends DataObject> path) {
         try {
             org.opendaylight.yangtools.yang.data.api.InstanceIdentifier biPath = mappingService.toDataDom(path);
-            
-            
+
             CompositeNode result = biDataService.readOperationalData(biPath);
             Class<? extends DataObject> targetType = path.getTargetType();
-            
-            if(Augmentation.class.isAssignableFrom(targetType)) {
+
+            if (Augmentation.class.isAssignableFrom(targetType)) {
                 path = mappingService.fromDataDom(biPath);
                 Class<? extends Augmentation<?>> augmentType = (Class<? extends Augmentation<?>>) targetType;
                 DataObject parentTo = mappingService.dataObjectFromDataDom(path, result);
-                if(parentTo instanceof Augmentable<?>) {
+                if (parentTo instanceof Augmentable<?>) {
                     return (DataObject) ((Augmentable) parentTo).getAugmentation(augmentType);
                 }
-                
+
             }
             return mappingService.dataObjectFromDataDom(path, result);
-            
+
         } catch (DeserializationException e) {
             throw new IllegalStateException(e);
         }
@@ -183,11 +234,24 @@ public class BindingIndependentDataServiceConnector implements //
         this.baDataService = baDataService;
     }
 
+    public RpcProviderRegistry getRpcRegistry() {
+        return baRpcRegistry;
+    }
+
+    public void setRpcRegistry(RpcProviderRegistryImpl rpcRegistry) {
+        this.baRpcRegistry = rpcRegistry;
+    }
+
     public void start() {
         baDataService.registerDataReader(ROOT, this);
         baCommitHandlerRegistration = baDataService.registerCommitHandler(ROOT, bindingToDomCommitHandler);
         biCommitHandlerRegistration = biDataService.registerCommitHandler(ROOT_BI, domToBindingCommitHandler);
         baDataService.registerCommitHandlerListener(domToBindingCommitHandler);
+
+        if (baRpcRegistry != null && biRpcRegistry != null) {
+            domToBindingRpcManager = baRpcRegistry.registerRouteChangeListener(new DomToBindingRpcForwardingManager());
+
+        }
     }
 
     public void setMappingService(BindingIndependentMappingService mappingService) {
@@ -205,6 +269,14 @@ public class BindingIndependentDataServiceConnector implements //
         start();
     }
 
+    public <T extends RpcService> void onRpcRouterCreated(Class<T> serviceType, RpcRouter<T> router) {
+
+    }
+
+    public void setDomRpcRegistry(RpcProvisionRegistry registry) {
+        biRpcRegistry = registry;
+    }
+
     @Override
     public void close() throws Exception {
         if (baCommitHandlerRegistration != null) {
@@ -358,4 +430,204 @@ public class BindingIndependentDataServiceConnector implements //
             return forwardedTransaction;
         }
     }
+
+    private class DomToBindingRpcForwardingManager implements
+            RouteChangeListener<RpcContextIdentifier, InstanceIdentifier<?>> {
+
+        private final Map<Class<? extends RpcService>, DomToBindingRpcForwarder> forwarders = new WeakHashMap<>();
+
+        @Override
+        public void onRouteChange(RouteChange<RpcContextIdentifier, InstanceIdentifier<?>> change) {
+            for (Entry<RpcContextIdentifier, Set<InstanceIdentifier<?>>> entry : change.getAnnouncements().entrySet()) {
+                bindingRoutesAdded(entry);
+            }
+        }
+
+        private void bindingRoutesAdded(Entry<RpcContextIdentifier, Set<InstanceIdentifier<?>>> entry) {
+            Class<? extends BaseIdentity> context = entry.getKey().getRoutingContext();
+            Class<? extends RpcService> service = entry.getKey().getRpcService();
+            if (context != null) {
+                getRpcForwarder(service, context).registerPaths(context, service, entry.getValue());
+            }
+        }
+
+        private DomToBindingRpcForwarder getRpcForwarder(Class<? extends RpcService> service,
+                Class<? extends BaseIdentity> context) {
+            DomToBindingRpcForwarder potential = forwarders.get(service);
+            if (potential != null) {
+                return potential;
+            }
+            if (context == null) {
+                potential = new DomToBindingRpcForwarder(service);
+            } else {
+                potential = new DomToBindingRpcForwarder(service, context);
+            }
+            forwarders.put(service, potential);
+            return potential;
+        }
+
+    }
+
+    private class DomToBindingRpcForwarder implements RpcImplementation {
+
+        private final Set<QName> supportedRpcs;
+        private final WeakReference<Class<? extends RpcService>> rpcServiceType;
+        private Set<org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration> registrations;
+
+        public DomToBindingRpcForwarder(Class<? extends RpcService> service) {
+            this.rpcServiceType = new WeakReference<Class<? extends RpcService>>(service);
+            this.supportedRpcs = mappingService.getRpcQNamesFor(service);
+            for (QName rpc : supportedRpcs) {
+                biRpcRegistry.addRpcImplementation(rpc, this);
+            }
+            registrations = ImmutableSet.of();
+        }
+
+        public DomToBindingRpcForwarder(Class<? extends RpcService> service, Class<? extends BaseIdentity> context) {
+            this.rpcServiceType = new WeakReference<Class<? extends RpcService>>(service);
+            this.supportedRpcs = mappingService.getRpcQNamesFor(service);
+            registrations = new HashSet<>();
+            for (QName rpc : supportedRpcs) {
+                registrations.add(biRpcRegistry.addRoutedRpcImplementation(rpc, this));
+            }
+            registrations = ImmutableSet.copyOf(registrations);
+        }
+
+        public void registerPaths(Class<? extends BaseIdentity> context, Class<? extends RpcService> service,
+                Set<InstanceIdentifier<?>> set) {
+            QName ctx = BindingReflections.findQName(context);
+            for (org.opendaylight.yangtools.yang.data.api.InstanceIdentifier path : FluentIterable.from(set).transform(
+                    toDOMInstanceIdentifier)) {
+                for (org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration reg : registrations) {
+                    reg.registerPath(ctx, path);
+                }
+            }
+        }
+
+        public void removePaths(Class<? extends BaseIdentity> context, Class<? extends RpcService> service,
+                Set<InstanceIdentifier<?>> set) {
+            QName ctx = BindingReflections.findQName(context);
+            for (org.opendaylight.yangtools.yang.data.api.InstanceIdentifier path : FluentIterable.from(set).transform(
+                    toDOMInstanceIdentifier)) {
+                for (org.opendaylight.controller.sal.core.api.Broker.RoutedRpcRegistration reg : registrations) {
+                    reg.unregisterPath(ctx, path);
+                }
+            }
+        }
+
+        @Override
+        public Set<QName> getSupportedRpcs() {
+            return supportedRpcs;
+        }
+
+        @Override
+        public RpcResult<CompositeNode> invokeRpc(QName rpc, CompositeNode domInput) {
+            checkArgument(rpc != null);
+            checkArgument(domInput != null);
+
+            Class<? extends RpcService> rpcType = rpcServiceType.get();
+            checkState(rpcType != null);
+            RpcService rpcService = baRpcRegistry.getRpcService(rpcType);
+            checkState(rpcService != null);
+            CompositeNode domUnwrappedInput = domInput.getFirstCompositeByName(QName.create(rpc, "input"));
+            try {
+                return resolveInvocationStrategy(rpc, rpcType).invokeOn(rpcService, domUnwrappedInput);
+            } catch (Exception e) {
+                throw new IllegalStateException(e);
+            }
+        }
+
+        private RpcInvocationStrategy resolveInvocationStrategy(final QName rpc,
+                final Class<? extends RpcService> rpcType) throws Exception {
+            return ClassLoaderUtils.withClassLoader(rpcType.getClassLoader(), new Callable<RpcInvocationStrategy>() {
+                @Override
+                public RpcInvocationStrategy call() throws Exception {
+                    String methodName = BindingMapping.getMethodName(rpc);
+                    Method targetMethod = null;
+                    for (Method possibleMethod : rpcType.getMethods()) {
+                        if (possibleMethod.getName().equals(methodName)
+                                && BindingReflections.isRpcMethod(possibleMethod)) {
+                            targetMethod = possibleMethod;
+                            break;
+                        }
+                    }
+                    checkState(targetMethod != null, "Rpc method not found");
+                    Optional<Class<?>> outputClass = BindingReflections.resolveRpcOutputClass(targetMethod);
+                    Optional<Class<? extends DataContainer>> inputClass = BindingReflections
+                            .resolveRpcInputClass(targetMethod);
+
+                    RpcInvocationStrategy strategy = null;
+                    if (outputClass.isPresent()) {
+                        if (inputClass.isPresent()) {
+                            strategy = new DefaultInvocationStrategy(targetMethod, outputClass.get(), inputClass.get());
+                        } else {
+                            strategy = new NoInputNoOutputInvocationStrategy(targetMethod);
+                        }
+                    } else {
+                        strategy = null;
+                    }
+                    return strategy;
+                }
+
+            });
+        }
+    }
+
+    private abstract class RpcInvocationStrategy {
+
+        protected final Method targetMethod;
+
+        public RpcInvocationStrategy(Method targetMethod) {
+            this.targetMethod = targetMethod;
+        }
+
+        public abstract RpcResult<CompositeNode> uncheckedInvoke(RpcService rpcService, CompositeNode domInput)
+                throws Exception;
+
+        public RpcResult<CompositeNode> invokeOn(RpcService rpcService, CompositeNode domInput) throws Exception {
+            return uncheckedInvoke(rpcService, domInput);
+        }
+    }
+
+    private class DefaultInvocationStrategy extends RpcInvocationStrategy {
+
+        @SuppressWarnings("rawtypes")
+        private WeakReference<Class> inputClass;
+
+        @SuppressWarnings("rawtypes")
+        private WeakReference<Class> outputClass;
+
+        public DefaultInvocationStrategy(Method targetMethod, Class<?> outputClass,
+                Class<? extends DataContainer> inputClass) {
+            super(targetMethod);
+            this.outputClass = new WeakReference(outputClass);
+            this.inputClass = new WeakReference(inputClass);
+        }
+
+        @Override
+        public RpcResult<CompositeNode> uncheckedInvoke(RpcService rpcService, CompositeNode domInput) throws Exception {
+            DataContainer bindingInput = mappingService.dataObjectFromDataDom(inputClass.get(), domInput);
+            Future<RpcResult<?>> result = (Future<RpcResult<?>>) targetMethod.invoke(rpcService, bindingInput);
+            if (result == null) {
+                return Rpcs.getRpcResult(false);
+            }
+            RpcResult<?> bindingResult = result.get();
+            return Rpcs.getRpcResult(true);
+        }
+
+    }
+
+    private class NoInputNoOutputInvocationStrategy extends RpcInvocationStrategy {
+
+        public NoInputNoOutputInvocationStrategy(Method targetMethod) {
+            super(targetMethod);
+        }
+
+        public RpcResult<CompositeNode> uncheckedInvoke(RpcService rpcService, CompositeNode domInput) throws Exception {
+            Future<RpcResult<Void>> result = (Future<RpcResult<Void>>) targetMethod.invoke(rpcService);
+            RpcResult<Void> bindingResult = result.get();
+            return Rpcs.getRpcResult(bindingResult.isSuccessful(), bindingResult.getErrors());
+        }
+
+    }
 }