org.opendaylight.controller.sal.binding.codegen.impl.AbstractRuntimeCodeGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.controller.sal.binding.codegen.impl.AbstractRuntimeCodeGenerator.java

Source

/*
 * 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 com.google.common.collect.Iterables;
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.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.util.ClassLoaderUtils;
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;

abstract class AbstractRuntimeCodeGenerator implements
        org.opendaylight.controller.sal.binding.codegen.RuntimeCodeGenerator, NotificationInvokerFactory {
    @GuardedBy("this")
    private final Map<Class<? extends NotificationListener>, RuntimeGeneratedInvokerPrototype> invokerClasses = new WeakHashMap<>();
    private final CtClass brokerNotificationListener;
    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<? extends NotificationListener> cls);

    protected abstract <T extends RpcService> Supplier<T> directProxySupplier(final Class<T> serviceType);

    protected abstract <T extends RpcService> Supplier<T> routerSupplier(final Class<T> 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(
                            String.format("RPC %s from %s is not routed", method.getName(), iface.getName()));
                }
            }
        }

        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<? extends NotificationListener> cls) {
        RuntimeGeneratedInvokerPrototype invoker = invokerClasses.get(cls);
        if (invoker != null) {
            return invoker;
        }

        synchronized (utils) {
            invoker = ClassLoaderUtils.withClassLoader(cls.getClassLoader(),
                    new Supplier<RuntimeGeneratedInvokerPrototype>() {
                        @Override
                        public RuntimeGeneratedInvokerPrototype get() {
                            return generateListenerInvoker(cls);
                        }
                    });
        }

        invokerClasses.put(cls, invoker);
        return invoker;
    }

    @Override
    public final NotificationInvokerFactory getInvokerFactory() {
        return this;
    }

    @Override
    public final <T extends RpcService> T getDirectProxyFor(final Class<T> serviceType) {
        synchronized (utils) {
            return ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(), directProxySupplier(serviceType));
        }
    }

    @Override
    public final <T extends RpcService> RpcRouter<T> getRouterFor(final Class<T> serviceType, final String name)
            throws RpcIsNotRoutedException {
        final RpcServiceMetadata metadata = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(),
                new Supplier<RpcServiceMetadata>() {
                    @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 %s", serviceType), e);
                        }
                    }
                });

        if (Iterables.isEmpty(metadata.getContexts())) {
            throw new RpcIsNotRoutedException("Service doesn't have routing context associated.");
        }

        synchronized (utils) {
            final T instance = ClassLoaderUtils.withClassLoader(serviceType.getClassLoader(),
                    routerSupplier(serviceType, metadata));
            return new RpcRouterCodegenInstance<T>(name, serviceType, instance, metadata.getContexts());
        }
    }

    @Override
    public NotificationInvoker invokerFor(final NotificationListener instance) {
        final Class<? extends NotificationListener> 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);
        }
    }
}