/* * 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 com.google.common.base.Supplier; 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.codegen.RpcIsNotRoutedException; import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory; import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils; import org.opendaylight.yangtools.yang.binding.BindingMapping; 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; 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, RpcIsNotRoutedException { final RpcServiceMetadata metadata = new RpcServiceMetadata(); for (CtMethod method : iface.getMethods()) { if (isRpcMethodWithInput(iface, method)) { 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()); } else { throw new RpcIsNotRoutedException("RPC " + method.getName() + " from "+ iface.getName() +" is not routed"); } } } return metadata; } private boolean isRpcMethodWithInput(final CtClass iface, final CtMethod method) throws NotFoundException { if(iface.equals(method.getDeclaringClass()) && method.getParameterTypes().length == 1) { final CtClass onlyArg = method.getParameterTypes()[0]; if(onlyArg.isInterface() && onlyArg.getName().endsWith(BindingMapping.RPC_INPUT_SUFFIX)) { return true; } } return false; } 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 { synchronized (utils) { 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 { synchronized (utils) { return ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), directProxySupplier(serviceType)); } } finally { utils.getLock().unlock(); } } @Override public final RpcRouter getRouterFor(final Class serviceType, final String name) throws RpcIsNotRoutedException { 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 { synchronized (utils) { 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); } } }