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