<!-- Binding Aware -->
<module>sal-binding-api</module>
<module>sal-binding-broker</module>
+ <module>sal-binding-it</module>
<!-- Samples -->
<module>samples</module>
* @return Instance of RpcService of provided serviceType which implements
* also {@link RpcRouter}<T> and {@link DelegateProxy}
*/
- <T extends RpcService> T getRouterFor(Class<T> serviceType) throws IllegalArgumentException;
+ <T extends RpcService> RpcRouter<T> getRouterFor(Class<T> serviceType) throws IllegalArgumentException;
}
val Class<? extends BaseIdentity> context;
@Property
val CtMethod getter;
+
+ @Property
+ val boolean encapsulated;
}
--- /dev/null
+package org.opendaylight.controller.sal.binding.codegen.impl
+
+import org.opendaylight.yangtools.yang.binding.RpcService
+import org.opendaylight.controller.sal.binding.spi.RpcRouter
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
+import static extension org.opendaylight.controller.sal.binding.codegen.RuntimeCodeHelper.*
+import java.util.Set
+import java.util.HashMap
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable
+import org.opendaylight.yangtools.yang.binding.DataObject
+import static org.opendaylight.controller.sal.binding.codegen.impl.XtendHelper.*
+
+class RpcRouterCodegenInstance<T extends RpcService> implements RpcRouter<T> {
+
+ @Property
+ val T invocationProxy
+
+ @Property
+ val Class<T> rpcServiceType
+
+ @Property
+ val Set<Class<? extends BaseIdentity>> contexts
+
+ val routingTables = new HashMap<Class<? extends BaseIdentity>,RpcRoutingTableImpl<? extends BaseIdentity,?>>;
+
+
+
+ @Property
+ var T defaultService
+
+ new(Class<T> type,T routerImpl,Set<Class<? extends BaseIdentity>> contexts) {
+ _rpcServiceType = type
+ _invocationProxy = routerImpl
+ _contexts = contexts
+
+ for(ctx : contexts) {
+ val table = XtendHelper.createRoutingTable(ctx)
+ invocationProxy.setRoutingTable(ctx,table.routes);
+ routingTables.put(ctx,table);
+ }
+ }
+
+ override <C extends BaseIdentity> getRoutingTable(Class<C> table) {
+ routingTables.get(table) as RpcRoutingTable<C,T>
+ }
+
+ override getService(Class<? extends BaseIdentity> context, InstanceIdentifier<?> path) {
+ val table = getRoutingTable(context);
+ return table.getRoute(path);
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.opendaylight.controller.sal.binding.codegen.impl
+
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import org.opendaylight.yangtools.yang.binding.RpcService
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
+import java.util.Map
+import org.opendaylight.yangtools.yang.binding.DataObject
+import java.util.HashMap
+
+class RpcRoutingTableImpl<C extends BaseIdentity,S extends RpcService> implements RpcRoutingTable<C,S>{
+
+ @Property
+ val Class<C> identifier;
+
+ @Property
+ var S defaultRoute;
+
+ @Property
+ val Map<InstanceIdentifier<? extends DataObject>,S> routes;
+
+ new(Class<C> ident, Map<InstanceIdentifier<? extends DataObject>,S> route) {
+ _identifier = ident
+ _routes = route
+ }
+
+ new(Class<C> ident) {
+ _identifier = ident
+ _routes = new HashMap
+ }
+
+
+ override getRoute(InstanceIdentifier nodeInstance) {
+ val ret = routes.get(nodeInstance);
+ if(ret !== null) {
+ return ret;
+ }
+ return defaultRoute;
+ }
+
+ override removeRoute(InstanceIdentifier<? extends Object> path) {
+ routes.remove(path);
+ }
+
+ @SuppressWarnings("rawtypes")
+ override updateRoute(InstanceIdentifier<? extends Object> path, S service) {
+ routes.put(path as InstanceIdentifier,service);
+ }
+}
\ No newline at end of file
import static extension org.opendaylight.controller.sal.binding.codegen.YangtoolsMappingHelper.*
import static extension org.opendaylight.controller.sal.binding.codegen.RuntimeCodeSpecification.*
+import java.util.HashSet
+import java.io.ObjectOutputStream.PutField
+import static org.opendaylight.controller.sal.binding.impl.osgi.ClassLoaderUtils.*
-class RuntimeCodeGenerator {
+class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator {
val ClassPool classPool;
classPool = pool;
}
- def <T extends RpcService> Class<? extends T> generateDirectProxy(Class<T> iface) {
+ override <T extends RpcService> getDirectProxyFor(Class<T> iface) {
val supertype = iface.asCtClass
val targetCls = createClass(iface.directProxyName, supertype) [
field(DELEGATE_FIELD, iface);
body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
]
]
- return targetCls.toClass(iface.classLoader)
+ return targetCls.toClass(iface.classLoader).newInstance as T
}
- def <T extends RpcService> Class<? extends T> generateRouter(Class<T> iface) {
- val supertype = iface.asCtClass
- val targetCls = createClass(iface.routerName, supertype) [
- //field(ROUTING_TABLE_FIELD,Map)
- field(DELEGATE_FIELD, iface)
- val contexts = new HashMap<String, Class<? extends BaseIdentity>>();
- // We search for routing pairs and add fields
- supertype.methods.filter[declaringClass == supertype && parameterTypes.size === 1].forEach [ method |
- val routingPair = method.routingContextInput;
- if (routingPair !== null)
- contexts.put(routingPair.context.routingTableField, routingPair.context);
- ]
- for (ctx : contexts.entrySet) {
- field(ctx.key, Map)
- }
- implementMethodsFrom(supertype) [
- if (parameterTypes.size === 1) {
- val routingPair = routingContextInput;
- val bodyTmp = '''
- {
- final «InstanceIdentifier.name» identifier = $1.«routingPair.getter.name»();
- «supertype.name» instance = («supertype.name») «routingPair.context.routingTableField».get(identifier);
- if(instance == null) {
- instance = «DELEGATE_FIELD»;
- }
- return ($r) instance.«it.name»($$);
- }'''
- body = bodyTmp
- } else if (parameterTypes.size === 0) {
- body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
+ override <T extends RpcService> getRouterFor(Class<T> iface) {
+ val contexts = new HashSet<Class<? extends BaseIdentity>>
+
+ val instance = <T>withClassLoader(iface.classLoader) [ |
+ val supertype = iface.asCtClass
+ val targetCls = createClass(iface.routerName, supertype) [
+ //field(ROUTING_TABLE_FIELD,Map)
+ field(DELEGATE_FIELD, iface)
+ val ctxMap = new HashMap<String, Class<? extends BaseIdentity>>();
+ // We search for routing pairs and add fields
+ supertype.methods.filter[declaringClass == supertype && parameterTypes.size === 1].forEach [ method |
+ val routingPair = method.routingContextInput;
+ if (routingPair !== null) {
+ ctxMap.put(routingPair.context.routingTableField, routingPair.context);
+ contexts.add(routingPair.context)
+ }
+ ]
+ for (ctx : ctxMap.entrySet) {
+ field(ctx.key, Map)
}
+ implementMethodsFrom(supertype) [
+ if (parameterTypes.size === 1) {
+ val routingPair = routingContextInput;
+ val bodyTmp = '''
+ {
+ final «InstanceIdentifier.name» identifier = $1.«routingPair.getter.name»()«IF routingPair.encapsulated».getValue()«ENDIF»;
+ «supertype.name» instance = («supertype.name») «routingPair.context.routingTableField».get(identifier);
+ if(instance == null) {
+ instance = «DELEGATE_FIELD»;
+ }
+ if(instance == null) {
+ throw new java.lang.IllegalStateException("No provider is processing supplied message");
+ }
+ return ($r) instance.«it.name»($$);
+ }'''
+ body = bodyTmp
+ } else if (parameterTypes.size === 0) {
+ body = '''return ($r) «DELEGATE_FIELD».«it.name»($$);'''
+ }
+ ]
]
- ]
- return targetCls.toClass(iface.classLoader)
+ return targetCls.toClass(iface.classLoader).newInstance as T
+ ];
+ return new RpcRouterCodegenInstance(iface, instance, contexts);
}
def Class<?> generateListenerInvoker(Class<? extends NotificationListener> iface) {
private def RoutingPair getContextInstance(CtClass dataClass) {
for (method : dataClass.methods) {
- if (method.parameterTypes.size === 0 && method.name.startsWith("get")) {
+ if (method.name.startsWith("get") && method.parameterTypes.size === 0) {
for (annotation : method.availableAnnotations) {
if (annotation instanceof RoutingContext) {
- return new RoutingPair((annotation as RoutingContext).value, method)
+ val encapsulated = !method.returnType.equals(InstanceIdentifier.asCtClass);
+
+ return new RoutingPair((annotation as RoutingContext).value, method,encapsulated);
}
}
}
}
for (iface : dataClass.interfaces) {
val ret = getContextInstance(iface);
- if (ret != null) return ret;
+ if(ret != null) return ret;
}
return null;
}
--- /dev/null
+package org.opendaylight.controller.sal.binding.codegen.impl;
+
+import org.opendaylight.yangtools.yang.binding.BaseIdentity;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+public class XtendHelper {
+
+ public static <C extends BaseIdentity> RpcRoutingTableImpl createRoutingTable(
+ Class<C> cls) {
+ return new RpcRoutingTableImpl<>(cls);
+ }
+
+ public static String foo() {
+ return "Foo";
+ }
+}
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration
import org.opendaylight.controller.sal.binding.api.data.DataProviderService
import org.opendaylight.controller.sal.binding.api.data.DataBrokerService
+import org.opendaylight.controller.sal.binding.spi.RpcRouter
+import java.util.concurrent.ConcurrentHashMap
+import static com.google.common.base.Preconditions.*
+import org.opendaylight.yangtools.concepts.AbstractObjectRegistration
+import org.opendaylight.yangtools.yang.binding.BaseIdentity
+import com.google.common.collect.Multimap
+import com.google.common.collect.HashMultimap
+import static org.opendaylight.controller.sal.binding.impl.osgi.ClassLoaderUtils.*
class BindingAwareBrokerImpl implements BindingAwareBroker {
private static val log = LoggerFactory.getLogger(BindingAwareBrokerImpl)
private val clsPool = ClassPool.getDefault()
private var RuntimeCodeGenerator generator;
- private Map<Class<? extends RpcService>, RpcProxyContext> managedProxies = new HashMap();
+
+ /**
+ * Map of all Managed Direct Proxies
+ *
+ */
+ private val Map<Class<? extends RpcService>, RpcProxyContext> managedProxies = new ConcurrentHashMap();
+
+ /**
+ *
+ * Map of all available Rpc Routers
+ *
+ *
+ */
+ private val Map<Class<? extends RpcService>, RpcRouter<? extends RpcService>> rpcRouters = new ConcurrentHashMap();
+
private var NotificationBrokerImpl notifyBroker
private var DataBrokerImpl dataBroker
private var ServiceRegistration<NotificationProviderService> notifyBrokerRegistration
notifyBrokerRegistration = brokerBundleContext.registerService(NotificationProviderService, notifyBroker,
brokerProperties)
brokerBundleContext.registerService(NotificationService, notifyBroker, brokerProperties)
- brokerBundleContext.registerService(DataProviderService,dataBroker,brokerProperties)
- brokerBundleContext.registerService(DataBrokerService,dataBroker,brokerProperties)
-
+ brokerBundleContext.registerService(DataProviderService, dataBroker, brokerProperties)
+ brokerBundleContext.registerService(DataBrokerService, dataBroker, brokerProperties)
+
}
def initGenerator() {
*
* If proxy class does not exist for supplied service class it will be generated automatically.
*/
- def <T extends RpcService> getManagedDirectProxy(Class<T> service) {
+ private def <T extends RpcService> getManagedDirectProxy(Class<T> service) {
var RpcProxyContext existing = null
- if((existing = managedProxies.get(service)) != null) {
+ if ((existing = managedProxies.get(service)) != null) {
return existing.proxy
}
- val proxyClass = generator.generateDirectProxy(service)
- val rpcProxyCtx = new RpcProxyContext(proxyClass)
+ val proxyInstance = generator.getDirectProxyFor(service)
+ val rpcProxyCtx = new RpcProxyContext(proxyInstance.class)
val properties = new Hashtable<String, String>()
- rpcProxyCtx.proxy = proxyClass.newInstance as RpcService
+ rpcProxyCtx.proxy = proxyInstance as RpcService
properties.salServiceType = SAL_SERVICE_TYPE_CONSUMER_PROXY
rpcProxyCtx.registration = brokerBundleContext.registerService(service, rpcProxyCtx.proxy as T, properties)
def <T extends RpcService> registerRpcImplementation(Class<T> type, T service, OsgiProviderContext context,
Hashtable<String, String> properties) {
val proxy = getManagedDirectProxy(type)
- if(proxy.delegate != null) {
- throw new IllegalStateException("Service " + type + "is already registered");
- }
+ checkState(proxy.delegate === null, "The Service for type {} is already registered", type)
+
val osgiReg = context.bundleContext.registerService(type, service, properties);
proxy.delegate = service;
return new RpcServiceRegistrationImpl<T>(type, service, osgiReg);
}
- def <T extends RpcService> RpcRegistration<T> registerMountedRpcImplementation(Class<T> tyoe, T service, InstanceIdentifier<?> identifier,
- OsgiProviderContext context, Hashtable<String, String> properties) {
+ def <T extends RpcService> RpcRegistration<T> registerMountedRpcImplementation(Class<T> type, T service,
+ InstanceIdentifier<?> identifier, OsgiProviderContext context) {
throw new UnsupportedOperationException("TODO: auto-generated method stub")
}
- def <T extends RpcService> RoutedRpcRegistration<T> registerRoutedRpcImplementation(Class<T> type, T service, OsgiProviderContext context,
- Hashtable<String, String> properties) {
- throw new UnsupportedOperationException("TODO: auto-generated method stub")
+ def <T extends RpcService> RoutedRpcRegistration<T> registerRoutedRpcImplementation(Class<T> type, T service,
+ OsgiProviderContext context) {
+ val router = resolveRpcRouter(type);
+ checkState(router !== null)
+ return new RoutedRpcRegistrationImpl<T>(service, router, this)
+ }
+
+ private def <T extends RpcService> RpcRouter<T> resolveRpcRouter(Class<T> type) {
+
+ val router = rpcRouters.get(type);
+ if (router !== null) {
+ return router as RpcRouter<T>;
+ }
+
+ // We created Router
+ val newRouter = generator.getRouterFor(type);
+ checkState(newRouter !== null);
+ rpcRouters.put(type, newRouter);
+
+ // We create / update Direct Proxy for router
+ val proxy = getManagedDirectProxy(type);
+ proxy.delegate = newRouter.invocationProxy
+ return newRouter;
+
+ }
+
+ protected def <T extends RpcService> void registerPath(RoutedRpcRegistrationImpl<T> registration,
+ Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+
+ val router = registration.router;
+ val paths = registration.registeredPaths;
+
+ val routingTable = router.getRoutingTable(context)
+ checkState(routingTable != null);
+
+ // Updating internal structure of registration
+ routingTable.updateRoute(path, registration.instance)
+ val success = paths.put(context, path);
+ }
+
+ protected def <T extends RpcService> void unregisterPath(RoutedRpcRegistrationImpl<T> registration,
+ Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+
+ val router = registration.router;
+ val paths = registration.registeredPaths;
+
+ val routingTable = router.getRoutingTable(context)
+ checkState(routingTable != null);
+
+ // Updating internal structure of registration
+ val target = routingTable.getRoute(path)
+ checkState(target === registration.instance)
+ routingTable.removeRoute(path)
+ checkState(paths.remove(context, path));
+ }
+
+ protected def <T extends RpcService> void unregisterRoutedRpcService(RoutedRpcRegistrationImpl<T> registration) {
+
+ val router = registration.router;
+ val paths = registration.registeredPaths;
+
+ for (ctxMap : registration.registeredPaths.entries) {
+ val context = ctxMap.key
+ val routingTable = router.getRoutingTable(context)
+ val path = ctxMap.value
+ routingTable.removeRoute(path)
+
+ }
+ }
+}
+
+class RoutedRpcRegistrationImpl<T extends RpcService> extends AbstractObjectRegistration<T> implements RoutedRpcRegistration<T> {
+
+ @Property
+ private val BindingAwareBrokerImpl broker;
+
+ @Property
+ private val RpcRouter<T> router;
+
+ @Property
+ private val Multimap<Class<? extends BaseIdentity>, InstanceIdentifier<?>> registeredPaths = HashMultimap.create();
+
+ private var closed = false;
+
+ new(T instance, RpcRouter<T> backingRouter, BindingAwareBrokerImpl broker) {
+ super(instance)
+ _router = backingRouter;
+ _broker = broker;
+ }
+
+ override protected removeRegistration() {
+ closed = true
+ broker.unregisterRoutedRpcService(this)
+ }
+
+ override registerInstance(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> instance) {
+ registerPath(context, instance);
+ }
+
+ override unregisterInstance(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> instance) {
+ unregisterPath(context, instance);
+ }
+
+ override getService() {
+ return instance;
+ }
+
+ override registerPath(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+ checkClosed()
+ broker.registerPath(this, context, path);
+ }
+
+ override unregisterPath(Class<? extends BaseIdentity> context, InstanceIdentifier<? extends Object> path) {
+ checkClosed()
+ broker.unregisterPath(this, context, path);
+ }
+
+ private def checkClosed() {
+ if (closed)
+ throw new IllegalStateException("Registration was closed.");
}
}
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier
import org.opendaylight.controller.sal.binding.api.BindingAwareProvider.ProviderFunctionality
+import static com.google.common.base.Preconditions.*
class OsgiProviderContext extends OsgiConsumerContext implements ProviderContext {
}
override <T extends RpcService> addMountRpcImplementation(Class<T> type, InstanceIdentifier<?> mount, T implementation) throws IllegalStateException {
+ checkNotNull(type, "Service type should not be null")
+ checkNotNull(mount,"Path to the mount should not be null")
+ checkNotNull(implementation, "Service instance should not be null")
val properties = new Hashtable<String, String>();
properties.salServiceType = SAL_SERVICE_TYPE_PROVIDER
// Fill requirements
- val salReg = broker.registerMountedRpcImplementation(type, implementation, mount, this, properties)
+ val salReg = broker.registerMountedRpcImplementation(type, implementation, mount, this)
registeredServices.put(type, salReg)
return salReg;
}
override <T extends RpcService> addRoutedRpcImplementation(Class<T> type, T implementation) throws IllegalStateException {
- val properties = new Hashtable<String, String>();
- properties.salServiceType = SAL_SERVICE_TYPE_PROVIDER
-
- // Fill requirements
- val salReg = broker.registerRoutedRpcImplementation(type, implementation, this, properties)
+ checkNotNull(type, "Service type should not be null")
+ checkNotNull(implementation, "Service type should not be null")
+
+ val salReg = broker.registerRoutedRpcImplementation(type, implementation, this)
registeredServices.put(type, salReg)
return salReg;
}
override registerFunctionality(ProviderFunctionality functionality) {
-
+ // NOOP for now
}
override unregisterFunctionality(ProviderFunctionality functionality) {
+ // NOOP for now
}
}
--- /dev/null
+package org.opendaylight.controller.sal.binding.impl.osgi;
+
+
+
+import java.util.concurrent.Callable;
+import static com.google.common.base.Preconditions.*;
+
+public class ClassLoaderUtils {
+
+ public static <V> V withClassLoader(ClassLoader cls,Callable<V> function) throws Exception {
+ checkNotNull(cls);
+ checkNotNull(function);
+ ClassLoader oldCls = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(cls);
+ V result = function.call();
+ Thread.currentThread().setContextClassLoader(oldCls);
+ return result;
+ } catch (Exception e) {
+ Thread.currentThread().setContextClassLoader(oldCls);
+ throw new Exception(e);
+ }
+ }
+}
\ No newline at end of file
*/
package org.opendaylight.controller.sal.binding.spi;
+import java.util.Set;
+
import org.opendaylight.yangtools.yang.binding.BaseIdentity;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.binding.RpcService;
* @return type of RpcService which is served by this instance of router.
*/
Class<T> getRpcServiceType();
+
+
+ /**
+ * Returns a instance of T which is associated with this router instance
+ * and routes messages based on routing tables.
+ *
+ * @return type of RpcService which is served by this instance of router.
+ */
+ T getInvocationProxy();
+
/**
* Returns a routing table for particular route context
/**
*
*/
- void setDefaultService();
+ void setDefaultService(T service);
+
+ Set<Class<? extends BaseIdentity>> getContexts();
}
import static org.opendaylight.controller.sal.binding.codegen.RuntimeCodeHelper.*;
import org.opendaylight.controller.sal.binding.codegen.impl.RuntimeCodeGenerator;
+import org.opendaylight.controller.sal.binding.spi.RpcRouter;
+import org.opendaylight.controller.sal.binding.spi.RpcRoutingTable;
import org.opendaylight.controller.sal.binding.test.mock.FooService;
import org.opendaylight.controller.sal.binding.test.mock.ReferencableObject;
import org.opendaylight.controller.sal.binding.test.mock.ReferencableObjectKey;
@Test
public void testGenerateDirectProxy() {
- Class<? extends FooService> product = codeGenerator.generateDirectProxy(FooService.class);
+ FooService product = codeGenerator.getDirectProxyFor(FooService.class);
assertNotNull(product);
}
@Test
public void testGenerateRouter() throws Exception {
- Class<? extends FooService> product = codeGenerator.generateRouter(FooService.class);
+ RpcRouter<FooService> product = codeGenerator.getRouterFor(FooService.class);
assertNotNull(product);
- assertNotNull(product.getSimpleName());
- assertEquals("2 fields should be generated.",2,product.getFields().length);
+ assertNotNull(product.getInvocationProxy());
- verifyRouting(product.newInstance());
+ assertEquals("2 fields should be generated.",2,product.getInvocationProxy().getClass().getFields().length);
+
+ verifyRouting(product);
}
- private void verifyRouting(FooService product) {
- Map<InstanceIdentifier<? extends Object>,FooService> routingTable = new HashMap<>();
- setRoutingTable(product, BaseIdentity.class, routingTable);
+ private void verifyRouting(RpcRouter<FooService> product) {
+ assertNotNull("Routing table should be initialized",product.getRoutingTable(BaseIdentity.class));
- assertSame("Returned routing table should be same instance",routingTable,getRoutingTable(product, BaseIdentity.class));
+ RpcRoutingTable<BaseIdentity, FooService> routingTable = product.getRoutingTable(BaseIdentity.class);
int servicesCount = 2;
int instancesPerService = 3;
for(int i = 0;i<service.length;i++) {
for (InstanceIdentifier<?> instance : identifiers[i]) {
- routingTable.put(instance, service[i]);
+ routingTable.updateRoute(instance, service[i]);
}
}
- assertEquals("All instances should be registered.", servicesCount*instancesPerService, routingTable.size());
+ assertEquals("All instances should be registered.", servicesCount*instancesPerService, routingTable.getRoutes().size());
SimpleInput[] instance_0_input = new SimpleInputImpl[] {
new SimpleInputImpl(identifiers[0][0]),
// We test sending mock messages
- product.simple(instance_0_input[0]);
+ product.getInvocationProxy().simple(instance_0_input[0]);
verify(service[0]).simple(instance_0_input[0]);
- product.simple(instance_0_input[1]);
- product.simple(instance_0_input[2]);
+ product.getInvocationProxy().simple(instance_0_input[1]);
+ product.getInvocationProxy().simple(instance_0_input[2]);
verify(service[0]).simple(instance_0_input[1]);
verify(service[0]).simple(instance_0_input[2]);
- product.simple(instance_1_input[0]);
+
+ product.getInvocationProxy().simple(instance_1_input[0]);
+
+ // We should have call to instance 1
verify(service[1]).simple(instance_1_input[0]);
}
--- /dev/null
+<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>
+ <artifactId>sal-parent</artifactId>
+ <groupId>org.opendaylight.controller</groupId>
+ <version>1.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>sal-binding-it</artifactId>
+ <scm>
+ <connection>scm:git:ssh://git.opendaylight.org:29418/controller.git</connection>
+ <developerConnection>scm:git:ssh://git.opendaylight.org:29418/controller.git</developerConnection>
+ <url>https://wiki.opendaylight.org/view/OpenDaylight_Controller:MD-SAL</url>
+ </scm>
+
+ <properties>
+ <exam.version>3.0.0</exam.version>
+ <url.version>1.5.0</url.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>maven-paxexam-plugin</artifactId>
+ <version>1.2.4</version>
+ <executions>
+ <execution>
+ <id>generate-config</id>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings
+ only. It has no influence on the Maven build itself. -->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>
+ org.ops4j.pax.exam
+ </groupId>
+ <artifactId>
+ maven-paxexam-plugin
+ </artifactId>
+ <versionRange>
+ [1.2.4,)
+ </versionRange>
+ <goals>
+ <goal>
+ generate-depends-file
+ </goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.yangtools.thirdparty</groupId>
+ <artifactId>xtend-lib-osgi</artifactId>
+ <version>2.4.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal-binding-broker-impl</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-container-native</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-junit4</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.ops4j.pax.exam</groupId>
+ <artifactId>pax-exam-link-mvn</artifactId>
+ <version>${exam.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>equinoxSDK381</groupId>
+ <artifactId>org.eclipse.osgi</artifactId>
+ <version>3.8.1.v20120830-144521</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>log4j-over-slf4j</artifactId>
+ <version>1.7.2</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <version>1.0.9</version>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <version>1.0.9</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller.model</groupId>
+ <artifactId>model-flow-service</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.opendaylight.controller.test.sal.binding.it;
+
+import static org.ops4j.pax.exam.CoreOptions.*;
+
+import javax.inject.Inject;
+
+import org.junit.runner.RunWith;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.options.DefaultCompositeOption;
+import org.osgi.framework.BundleContext;
+
+@RunWith(PaxExam.class)
+public abstract class AbstractTest {
+
+ public static final String CONTROLLER = "org.opendaylight.controller";
+ public static final String YANGTOOLS = "org.opendaylight.yangtools";
+
+ public static final String CONTROLLER_MODELS = "org.opendaylight.controller.model";
+ public static final String YANGTOOLS_MODELS = "org.opendaylight.yangtools.model";
+
+
+ @Inject
+ BindingAwareBroker broker;
+
+ @Inject
+ BundleContext bundleContext;
+
+
+
+ public BindingAwareBroker getBroker() {
+ return broker;
+ }
+
+
+
+ public void setBroker(BindingAwareBroker broker) {
+ this.broker = broker;
+ }
+
+
+
+ public BundleContext getBundleContext() {
+ return bundleContext;
+ }
+
+
+
+ public void setBundleContext(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+
+
+ @Configuration
+ public Option[] config() {
+ return options(systemProperty("osgi.console").value("2401"),
+ mavenBundle("org.slf4j", "slf4j-api").versionAsInProject(), //
+ mavenBundle("org.slf4j", "log4j-over-slf4j").versionAsInProject(), //
+ mavenBundle("ch.qos.logback", "logback-core").versionAsInProject(), //
+ mavenBundle("ch.qos.logback", "logback-classic").versionAsInProject(), //
+
+ // MD-SAL Dependencies
+ mavenBundle(YANGTOOLS, "concepts").versionAsInProject(),
+ mavenBundle(YANGTOOLS, "yang-binding").versionAsInProject(), //
+ mavenBundle(YANGTOOLS, "yang-common").versionAsInProject(), //
+
+ mavenBundle(YANGTOOLS+".thirdparty", "xtend-lib-osgi").versionAsInProject(),
+ mavenBundle("com.google.guava", "guava").versionAsInProject(), //
+ mavenBundle("org.javassist", "javassist").versionAsInProject(),
+
+ // MD SAL
+ mavenBundle(CONTROLLER, "sal-binding-api").versionAsInProject(), //
+ mavenBundle(CONTROLLER, "sal-binding-broker-impl").versionAsInProject(), //
+ mavenBundle(CONTROLLER, "sal-common").versionAsInProject(), //
+ mavenBundle(CONTROLLER, "sal-common-api").versionAsInProject(),
+ mavenBundle(CONTROLLER, "sal-common-util").versionAsInProject(), //
+
+ // BASE Models
+ mavenBundle(YANGTOOLS_MODELS,"yang-ext").versionAsInProject(),
+ mavenBundle(YANGTOOLS_MODELS,"ietf-inet-types").versionAsInProject(),
+ mavenBundle(YANGTOOLS_MODELS,"ietf-yang-types").versionAsInProject(),
+ mavenBundle(YANGTOOLS_MODELS,"opendaylight-l2-types").versionAsInProject(),
+ mavenBundle(CONTROLLER_MODELS,"model-flow-base").versionAsInProject(),
+ mavenBundle(CONTROLLER_MODELS,"model-flow-service").versionAsInProject(),
+ mavenBundle(CONTROLLER_MODELS,"model-inventory").versionAsInProject(),
+ junitAndMockitoBundles()
+ );
+ }
+
+
+ public static Option junitAndMockitoBundles() {
+ return new DefaultCompositeOption(
+ // Repository required to load harmcrest (OSGi-fied version).
+ repository("http://repository.springsource.com/maven/bundles/external").id(
+ "com.springsource.repository.bundles.external"),
+
+ // Mockito without Hamcrest and Objenesis
+ mavenBundle("org.mockito", "mockito-all", "1.9.5"),
+
+ // Hamcrest with a version matching the range expected by Mockito
+ //mavenBundle("org.hamcrest", "com.springsource.org.hamcrest.core", "1.1.0"),
+
+ // Objenesis with a version matching the range expected by Mockito
+ //wrappedBundle(mavenBundle("org.objenesis", "objenesis", "1.2"))
+ // .exports("*;version=1.2"),
+
+ // The default JUnit bundle also exports Hamcrest, but with an (incorrect) version of
+ // 4.9 which does not match the Mockito import. When deployed after the hamcrest bundles, it gets
+ // resolved correctly.
+ junitBundles(),
+
+ /*
+ * Felix has implicit boot delegation enabled by default. It conflicts with Mockito:
+ * java.lang.LinkageError: loader constraint violation in interface itable initialization:
+ * when resolving method "org.osgi.service.useradmin.User$$EnhancerByMockitoWithCGLIB$$dd2f81dc
+ * .newInstance(Lorg/mockito/cglib/proxy/Callback;)Ljava/lang/Object;" the class loader
+ * (instance of org/mockito/internal/creation/jmock/SearchingClassLoader) of the current class,
+ * org/osgi/service/useradmin/User$$EnhancerByMockitoWithCGLIB$$dd2f81dc, and the class loader
+ * (instance of org/apache/felix/framework/BundleWiringImpl$BundleClassLoaderJava5) for interface
+ * org/mockito/cglib/proxy/Factory have different Class objects for the type org/mockito/cglib/
+ * proxy/Callback used in the signature
+ *
+ * So we disable the bootdelegation. this property has no effect on the other OSGi implementation.
+ */
+ frameworkProperty("felix.bootdelegation.implicit").value("false")
+ );
+ }
+}
--- /dev/null
+package org.opendaylight.controller.test.sal.binding.it;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+public abstract class AbstractTestProvider implements BindingAwareProvider {
+
+ @Override
+ public Collection<? extends RpcService> getImplementations() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Collection<? extends ProviderFunctionality> getFunctionality() {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void onSessionInitialized(ConsumerContext session) {
+ // Noop
+
+ }
+
+}
--- /dev/null
+package org.opendaylight.controller.test.sal.binding.it;
+
+import static org.junit.Assert.*;
+
+import java.math.BigInteger;
+import java.util.Collection;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ConsumerContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
+import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RoutedRpcRegistration;
+import org.opendaylight.controller.sal.binding.api.BindingAwareConsumer;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider.ProviderFunctionality;
+import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeContext;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.RpcService;
+
+import com.google.inject.Inject;
+
+import static org.mockito.Mockito.*;
+
+public class RoutedServiceTest extends AbstractTest {
+
+ private SalFlowService first;
+ private SalFlowService second;
+
+ private SalFlowService consumerService;
+
+ private RoutedRpcRegistration<SalFlowService> firstReg;
+ private RoutedRpcRegistration<SalFlowService> secondReg;
+
+ @Before
+ public void setUp() throws Exception {
+ first = mock(SalFlowService.class,"First Flow Service");
+ second = mock(SalFlowService.class,"Second Flow Service");
+ }
+
+ @Test
+ public void testServiceRegistration() {
+
+ assertNotNull(getBroker());
+
+ BindingAwareProvider provider1 = new AbstractTestProvider() {
+
+ @Override
+ public void onSessionInitiated(ProviderContext session) {
+ assertNotNull(session);
+ firstReg = session.addRoutedRpcImplementation(SalFlowService.class, first);
+ }
+ };
+
+ /**
+ * Register first provider, which register first implementation of
+ * SalFlowService
+ *
+ */
+ getBroker().registerProvider(provider1, getBundleContext());
+ assertNotNull("Registration should not be null", firstReg);
+ assertSame(first, firstReg.getInstance());
+
+ BindingAwareProvider provider2 = new AbstractTestProvider() {
+
+ @Override
+ public void onSessionInitiated(ProviderContext session) {
+ assertNotNull(session);
+ secondReg = session.addRoutedRpcImplementation(SalFlowService.class, second);
+ }
+ };
+ getBroker().registerProvider(provider2, getBundleContext());
+ assertNotNull("Registration should not be null", firstReg);
+ assertNotSame(secondReg, firstReg);
+
+
+ BindingAwareConsumer consumer = new BindingAwareConsumer() {
+ @Override
+ public void onSessionInitialized(ConsumerContext session) {
+ consumerService = session.getRpcService(SalFlowService.class);
+ }
+ };
+ broker.registerConsumer(consumer, getBundleContext());
+
+ assertNotNull("MD-SAL instance of Flow Service should be returned", consumerService);
+ assertNotSame("Provider instance and consumer instance should not be same.", first, consumerService);
+
+ NodeRef nodeOne = createNodeRef("foo:node:1");
+
+
+ /**
+ * Provider 1 - register itself as provider for SalFlowService
+ * for nodeOne
+ */
+ firstReg.registerPath(NodeContext.class, nodeOne.getValue());
+
+ /**
+ * Consumer creates addFlow Message for node one and sends
+ * it to the MD-SAL
+ *
+ */
+ AddFlowInput firstMessage = createSampleAddFlow(nodeOne,1);
+ consumerService.addFlow(firstMessage);
+
+ /**
+ * We verify if implementation of first provider received same
+ * message from MD-SAL.
+ *
+ */
+ verify(first).addFlow(firstMessage);
+
+ /**
+ * Verifies that second instance was not invoked with first
+ * message
+ *
+ */
+ verify(second, times(0)).addFlow(firstMessage);
+
+ /**
+ * Second provider registers as provider for nodeTwo
+ *
+ */
+ NodeRef nodeTwo = createNodeRef("foo:node:2");
+ secondReg.registerPath(NodeContext.class, nodeTwo.getValue());
+
+
+ /**
+ * Consumer sends message to nodeTwo for three times.
+ * Should be processed by second instance.
+ */
+ AddFlowInput secondMessage = createSampleAddFlow(nodeTwo,2);
+ consumerService.addFlow(secondMessage);
+ consumerService.addFlow(secondMessage);
+ consumerService.addFlow(secondMessage);
+
+ /**
+ * We verify that second was invoked 3 times, with message
+ * two as parameter, first was invoked 0 times.
+ *
+ */
+ verify(second, times(3)).addFlow(secondMessage);
+ verify(first, times(0)).addFlow(secondMessage);
+
+
+ /**
+ * First provider unregisters as implementation of FlowService
+ * for node one
+ *
+ */
+ firstReg.unregisterPath(NodeContext.class, nodeOne.getValue());
+
+
+ /**
+ * Second provider registers as implementation for FlowService
+ * for node one
+ *
+ */
+ secondReg.registerPath(NodeContext.class, nodeOne.getValue());
+
+ /**
+ * Consumer sends third message to Node 1, should be processed
+ * by second instance.
+ *
+ */
+ AddFlowInput thirdMessage = createSampleAddFlow(nodeOne,3);
+ consumerService.addFlow(thirdMessage);
+
+ /**
+ * We verify that first provider was invoked 0 times,
+ * second provider 1 time.
+ */
+ verify(first,times(0)).addFlow(thirdMessage);
+ verify(second).addFlow(thirdMessage);
+
+ }
+
+ private static NodeRef createNodeRef(String string) {
+ NodeKey key = new NodeKey(new NodeId(string));
+ InstanceIdentifier<Node> path = InstanceIdentifier.builder().node(Nodes.class).node(Node.class, key)
+ .toInstance();
+
+ return new NodeRef(path);
+ }
+
+ static AddFlowInput createSampleAddFlow(NodeRef node,int cookie) {
+ AddFlowInputBuilder ret = new AddFlowInputBuilder();
+ ret.setNode(node);
+ ret.setCookie(BigInteger.valueOf(cookie));
+ return ret.build();
+ }
+}