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