From 35000765cff05e4f59541bc497dfe253a3188644 Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Thu, 5 Jun 2014 14:15:44 +0200 Subject: [PATCH] BUG-614: introduce AbstractRuntimeCodeGenerator Introduce AbstractRuntimeCodeGenerator, which is the base service class for interactions with the rest of the world. It implements the public contracts and as much of the logic as possible in pure Java and defines a clear interface subclasses need to provide. RuntimeCodeGenerator.xtend then adds an implementation of it. Change-Id: Ic04f3e840825ae1f59583c0a9a2f6fbfc7bb66f6 Signed-off-by: Robert Varga --- .../impl/AbstractRuntimeCodeGenerator.java | 187 ++++++++++++++++++ .../impl/RpcRouterCodegenInstance.java | 28 ++- .../codegen/impl/RpcServiceMetadata.java | 28 +-- .../codegen/impl/RuntimeCodeGenerator.xtend | 124 ++---------- 4 files changed, 231 insertions(+), 136 deletions(-) create mode 100644 opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/AbstractRuntimeCodeGenerator.java diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/AbstractRuntimeCodeGenerator.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/AbstractRuntimeCodeGenerator.java new file mode 100644 index 0000000000..9605a4d372 --- /dev/null +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/AbstractRuntimeCodeGenerator.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.binding.codegen.impl; + +import java.util.Map; +import java.util.WeakHashMap; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import javax.annotation.concurrent.GuardedBy; + +import org.eclipse.xtext.xbase.lib.Extension; +import org.opendaylight.controller.sal.binding.api.rpc.RpcRouter; +import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory; +import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; +import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; +import org.opendaylight.yangtools.yang.binding.NotificationListener; +import org.opendaylight.yangtools.yang.binding.RpcService; +import org.opendaylight.yangtools.yang.binding.annotations.RoutingContext; +import org.opendaylight.yangtools.yang.binding.util.ClassLoaderUtils; + +import com.google.common.base.Supplier; + +abstract class AbstractRuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator, NotificationInvokerFactory { + @GuardedBy("this") + private final Map, RuntimeGeneratedInvokerPrototype> invokerClasses = new WeakHashMap<>(); + private final CtClass brokerNotificationListener; + + @Extension + protected final JavassistUtils utils; + + protected AbstractRuntimeCodeGenerator(final ClassPool pool) { + utils = JavassistUtils.forClassPool(pool); + + /* + * Make sure Javassist ClassPool sees the classloader of RpcService + */ + utils.ensureClassLoader(RpcService.class); + + brokerNotificationListener = utils.asCtClass(org.opendaylight.controller.sal.binding.api.NotificationListener.class); + } + + protected final CtClass getBrokerNotificationListener() { + return brokerNotificationListener; + } + + protected abstract RuntimeGeneratedInvokerPrototype generateListenerInvoker(Class cls); + protected abstract Supplier directProxySupplier(final Class serviceType); + protected abstract Supplier routerSupplier(final Class serviceType, RpcServiceMetadata metadata); + + private RpcServiceMetadata getRpcMetadata(final CtClass iface) throws ClassNotFoundException, NotFoundException { + final RpcServiceMetadata metadata = new RpcServiceMetadata(); + + for (CtMethod method : iface.getMethods()) { + if (iface.equals(method.getDeclaringClass()) && method.getParameterTypes().length == 1) { + final RpcMetadata routingPair = getRpcMetadata(method); + if (routingPair != null) { + metadata.addContext(routingPair.getContext()); + metadata.addRpcMethod(method.getName(), routingPair); + + /* + * Force-load the RPC class representing the "input" of this RPC. + * + * FIXME: this is pre-existing side-effect of the original code, which + * kept a reference to the loaded class, but it did not use it. + * + * There was no explanation as to why forcing this load was + * necessary. As far as I can tell now is that it forces the + * resolution of method arguments, which would (according to + * my reading of JLS) occur only when the method is invoked via + * binding-aware class action, not when coming from + * binding-independent world. Whether that makes sense or not, + * remains to be investigated. + */ + Thread.currentThread().getContextClassLoader().loadClass(routingPair.getInputType().getName()); + } + } + } + + return metadata; + } + + private RpcMetadata getRpcMetadata(final CtMethod method) throws NotFoundException { + final CtClass inputClass = method.getParameterTypes()[0]; + return rpcMethodMetadata(inputClass, inputClass, method.getName()); + } + + private RpcMetadata rpcMethodMetadata(final CtClass dataClass, final CtClass inputClass, final String rpcMethod) throws NotFoundException { + for (CtMethod method : dataClass.getMethods()) { + if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) { + for (Object annotation : method.getAvailableAnnotations()) { + if (annotation instanceof RoutingContext) { + boolean encapsulated = !method.getReturnType().equals(utils.asCtClass(InstanceIdentifier.class)); + return new RpcMetadata(rpcMethod, ((RoutingContext)annotation).value(), method, encapsulated, inputClass); + } + } + } + } + + for (CtClass iface : dataClass.getInterfaces()) { + final RpcMetadata ret = rpcMethodMetadata(iface, inputClass, rpcMethod); + if(ret != null) { + return ret; + } + } + return null; + } + + private synchronized RuntimeGeneratedInvokerPrototype resolveInvokerClass(final Class cls) { + RuntimeGeneratedInvokerPrototype invoker = invokerClasses.get(cls); + if (invoker != null) { + return invoker; + } + + utils.getLock().lock(); + try { + invoker = ClassLoaderUtils.withClassLoader(cls.getClassLoader(), new Supplier() { + @Override + public RuntimeGeneratedInvokerPrototype get() { + return generateListenerInvoker(cls); + } + }); + + invokerClasses.put(cls, invoker); + return invoker; + } finally { + utils.getLock().unlock(); + } + } + + @Override + public final NotificationInvokerFactory getInvokerFactory() { + return this; + } + + @Override + public final T getDirectProxyFor(final Class serviceType) { + utils.getLock().lock(); + try { + return ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), directProxySupplier(serviceType)); + } finally { + utils.getLock().unlock(); + } + } + + @Override + public final RpcRouter getRouterFor(final Class serviceType, final String name) { + final RpcServiceMetadata metadata = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), new Supplier() { + @Override + public RpcServiceMetadata get() { + try { + return getRpcMetadata(utils.asCtClass(serviceType)); + } catch (ClassNotFoundException | NotFoundException e) { + throw new IllegalStateException(String.format("Failed to load metadata for class {}", serviceType), e); + } + } + }); + + utils.getLock().lock(); + try { + final T instance = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), routerSupplier(serviceType, metadata)); + return new RpcRouterCodegenInstance(name, serviceType, instance, metadata.getContexts()); + } finally { + utils.getLock().unlock(); + } + } + + @Override + public NotificationInvoker invokerFor(final NotificationListener instance) { + final Class cls = instance.getClass(); + final RuntimeGeneratedInvokerPrototype prototype = resolveInvokerClass(cls); + + try { + return RuntimeGeneratedInvoker.create(instance, prototype); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException(String.format("Failed to create invoker for %s", instance), e); + } + } +} diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.java index 5578f75ae2..052fd2169a 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcRouterCodegenInstance.java @@ -24,7 +24,6 @@ import org.opendaylight.yangtools.concepts.AbstractObjectRegistration; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.concepts.util.ListenerRegistry; import org.opendaylight.yangtools.yang.binding.BaseIdentity; -import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.RpcService; import org.slf4j.Logger; @@ -34,7 +33,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; public class RpcRouterCodegenInstance implements // - RpcRouter, RouteChangeListener, InstanceIdentifier> { +RpcRouter, RouteChangeListener, InstanceIdentifier> { private static final Logger LOG = LoggerFactory.getLogger(RpcRouterCodegenInstance.class); @@ -53,8 +52,7 @@ public class RpcRouterCodegenInstance implements // private final String name; @SuppressWarnings("unchecked") - public RpcRouterCodegenInstance(String name,Class type, T routerImpl, Set> contexts, - Set> inputs) { + public RpcRouterCodegenInstance(final String name,final Class type, final T routerImpl, final Iterable> contexts) { this.name = name; this.listeners = ListenerRegistry.create(); this.serviceType = type; @@ -86,7 +84,7 @@ public class RpcRouterCodegenInstance implements // @Override @SuppressWarnings("unchecked") - public RpcRoutingTable getRoutingTable(Class routeContext) { + public RpcRoutingTable getRoutingTable(final Class routeContext) { return (RpcRoutingTable) routingTables.get(routeContext); } @@ -102,12 +100,12 @@ public class RpcRouterCodegenInstance implements // @Override public , InstanceIdentifier>> ListenerRegistration registerRouteChangeListener( - L listener) { + final L listener) { return listeners.registerWithType(listener); } @Override - public void onRouteChange(RouteChange, InstanceIdentifier> change) { + public void onRouteChange(final RouteChange, InstanceIdentifier> change) { for (ListenerRegistration, InstanceIdentifier>> listener : listeners) { try { listener.getInstance().onRouteChange(change); @@ -118,17 +116,17 @@ public class RpcRouterCodegenInstance implements // } @Override - public T getService(Class context, InstanceIdentifier path) { + public T getService(final Class context, final InstanceIdentifier path) { return routingTables.get(context).getRoute(path); } @Override - public RoutedRpcRegistration addRoutedRpcImplementation(T service) { + public RoutedRpcRegistration addRoutedRpcImplementation(final T service) { return new RoutedRpcRegistrationImpl(service); } @Override - public RpcRegistration registerDefaultService(T service) { + public RpcRegistration registerDefaultService(final T service) { // TODO Auto-generated method stub RuntimeCodeHelper.setDelegate(invocationProxy, service); return null; @@ -136,7 +134,7 @@ public class RpcRouterCodegenInstance implements // private class RoutedRpcRegistrationImpl extends AbstractObjectRegistration implements RoutedRpcRegistration { - public RoutedRpcRegistrationImpl(T instance) { + public RoutedRpcRegistrationImpl(final T instance) { super(instance); } @@ -146,22 +144,22 @@ public class RpcRouterCodegenInstance implements // } @Override - public void registerPath(Class context, InstanceIdentifier path) { + public void registerPath(final Class context, final InstanceIdentifier path) { routingTables.get(context).updateRoute(path, getInstance()); } @Override - public void unregisterPath(Class context, InstanceIdentifier path) { + public void unregisterPath(final Class context, final InstanceIdentifier path) { routingTables.get(context).removeRoute(path, getInstance()); } @Override - public void registerInstance(Class context, InstanceIdentifier instance) { + public void registerInstance(final Class context, final InstanceIdentifier instance) { registerPath(context, instance); } @Override - public void unregisterInstance(Class context, InstanceIdentifier instance) { + public void unregisterInstance(final Class context, final InstanceIdentifier instance) { unregisterPath(context, instance); } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcServiceMetadata.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcServiceMetadata.java index 399afe74e8..430b7a7e31 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcServiceMetadata.java +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RpcServiceMetadata.java @@ -9,29 +9,31 @@ package org.opendaylight.controller.sal.binding.codegen.impl; import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Set; import org.opendaylight.yangtools.yang.binding.BaseIdentity; -import org.opendaylight.yangtools.yang.binding.DataContainer; + +import com.google.common.collect.Iterables; final class RpcServiceMetadata { - private final HashMap, RpcMetadata> rpcInputs = new HashMap<>(); - private final HashSet> supportedInputs = new HashSet<>(); - private final HashSet> contexts = new HashSet<>(); - private final HashMap rpcMethods = new HashMap<>(); + private final Set> contexts = new HashSet<>(); + private final Map rpcMethods = new HashMap<>(); + private final Iterable> roContexts = Iterables.unmodifiableIterable(contexts); - public HashSet> getContexts() { - return this.contexts; + public Iterable> getContexts() { + return roContexts; } - public HashMap getRpcMethods() { - return this.rpcMethods; + public RpcMetadata getRpcMethod(final String name) { + return rpcMethods.get(name); } - public HashMap, RpcMetadata> getRpcInputs() { - return this.rpcInputs; + public void addContext(final Class context) { + contexts.add(context); } - public HashSet> getSupportedInputs() { - return this.supportedInputs; + public void addRpcMethod(final String name, final RpcMetadata routingPair) { + rpcMethods.put(name, routingPair); } } diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend index ac782051a8..1be6e2dfb1 100644 --- a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend +++ b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/codegen/impl/RuntimeCodeGenerator.xtend @@ -8,45 +8,27 @@ package org.opendaylight.controller.sal.binding.codegen.impl import java.util.Map -import java.util.WeakHashMap import javassist.ClassPool -import javassist.CtClass -import javassist.CtMethod -import javassist.LoaderClassPath -import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory -import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils -import org.opendaylight.yangtools.yang.binding.DataContainer import org.opendaylight.yangtools.yang.binding.InstanceIdentifier import org.opendaylight.yangtools.yang.binding.Notification -import org.opendaylight.yangtools.yang.binding.NotificationListener import org.opendaylight.yangtools.yang.binding.RpcImplementation -import org.opendaylight.yangtools.yang.binding.RpcService -import org.opendaylight.yangtools.yang.binding.annotations.RoutingContext +import org.opendaylight.controller.sal.binding.codegen.YangtoolsMappingHelper import org.opendaylight.yangtools.yang.binding.util.ClassLoaderUtils import static extension org.opendaylight.controller.sal.binding.codegen.RuntimeCodeSpecification.* -import static extension org.opendaylight.controller.sal.binding.codegen.YangtoolsMappingHelper.* - -class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator, NotificationInvokerFactory { - - val CtClass BROKER_NOTIFICATION_LISTENER; - val extension JavassistUtils utils; - val Map, RuntimeGeneratedInvokerPrototype> invokerClasses; +class RuntimeCodeGenerator extends AbstractRuntimeCodeGenerator { new(ClassPool pool) { - utils = new JavassistUtils(pool); - invokerClasses = new WeakHashMap(); - BROKER_NOTIFICATION_LISTENER = org.opendaylight.controller.sal.binding.api.NotificationListener.asCtClass; - pool.appendClassPath(new LoaderClassPath(RpcService.classLoader)); + super(pool) } - override getDirectProxyFor(Class iface) { - val T instance = ClassLoaderUtils.withClassLoaderAndLock(iface.classLoader,lock) [| + override directProxySupplier(Class iface) { + return [| val proxyName = iface.directProxyName; val potentialClass = ClassLoaderUtils.tryToLoadClassWithTCCL(proxyName) if(potentialClass != null) { - return potentialClass.newInstance as T; + return potentialClass.newInstance; } val supertype = iface.asCtClass val createdCls = createClass(iface.directProxyName, supertype) [ @@ -71,23 +53,17 @@ class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.co ''' ] ] - return createdCls.toClass(iface.classLoader).newInstance as T + return createdCls.toClass(iface.classLoader).newInstance ] - return instance; } - override getRouterFor(Class iface,String routerInstanceName) { - val metadata = ClassLoaderUtils.withClassLoader(iface.classLoader) [| - val supertype = iface.asCtClass - return supertype.rpcMetadata; - ] - - val instance = ClassLoaderUtils.withClassLoaderAndLock(iface.classLoader,lock) [ | + override routerSupplier(Class iface, RpcServiceMetadata metadata) { + return [ | val supertype = iface.asCtClass val routerName = iface.routerName; val potentialClass = ClassLoaderUtils.tryToLoadClassWithTCCL(routerName) if(potentialClass != null) { - return potentialClass.newInstance as T; + return potentialClass.newInstance; } val targetCls = createClass(iface.routerName, supertype) [ @@ -102,7 +78,7 @@ class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.co } implementMethodsFrom(supertype) [ if (parameterTypes.size === 1) { - val rpcMeta = metadata.rpcMethods.get(name); + val rpcMeta = metadata.getRpcMethod(name); val bodyTmp = ''' { final «InstanceIdentifier.name» identifier = $1.«rpcMeta.inputRouteGetter.name»()«IF rpcMeta. @@ -130,74 +106,18 @@ class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.co ''' ] ] - return targetCls.toClass(iface.classLoader,iface.protectionDomain).newInstance as T - + return targetCls.toClass(iface.classLoader,iface.protectionDomain).newInstance ]; - return new RpcRouterCodegenInstance(routerInstanceName,iface, instance, metadata.contexts,metadata.supportedInputs); } - private def RpcServiceMetadata getRpcMetadata(CtClass iface) { - val metadata = new RpcServiceMetadata; - - iface.methods.filter[declaringClass == iface && parameterTypes.size === 1].forEach [ method | - val routingPair = method.rpcMetadata; - if (routingPair !== null) { - metadata.contexts.add(routingPair.context) - metadata.rpcMethods.put(method.name,routingPair) - val input = routingPair.inputType.javaClass as Class; - metadata.supportedInputs.add(input); - metadata.rpcInputs.put(input,routingPair); - } - ] - return metadata; - } - - private def getRpcMetadata(CtMethod method) { - val inputClass = method.parameterTypes.get(0); - return inputClass.rpcMethodMetadata(inputClass,method.name); - } - - private def RpcMetadata rpcMethodMetadata(CtClass dataClass, CtClass inputClass, String rpcMethod) { - for (method : dataClass.methods) { - if (method.name.startsWith("get") && method.parameterTypes.size === 0) { - for (annotation : method.availableAnnotations) { - if (annotation instanceof RoutingContext) { - val encapsulated = !method.returnType.equals(InstanceIdentifier.asCtClass); - return new RpcMetadata(rpcMethod,(annotation as RoutingContext).value, method, encapsulated,inputClass); - } - } - } - } - for (iface : dataClass.interfaces) { - val ret = rpcMethodMetadata(iface,inputClass,rpcMethod); - if(ret != null) return ret; - } - return null; - } - - private def getJavaClass(CtClass cls) { - Thread.currentThread.contextClassLoader.loadClass(cls.name) - } - - override getInvokerFactory() { - return this; - } - - override invokerFor(NotificationListener instance) { - val cls = instance.class - val prototype = resolveInvokerClass(cls); - - return RuntimeGeneratedInvoker.create(instance, prototype) - } - - protected def generateListenerInvoker(Class iface) { - val callbacks = iface.methods.filter[notificationCallback] + override generateListenerInvoker(Class iface) { + val callbacks = iface.methods.filter[YangtoolsMappingHelper.isNotificationCallback(it)] val supportedNotification = callbacks.map[parameterTypes.get(0) as Class].toSet; - val targetCls = createClass(iface.invokerName, BROKER_NOTIFICATION_LISTENER) [ + val targetCls = createClass(iface.invokerName, brokerNotificationListener) [ field(DELEGATE_FIELD, iface) - implementMethodsFrom(BROKER_NOTIFICATION_LISTENER) [ + implementMethodsFrom(brokerNotificationListener) [ body = ''' { «FOR callback : callbacks SEPARATOR " else "» @@ -216,16 +136,4 @@ class RuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.co return new RuntimeGeneratedInvokerPrototype(supportedNotification, finalClass as Class>); } - - protected def resolveInvokerClass(Class class1) { - return ClassLoaderUtils.withClassLoaderAndLock(class1.classLoader,lock) [| - val invoker = invokerClasses.get(class1); - if (invoker !== null) { - return invoker; - } - val newInvoker = generateListenerInvoker(class1); - invokerClasses.put(class1, newInvoker); - return newInvoker - ] - } } -- 2.36.6