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