Migrate AbstractRuntimeCodeGenerator to new ClassLoaderUtils
[controller.git] / opendaylight / md-sal / sal-binding-broker / src / main / java / org / opendaylight / controller / sal / binding / codegen / impl / AbstractRuntimeCodeGenerator.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.sal.binding.codegen.impl;
9
10 import com.google.common.base.Supplier;
11 import com.google.common.collect.Iterables;
12 import java.util.Map;
13 import java.util.WeakHashMap;
14 import javassist.ClassPool;
15 import javassist.CtClass;
16 import javassist.CtMethod;
17 import javassist.NotFoundException;
18 import javax.annotation.concurrent.GuardedBy;
19 import org.opendaylight.controller.sal.binding.api.rpc.RpcRouter;
20 import org.opendaylight.controller.sal.binding.codegen.RpcIsNotRoutedException;
21 import org.opendaylight.controller.sal.binding.spi.NotificationInvokerFactory;
22 import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
23 import org.opendaylight.yangtools.util.ClassLoaderUtils;
24 import org.opendaylight.yangtools.yang.binding.BindingMapping;
25 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
26 import org.opendaylight.yangtools.yang.binding.NotificationListener;
27 import org.opendaylight.yangtools.yang.binding.RpcService;
28 import org.opendaylight.yangtools.yang.binding.annotations.RoutingContext;
29
30 abstract class AbstractRuntimeCodeGenerator implements org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator, NotificationInvokerFactory {
31     @GuardedBy("this")
32     private final Map<Class<? extends NotificationListener>, RuntimeGeneratedInvokerPrototype> invokerClasses = new WeakHashMap<>();
33     private final CtClass brokerNotificationListener;
34     protected final JavassistUtils utils;
35
36     protected AbstractRuntimeCodeGenerator(final ClassPool pool) {
37         utils = JavassistUtils.forClassPool(pool);
38
39         /*
40          * Make sure Javassist ClassPool sees the classloader of RpcService
41          */
42         utils.ensureClassLoader(RpcService.class);
43
44         brokerNotificationListener = utils.asCtClass(org.opendaylight.controller.sal.binding.api.NotificationListener.class);
45     }
46
47     protected final CtClass getBrokerNotificationListener() {
48         return brokerNotificationListener;
49     }
50
51     protected abstract RuntimeGeneratedInvokerPrototype generateListenerInvoker(Class<? extends NotificationListener> cls);
52     protected abstract <T extends RpcService> Supplier<T> directProxySupplier(final Class<T> serviceType);
53     protected abstract <T extends RpcService> Supplier<T> routerSupplier(final Class<T> serviceType, RpcServiceMetadata metadata);
54
55     private RpcServiceMetadata getRpcMetadata(final CtClass iface) throws ClassNotFoundException, NotFoundException, RpcIsNotRoutedException {
56         final RpcServiceMetadata metadata = new RpcServiceMetadata();
57
58         for (CtMethod method : iface.getMethods()) {
59             if (isRpcMethodWithInput(iface, method)) {
60                 final RpcMetadata routingPair = getRpcMetadata(method);
61                 if (routingPair != null) {
62                     metadata.addContext(routingPair.getContext());
63                     metadata.addRpcMethod(method.getName(), routingPair);
64
65                     /*
66                      * Force-load the RPC class representing the "input" of this RPC.
67                      *
68                      * FIXME: this is pre-existing side-effect of the original code, which
69                      *        kept a reference to the loaded class, but it did not use it.
70                      *
71                      *        There was no explanation as to why forcing this load was
72                      *        necessary. As far as I can tell now is that it forces the
73                      *        resolution of method arguments, which would (according to
74                      *        my reading of JLS) occur only when the method is invoked via
75                      *        binding-aware class action, not when coming from
76                      *        binding-independent world. Whether that makes sense or not,
77                      *        remains to be investigated.
78                      */
79                     Thread.currentThread().getContextClassLoader().loadClass(routingPair.getInputType().getName());
80                 } else {
81                     throw new RpcIsNotRoutedException(String.format("RPC %s from %s is not routed", method.getName(), iface.getName()));
82                 }
83             }
84         }
85
86         return metadata;
87     }
88
89
90     private boolean isRpcMethodWithInput(final CtClass iface, final CtMethod method) throws NotFoundException {
91         if(iface.equals(method.getDeclaringClass())
92                 && method.getParameterTypes().length == 1) {
93             final CtClass onlyArg = method.getParameterTypes()[0];
94             if(onlyArg.isInterface() && onlyArg.getName().endsWith(BindingMapping.RPC_INPUT_SUFFIX)) {
95                 return true;
96             }
97         }
98         return false;
99     }
100
101     private RpcMetadata getRpcMetadata(final CtMethod method) throws NotFoundException {
102         final CtClass inputClass = method.getParameterTypes()[0];
103         return rpcMethodMetadata(inputClass, inputClass, method.getName());
104     }
105
106     private RpcMetadata rpcMethodMetadata(final CtClass dataClass, final CtClass inputClass, final String rpcMethod) throws NotFoundException {
107         for (CtMethod method : dataClass.getMethods()) {
108             if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) {
109                 for (Object annotation : method.getAvailableAnnotations()) {
110                     if (annotation instanceof RoutingContext) {
111                         boolean encapsulated = !method.getReturnType().equals(utils.asCtClass(InstanceIdentifier.class));
112                         return new RpcMetadata(rpcMethod, ((RoutingContext)annotation).value(), method, encapsulated, inputClass);
113                     }
114                 }
115             }
116         }
117
118         for (CtClass iface : dataClass.getInterfaces()) {
119             final RpcMetadata ret = rpcMethodMetadata(iface, inputClass, rpcMethod);
120             if(ret != null) {
121                 return ret;
122             }
123         }
124         return null;
125     }
126
127     private synchronized RuntimeGeneratedInvokerPrototype resolveInvokerClass(final Class<? extends NotificationListener> cls) {
128         RuntimeGeneratedInvokerPrototype invoker = invokerClasses.get(cls);
129         if (invoker != null) {
130             return invoker;
131         }
132
133         synchronized (utils) {
134             invoker = ClassLoaderUtils.withClassLoader(cls.getClassLoader(), new Supplier<RuntimeGeneratedInvokerPrototype>() {
135                 @Override
136                 public RuntimeGeneratedInvokerPrototype get() {
137                     return generateListenerInvoker(cls);
138                 }
139             });
140         }
141
142         invokerClasses.put(cls, invoker);
143         return invoker;
144     }
145
146     @Override
147     public final NotificationInvokerFactory getInvokerFactory() {
148         return this;
149     }
150
151     @Override
152     public final <T extends RpcService> T getDirectProxyFor(final Class<T> serviceType) {
153         synchronized (utils) {
154             return ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), directProxySupplier(serviceType));
155         }
156     }
157
158     @Override
159     public final <T extends RpcService> RpcRouter<T> getRouterFor(final Class<T> serviceType, final String name) throws RpcIsNotRoutedException {
160         final RpcServiceMetadata metadata = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), new Supplier<RpcServiceMetadata>() {
161             @Override
162             public RpcServiceMetadata get() {
163                 try {
164                     return getRpcMetadata(utils.asCtClass(serviceType));
165                 } catch (ClassNotFoundException | NotFoundException e) {
166                     throw new IllegalStateException(String.format("Failed to load metadata for class %s", serviceType), e);
167                 }
168             }
169         });
170
171         if (Iterables.isEmpty(metadata.getContexts())) {
172             throw new RpcIsNotRoutedException("Service doesn't have routing context associated.");
173         }
174
175         synchronized (utils) {
176             final T instance = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), routerSupplier(serviceType, metadata));
177             return new RpcRouterCodegenInstance<T>(name, serviceType, instance, metadata.getContexts());
178         }
179     }
180
181     @Override
182     public NotificationInvoker invokerFor(final NotificationListener instance) {
183         final Class<? extends NotificationListener> cls = instance.getClass();
184         final RuntimeGeneratedInvokerPrototype prototype = resolveInvokerClass(cls);
185
186         try {
187             return RuntimeGeneratedInvoker.create(instance, prototype);
188         } catch (InstantiationException | IllegalAccessException e) {
189             throw new IllegalStateException(String.format("Failed to create invoker for %s", instance), e);
190         }
191     }
192 }