Package org.ow2.easybeans.enhancer.interceptors

Source Code of org.ow2.easybeans.enhancer.interceptors.InterceptorClassAdapter

/**
* EasyBeans
* Copyright (C) 2006-2007 Bull S.A.S.
* Contact: easybeans@ow2.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
* USA
*
* --------------------------------------------------------------------------
* $Id: InterceptorClassAdapter.java 5369 2010-02-24 14:58:19Z benoitf $
* --------------------------------------------------------------------------
*/

package org.ow2.easybeans.enhancer.interceptors;

import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.AROUND_INVOKE;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.DEP_INJECT;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.POST_ACTIVATE;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.POST_CONSTRUCT;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.PRE_DESTROY;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.PRE_PASSIVATE;
import static org.ow2.util.ee.metadata.ejbjar.api.InterceptorType.TIMED_OBJECT;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.ow2.easybeans.api.bean.lifecycle.EasyBeansMDBLifeCycle;
import org.ow2.easybeans.api.bean.lifecycle.EasyBeansSFSBLifeCycle;
import org.ow2.easybeans.api.bean.lifecycle.EasyBeansSLSBLifeCycle;
import org.ow2.easybeans.asm.ClassAdapter;
import org.ow2.easybeans.asm.ClassVisitor;
import org.ow2.easybeans.asm.Label;
import org.ow2.easybeans.asm.MethodVisitor;
import org.ow2.easybeans.asm.Opcodes;
import org.ow2.easybeans.asm.Type;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarClassMetadata;
import org.ow2.easybeans.deployment.metadata.ejbjar.EasyBeansEjbJarMethodMetadata;
import org.ow2.easybeans.enhancer.CommonClassGenerator;
import org.ow2.easybeans.enhancer.DefinedClass;
import org.ow2.easybeans.enhancer.bean.BeanClassAdapter;
import org.ow2.easybeans.enhancer.injection.InjectionClassAdapter;
import org.ow2.easybeans.enhancer.lib.AnnotationRecorder;
import org.ow2.easybeans.enhancer.lib.MethodAdapterWithAnnotationRecorder;
import org.ow2.easybeans.enhancer.lib.MethodRenamer;
import org.ow2.easybeans.enhancer.lib.ParameterAnnotationRecorder;
import org.ow2.util.ee.metadata.ejbjar.api.IJClassInterceptor;
import org.ow2.util.ee.metadata.ejbjar.api.InterceptorType;
import org.ow2.util.scan.api.metadata.structures.JMethod;

/**
* This class delegates the creation of an implementation of a
* EasyBeansInvocationContext interface and intercepts all business methods() of a
* Bean.
* @author Florent Benoit
*/
public class InterceptorClassAdapter extends ClassAdapter implements Opcodes {

    /**
     * If this flag is enabled, it allows to share the bean class with other frameworks/tools that load the enhanced class.
     */
    public static final String EASYBEANS_SHARED_CLASS_FLAG = "easybeans.sharedclass";

    /**
     * Metadata available by this adapter for a class.
     */
    private EasyBeansEjbJarClassMetadata classAnnotationMetadata;

    /**
     * List of methods which have been renamed.
     */
    private List<JMethod> renamedMethods = null;

    /**
     * Mappping between className and the bytecode.
     */
    private List<DefinedClass> definedClasses = null;

    /**
     * List of generated classes for each interceptor type.
     */
    private List<InterceptorType> generatedTypes = null;

    /**
     * List of interceptors classes used by the bean.
     */
    private List<String> beanInterceptors = null;

    /**
     * If it is true, interfaces of interceptor lifecycle will be added.
     */
    private boolean addInterface = true;

    /**
     * Map between desc of a method and the list of annotation on the method.
     */
    private Map<String, List<AnnotationRecorder>> annotationsOfMethod = null;

    /**
     * Map between desc of a method and the list of annotation of parameters of the method.
     */
    private Map<String, List<ParameterAnnotationRecorder>> parametersAnnotationsOfMethod = null;

    /**
     * Constructor.
     * @param classAnnotationMetadata object containing all attributes of the
     *        class
     * @param cv the class visitor to which this adapter must delegate calls.
     */
    public InterceptorClassAdapter(final EasyBeansEjbJarClassMetadata classAnnotationMetadata, final ClassVisitor cv) {
        this(classAnnotationMetadata, cv, false);
        this.beanInterceptors = new ArrayList<String>();
        this.annotationsOfMethod = new HashMap<String, List<AnnotationRecorder>>();
        this.parametersAnnotationsOfMethod = new HashMap<String, List<ParameterAnnotationRecorder>>();
    }

    /**
     * Constructor.
     * @param classAnnotationMetadata object containing all attributes of the
     *        class
     * @param cv the class visitor to which this adapter must delegate calls.
     * @param addInterface adds lifecycle interface for a given bean.
     */
    public InterceptorClassAdapter(final EasyBeansEjbJarClassMetadata classAnnotationMetadata,
            final ClassVisitor cv, final boolean addInterface) {
        super(cv);
        this.classAnnotationMetadata = classAnnotationMetadata;
        this.renamedMethods = new ArrayList<JMethod>();
        this.definedClasses = new ArrayList<DefinedClass>();
        this.addInterface = addInterface;
        this.generatedTypes = new ArrayList<InterceptorType>();
    }


    /**
     * Visits the header of the class.
     * @param version the class version.
     * @param access the class's access flags (see
     *        {@link org.ow2.easybeans.asm.Opcodes}). This parameter also indicates
     *        if the class is deprecated.
     * @param name the internal name of the class (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     * @param signature the signature of this class. May be <tt>null</tt> if
     *        the class is not a generic one, and does not extend or implement
     *        generic classes or interfaces.
     * @param superName the internal of name of the super class (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     *        For interfaces, the super class is {@link Object}. May be
     *        <tt>null</tt>, but only for the {@link Object} class.
     * @param interfaces the internal names of the class's interfaces (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     *        May be <tt>null</tt>.
     */
    @Override
    public void visit(final int version, final int access, final String name, final String signature, final String superName,
            final String[] interfaces) {

        String[] newInterfaces = null;

        // Add new interface for lifecycle (if asked)
        if (this.classAnnotationMetadata.isBean() && this.addInterface) {
            // copy old interfaces in the new array
            newInterfaces = new String[interfaces.length + 1];
            System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);

            int indexElement = newInterfaces.length - 1;

            // Add the right interface (SLSB, SFSB, MDB)
            if (this.classAnnotationMetadata.isStateless()) {
                newInterfaces[indexElement] = Type.getInternalName(EasyBeansSLSBLifeCycle.class);
            } else if (this.classAnnotationMetadata.isStateful()) {
                newInterfaces[indexElement] = Type.getInternalName(EasyBeansSFSBLifeCycle.class);
            } else if (this.classAnnotationMetadata.isMdb()) {
                newInterfaces[indexElement] = Type.getInternalName(EasyBeansMDBLifeCycle.class);
            } else {
                throw new IllegalStateException("Bean '" + this.classAnnotationMetadata.getClassName() + "' not SLSB, SFSB or MDB");
            }
        } else {
            newInterfaces = interfaces;
        }

        super.visit(version, access, name, signature, superName, newInterfaces);

    }

    /**
     * Visits information about an inner class. This inner class is not
     * necessarily a member of the class being visited.
     * @param name the internal name of an inner class (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     * @param outerName the internal name of the class to which the inner class
     *        belongs (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     *        May be <tt>null</tt>.
     * @param innerName the (simple) name of the inner class inside its
     *        enclosing class. May be <tt>null</tt> for anonymous inner
     *        classes.
     * @param access the access flags of the inner class as originally declared
     *        in the enclosing class.
     */
    @Override
    public void visitInnerClass(final String name, final String outerName, final String innerName, final int access) {
        super.visitInnerClass(name, outerName, innerName, access);
    }

    /**
     * Visits a method of the class. T
     * @param access the method's access flags (see {@link Opcodes}). This
     *        parameter also indicates if the method is synthetic and/or
     *        deprecated.
     * @param name the method's name.
     * @param desc the method's descriptor (see {@link org.ow2.easybeans.asm.Type}).
     * @param signature the method's signature. May be <tt>null</tt> if the
     *        method parameters, return type and exceptions do not use generic
     *        types.
     * @param exceptions the internal names of the method's exception classes
     *        (see
     *        {@link org.ow2.easybeans.asm.Type#getInternalName() getInternalName}).
     *        May be <tt>null</tt>.
     * @return an object to visit the byte code of the method, or <tt>null</tt>
     *         if this class visitor is not interested in visiting the code of
     *         this method.
     */
    @Override
    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature,
            final String[] exceptions) {
        JMethod jMethod = new JMethod(access, name, desc, signature, exceptions);
        String newName = name;
        int newAccess = access;

        // Intercepted method : need to change the method name for Beans
        if (isInterceptedMethod(jMethod) && this.classAnnotationMetadata.isBean()) {
            // Add the method as renamed
            this.renamedMethods.add(jMethod);

            // Rename the method name
            newName = MethodRenamer.encode(name);
        }

        // Interceptor method : need to change access to public.
        if (!isDependencyInjectionMethod(jMethod) && !isInjectedMethod(jMethod) && isInterceptorMethod(jMethod)) {
            // Change modifier to public
            newAccess = Opcodes.ACC_PUBLIC;
        }

        // Needs to keep annotation for intercepted method
        if (this.classAnnotationMetadata.isBean()) {
            if (isInterceptedMethod(jMethod) || isInterceptorMethod(jMethod)) {
                MethodAdapterWithAnnotationRecorder methodAdapter = new MethodAdapterWithAnnotationRecorder(super.visitMethod(
                        newAccess, newName, desc, signature, exceptions));
                // keep annotations
                this.annotationsOfMethod.put(name + desc, methodAdapter.getAnnotationRecorders());
                this.parametersAnnotationsOfMethod.put(name + desc, methodAdapter.getParameterAnnotationRecorders());

                return methodAdapter;
            }
        }
        // Else only call super method
        return super.visitMethod(newAccess, newName, desc, signature, exceptions);
    }

    /**
     * Visits the end of the class. This method, which is the last one to be
     * called, is used to inform the visitor that all the fields and methods of
     * the class have been visited.
     */
    @Override
    public void visitEnd() {
        super.visitEnd();

        // For Bean only
        if (this.classAnnotationMetadata.isBean()) {
            // Add default lifecycle methods. These methods will call defined
            // lifecycle callback method and super methods or will do nothing.
            EasyBeansEjbJarMethodMetadata posConsMetaData = generateBeanLifeCycleMethod(this.classAnnotationMetadata, POST_CONSTRUCT);
            EasyBeansEjbJarMethodMetadata preDesMetaData = generateBeanLifeCycleMethod(this.classAnnotationMetadata, PRE_DESTROY);
            EasyBeansEjbJarMethodMetadata postActMetaData = generateBeanLifeCycleMethod(this.classAnnotationMetadata, POST_ACTIVATE);
            EasyBeansEjbJarMethodMetadata prePassMetaData = generateBeanLifeCycleMethod(this.classAnnotationMetadata, PRE_PASSIVATE);

            // Generate class for dependency injection
            generateClass(
                    new EasyBeansEjbJarMethodMetadata(InjectionClassAdapter.INJECTED_JMETHOD, this.classAnnotationMetadata),
                    DEP_INJECT);

            // Generate class for timer
            // Create the method by cloning the existing timer method
            EasyBeansEjbJarMethodMetadata timerMethodAnnotationMetadata = null;
            for (EasyBeansEjbJarMethodMetadata m : this.classAnnotationMetadata.getMethodMetadataCollection()) {
                // Found the timer method ?
                if (m.isTimeout()) {
                    // clone this method (to get the correct interceptors, etc)
                    timerMethodAnnotationMetadata = (EasyBeansEjbJarMethodMetadata) m.clone();
                    // Change the method name to the generated method
                    timerMethodAnnotationMetadata.setJMethod(BeanClassAdapter.TIMER_JMETHOD);

                    // set the class
                    timerMethodAnnotationMetadata.setClassMetadata(this.classAnnotationMetadata);
                    // It is not inherited as it's build on this class level
                    timerMethodAnnotationMetadata.setInherited(false, null);
                    break;
                }

            }
            // build an empty one if not built just before
            if (timerMethodAnnotationMetadata == null) {
                timerMethodAnnotationMetadata = new EasyBeansEjbJarMethodMetadata(BeanClassAdapter.TIMER_JMETHOD,
                        this.classAnnotationMetadata);
            }
            // Generate the class
            generateClass(timerMethodAnnotationMetadata, TIMED_OBJECT);


            // Need to generate the implementation of EasyBeansInvocationContext Impl on intercepted methods
            for (EasyBeansEjbJarMethodMetadata method : this.classAnnotationMetadata.getMethodMetadataCollection()) {

                // No else if, need to generate an invocationcontext for each case
                if (method.isBusinessMethod() && !method.isIgnored()) {
                    generateClass(method, AROUND_INVOKE);

                    // method was not renamed (it is inherited), need to generate a method calling super method().
                    if (!this.renamedMethods.contains(method.getJMethod())) {
                        generateCallSuperEncodedMethod(method);
                    }
                }
            }


            // First method is the method that has been generated by the call to generateBeanLifeCycleMethod
            // This is method which needs to be intercepted. (there is always
            // one method as we added default method, so no need to check
            // null list)
            generateClass(posConsMetaData, POST_CONSTRUCT);
            generateClass(preDesMetaData, PRE_DESTROY);
            generateClass(prePassMetaData, PRE_PASSIVATE);
            generateClass(postActMetaData, POST_ACTIVATE);

            // Then generate the interceptorManager
            String generatedClName = this.classAnnotationMetadata.getClassName()
                    + EasyBeansInvocationContextGenerator.SUFFIX_INTERCEPTOR_MANAGER;
            InterceptorManagerGenerator interceptorManagerGenerator = new InterceptorManagerGenerator(
                    this.classAnnotationMetadata.getEjbJarDeployableMetadata(), generatedClName, this.beanInterceptors);
            interceptorManagerGenerator.generate();
            DefinedClass dc = new DefinedClass(generatedClName.replace("/", "."), interceptorManagerGenerator.getBytes());
            // this class will be defined later on the classloader
            this.definedClasses.add(dc);

        }
    }

    /**
     * Generates the call to InvocationContext impl proceed method after
     * building a new object. ie :
     *
     * <pre>
     * public int generatedMethodName(int a, int b) throws MyException {
     *     try {
     *         return ((Integer) new MethodAddInvocationContextImpl(this, a, b).proceed()).intValue();
     *     } catch (MyException e) {
     *         throw e;
     *     } catch (Exception e) {
     *         if (e instanceof RuntimeException) {
     *             throw (RuntimeException) e;
     *         } else {
     *             throw new RuntimeException(e);
     *         }
     *     }
     * }
     * </pre>
     *
     * @param method the annotation metadata of the method
     * @param genInvCtx the generator of the EasyBeansInvocationContext impl class.
     * @param interceptorType the type of method which is intercepted
     */
    private void generateCallToInvocationContext(final EasyBeansEjbJarMethodMetadata method,
            final EasyBeansInvocationContextGenerator genInvCtx, final InterceptorType interceptorType) {

        /**
         * Method name, two cases :
         *      - AroundInvoke : method = original name
         *      - LifeCycle    : method = postConstructEasyBeansLifeCycle, preDestroyEasyBeansLifeCycle
         */
        String generatedMethodName = null;
        switch (interceptorType) {
            case AROUND_INVOKE:
                generatedMethodName = method.getMethodName();
                break;
            case DEP_INJECT:
            case TIMED_OBJECT:
                generatedMethodName = MethodRenamer.decode(method.getMethodName());
                break;
            case POST_CONSTRUCT:
                generatedMethodName = "postConstructEasyBeansLifeCycle";
                break;
            case PRE_DESTROY:
                generatedMethodName = "preDestroyEasyBeansLifeCycle";
                break;
            case PRE_PASSIVATE:
                generatedMethodName = "prePassivateEasyBeansLifeCycle";
                break;
            case POST_ACTIVATE:
                generatedMethodName = "postActivateEasyBeansLifeCycle";
                break;
                default:
                    throw new RuntimeException("No generated method name found for method '" + method.getMethodName() + "'");
        }

        if (generatedMethodName == null) {
            throw new RuntimeException("No generated method name found for method '" + method.getMethodName() + "'");
        }

        // Adds a method which will call the invocationcontext impl
        MethodVisitor mv = this.cv.visitMethod(ACC_PUBLIC, generatedMethodName, method.getJMethod().getDescriptor(),
                null, method.getJMethod().getExceptions());

        boolean webMethodAnnotationPresent = false;

        // replay annotations
        String nameDesc =  MethodRenamer.tryDecode(method.getMethodName()) + method.getJMethod().getDescriptor();
        List<AnnotationRecorder> annotationRecorders = this.annotationsOfMethod.get(nameDesc);
        if (annotationRecorders != null) {
            for (AnnotationRecorder annotationRecorder : annotationRecorders) {
                if ("Ljavax/jws/WebMethod;".equals(annotationRecorder.getName())) {
                    webMethodAnnotationPresent = true;
                }
                annotationRecorder.replay(mv);
            }
        }

        // parameters annotations
        List<ParameterAnnotationRecorder> parameterAnnotationRecorders = this.parametersAnnotationsOfMethod.get(nameDesc);
        if (parameterAnnotationRecorders != null) {
            for (ParameterAnnotationRecorder parameterAnnotationRecorder : parameterAnnotationRecorders) {
                parameterAnnotationRecorder.replay(mv);
            }
        }

        // Add some flags on the generated method
        if (!webMethodAnnotationPresent) {
            CommonClassGenerator.addAnnotationsOnGeneratedMethod(mv);
        }

        mv.visitCode();

        if (Boolean.getBoolean(EASYBEANS_SHARED_CLASS_FLAG)) {
            // if the class is used by other framework, interceptors shouldn't be invoked, so call the renamed method directly.
            // if (getEasyBeansFactory() == null) {
            //     original$MethodHelloWorld();
            // }


            // if the factory is not null, skip the code and go the easyBeansFactoryNotNullLabel label
            Label easyBeansFactoryNotNullLabel = new Label();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKEVIRTUAL, this.classAnnotationMetadata.getClassName(), "getEasyBeansFactory", "()Lorg/ow2/easybeans/api/Factory;");
            mv.visitJumpInsn(IFNONNULL, easyBeansFactoryNotNullLabel);

            // factory is null, needs to call the original/renamed method
            Type[] args = Type.getArgumentTypes(method.getJMethod().getDescriptor());
            mv.visitVarInsn(ALOAD, 0);

            // for each argument of the methods :
            int methodArg = 1;
            for (Type type : args) {
                int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
                mv.visitVarInsn(opCode, methodArg);
                // Double and Long are special parameters
                if (opCode == LLOAD || opCode == DLOAD) {
                    methodArg++;
                }
                methodArg++;
            }

            // Call the original method
            mv.visitMethodInsn(INVOKEVIRTUAL,  this.classAnnotationMetadata.getClassName(), MethodRenamer.encode(method.getMethodName()), method.getJMethod().getDescriptor());

            // Cast and return value
            Type returnType = Type.getReturnType(method.getJMethod().getDescriptor());
            CommonClassGenerator.addReturnType(returnType, mv);
            // if the factory is not null, skip the previous code
            mv.visitLabel(easyBeansFactoryNotNullLabel);
        }




        if (interceptorType == AROUND_INVOKE || interceptorType  == POST_CONSTRUCT || interceptorType == PRE_DESTROY) {
            //        if (getEasyBeansInvocationContextFactory() != null) {
            //            try {
            //                return ((Integer) getEasyBeansInvocationContextFactory().getContext(this, getEasyBeansDynamicInterceptorManager(), interceptorType.toString(), "addMethodSignature" , a, b)
            //                        .proceed()).intValue();
            //            } catch (Exception e) {
            //                throw new RuntimeException(e);
            //            }
            //        }
            Label tryLabelStart = new Label();
            Label l1 = new Label();
            Label l2 = new Label();
            mv.visitTryCatchBlock(tryLabelStart, l1, l2, "java/lang/Exception");
            // Add bean (as first argument)
            mv.visitVarInsn(ALOAD, 0);
            // Test if invocation context factory is null
            // If there is no invocation context factory, jump to the end
            mv.visitMethodInsn(INVOKEVIRTUAL, this.classAnnotationMetadata.getClassName(), "getEasyBeansInvocationContextFactory", "()Lorg/ow2/easybeans/api/interceptor/EZBInvocationContextFactory;");
            Label labelNoInvocationContextFactory = new Label();
            mv.visitJumpInsn(IFNULL, labelNoInvocationContextFactory);

            // Begin of the try block
            mv.visitLabel(tryLabelStart);
            mv.visitVarInsn(ALOAD, 0);

            // There is an invocation context factory, get it
            mv.visitMethodInsn(INVOKEVIRTUAL, this.classAnnotationMetadata.getClassName(), "getEasyBeansInvocationContextFactory", "()Lorg/ow2/easybeans/api/interceptor/EZBInvocationContextFactory;");
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 0);
            // Get the interceptor manager
            mv.visitMethodInsn(INVOKEVIRTUAL, this.classAnnotationMetadata.getClassName(), "getEasyBeansDynamicInterceptorManager", "()Lorg/ow2/easybeans/api/interceptor/EZBInterceptorManager;");

            // Add the interceptor type
            mv.visitFieldInsn(GETSTATIC, Type.getInternalName(InterceptorType.class), interceptorType.toString(), "Lorg/ow2/util/ee/metadata/ejbjar/api/InterceptorType;");
            mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(InterceptorType.class), "toString", "()Ljava/lang/String;");

            // Signature of the method
            mv.visitLdcInsn(MethodHelper.getSignature(method));

            // Arguments of the method
            // parameters = new Object[] {arg0, arg1, arg...};
            // put size of the array
            Type[] args = Type.getArgumentTypes(method.getJMethod().getDescriptor());
            int methodArg = 1;

            mv.visitIntInsn(BIPUSH, args.length);
            mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");

            // for each argument of the methods :
            int argCount = 0;
            for (Type type : args) {
                mv.visitInsn(DUP);
                mv.visitIntInsn(BIPUSH, argCount);

                int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
                mv.visitVarInsn(opCode, methodArg);
                // Double and Long are special parameters
                if (opCode == LLOAD || opCode == DLOAD) {
                    methodArg++;
                }
                methodArg++;

                // if type is not object type, need to convert it
                // for example : Integer.valueOf(i);
                CommonClassGenerator.transformPrimitiveIntoObject(type, mv);
                mv.visitInsn(AASTORE);
                argCount++;
            }

            Type returnType = Type.getReturnType(method.getJMethod().getDescriptor());

            // Call getContext method
            mv.visitMethodInsn(INVOKEINTERFACE, "org/ow2/easybeans/api/interceptor/EZBInvocationContextFactory", "getContext", "(Lorg/ow2/easybeans/api/bean/EasyBeansBean;Lorg/ow2/easybeans/api/interceptor/EZBInterceptorManager;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/Object;)Lorg/ow2/easybeans/api/EasyBeansInvocationContext;");
            // Call of proceed method
            mv.visitMethodInsn(INVOKEINTERFACE, "org/ow2/easybeans/api/EasyBeansInvocationContext", "proceed", "()Ljava/lang/Object;");

            // Cast and return value
            CommonClassGenerator.transformObjectIntoPrimitive(returnType, mv);
            mv.visitLabel(l1);
            CommonClassGenerator.addReturnType(returnType, mv);
            mv.visitLabel(l2);




            boolean methodAlreadyThrowJavaLangException = false;

            // Catch blocks
            String[] methodExceptions = method.getJMethod().getExceptions();
            // catch label = exceptions thrown by method + 1
            Label[] catchsLabel = null;
            if (methodExceptions != null) {
                // if the java.lang.Exception is present, don't need two catchs
                // blocks
                // for java/lang/Exception
                if (Arrays.asList(methodExceptions).contains("java/lang/Exception")) {
                    methodAlreadyThrowJavaLangException = true;
                    catchsLabel = new Label[methodExceptions.length];
                } else {
                    // else, add a catch for java.lang.Exception
                    catchsLabel = new Label[methodExceptions.length + 1];
                }
            } else {
                catchsLabel = new Label[1];
            }

            // init labels
            for (int i = 0; i < catchsLabel.length; i++) {
                catchsLabel[i] = new Label();
            }

            // First, do method exceptions (just rethrow the given exception)
            int lastCatchBlockLabel = 0;
            if (methodAlreadyThrowJavaLangException) {
                lastCatchBlockLabel = catchsLabel.length;
            } else {
                lastCatchBlockLabel = catchsLabel.length - 1;
            }

            for (int block = 0; block < lastCatchBlockLabel; block++) {
                mv.visitLabel(catchsLabel[block]);
                mv.visitVarInsn(ASTORE, methodArg);
                mv.visitVarInsn(ALOAD, methodArg);
                mv.visitInsn(ATHROW);
            }
            // Now, do the wrapped of Exception into a RuntimeException
            if (!methodAlreadyThrowJavaLangException) {
                // start label
                mv.visitLabel(catchsLabel[lastCatchBlockLabel]);
                mv.visitVarInsn(ASTORE, methodArg);

                // instanceof RuntimeException
                mv.visitVarInsn(ALOAD, methodArg);
                mv.visitTypeInsn(INSTANCEOF, "java/lang/RuntimeException");
                Label notInstanceOfRuntimeExceptionLabel = new Label();
                mv.visitJumpInsn(IFEQ, notInstanceOfRuntimeExceptionLabel);

                // throw existing runtime exception (by casting it)
                mv.visitVarInsn(ALOAD, methodArg);
                mv.visitTypeInsn(CHECKCAST, "java/lang/RuntimeException");
                mv.visitInsn(ATHROW);

                // build Runtime exception with given exception
                mv.visitLabel(notInstanceOfRuntimeExceptionLabel);
                mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
                mv.visitInsn(DUP);
                mv.visitVarInsn(ALOAD, methodArg);
                mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/Throwable;)V");
                mv.visitInsn(ATHROW);

            }

            // Perform try/catch blocks with ASM
            int block = 0;
            // method exception
            if (methodExceptions != null) {
                for (String exception : methodExceptions) {
                    mv.visitTryCatchBlock(tryLabelStart, catchsLabel[0], catchsLabel[block], exception);
                    block++;
                }
            }
            // Exception thrown by proceed() call
            if (!methodAlreadyThrowJavaLangException) {
                mv.visitTryCatchBlock(tryLabelStart, catchsLabel[0], catchsLabel[lastCatchBlockLabel], "java/lang/Exception");
            }

            // No invocation context factory end
            mv.visitLabel(labelNoInvocationContextFactory);
        }


        // Start of the Try label of Try/Catch
        Label tryLabel = new Label();
        mv.visitLabel(tryLabel);

        // build new object by calling the constructor
        mv.visitTypeInsn(NEW, genInvCtx.getGeneratedClassName());
        mv.visitInsn(DUP);

        // Add bean (as first argument)
        mv.visitVarInsn(ALOAD, 0);

        // for each argument
        Type[] args = Type.getArgumentTypes(method.getJMethod().getDescriptor());
        int methodArg = 1;
        for (Type type : args) {
            int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
            mv.visitVarInsn(opCode, methodArg);
            // Double and Long are special parameters
            if (opCode == LLOAD || opCode == DLOAD) {
                methodArg++;
            }
            methodArg++;
        }
        Type returnType = Type.getReturnType(method.getJMethod().getDescriptor());

        String constructorDesc = genInvCtx.getConstructorDesc();
        mv.visitMethodInsn(INVOKESPECIAL, genInvCtx.getGeneratedClassName(), "<init>", constructorDesc);
        mv.visitMethodInsn(INVOKEVIRTUAL, genInvCtx.getGeneratedClassName(), "proceed", "()Ljava/lang/Object;");

        CommonClassGenerator.transformObjectIntoPrimitive(returnType, mv);
        CommonClassGenerator.addReturnType(returnType, mv);

        boolean methodAlreadyThrowJavaLangException = false;

        // Catch blocks
        String[] methodExceptions = method.getJMethod().getExceptions();
        // catch label = exceptions thrown by method + 1
        Label[] catchsLabel = null;
        if (methodExceptions != null) {
            // if the java.lang.Exception is present, don't need two catchs
            // blocks
            // for java/lang/Exception
            if (Arrays.asList(methodExceptions).contains("java/lang/Exception")) {
                methodAlreadyThrowJavaLangException = true;
                catchsLabel = new Label[methodExceptions.length];
            } else {
                // else, add a catch for java.lang.Exception
                catchsLabel = new Label[methodExceptions.length + 1];
            }
        } else {
            catchsLabel = new Label[1];
        }

        // init labels
        for (int i = 0; i < catchsLabel.length; i++) {
            catchsLabel[i] = new Label();
        }

        // First, do method exceptions (just rethrow the given exception)
        int lastCatchBlockLabel = 0;
        if (methodAlreadyThrowJavaLangException) {
            lastCatchBlockLabel = catchsLabel.length;
        } else {
            lastCatchBlockLabel = catchsLabel.length - 1;
        }

        for (int block = 0; block < lastCatchBlockLabel; block++) {
            mv.visitLabel(catchsLabel[block]);
            mv.visitVarInsn(ASTORE, methodArg);
            mv.visitVarInsn(ALOAD, methodArg);
            mv.visitInsn(ATHROW);
        }
        // Now, do the wrapped of Exception into a RuntimeException
        if (!methodAlreadyThrowJavaLangException) {
            // start label
            mv.visitLabel(catchsLabel[lastCatchBlockLabel]);
            mv.visitVarInsn(ASTORE, methodArg);

            // instanceof RuntimeException
            mv.visitVarInsn(ALOAD, methodArg);
            mv.visitTypeInsn(INSTANCEOF, "java/lang/RuntimeException");
            Label notInstanceOfRuntimeExceptionLabel = new Label();
            mv.visitJumpInsn(IFEQ, notInstanceOfRuntimeExceptionLabel);

            // throw existing runtime exception (by casting it)
            mv.visitVarInsn(ALOAD, methodArg);
            mv.visitTypeInsn(CHECKCAST, "java/lang/RuntimeException");
            mv.visitInsn(ATHROW);

            // build Runtime exception with given exception
            mv.visitLabel(notInstanceOfRuntimeExceptionLabel);
            mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, methodArg);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/Throwable;)V");
            mv.visitInsn(ATHROW);

        }

        // Perform try/catch blocks with ASM
        int block = 0;
        // method exception
        if (methodExceptions != null) {
            for (String exception : methodExceptions) {
                mv.visitTryCatchBlock(tryLabel, catchsLabel[0], catchsLabel[block], exception);
                block++;
            }
        }
        // Exception thrown by proceed() call
        if (!methodAlreadyThrowJavaLangException) {
            mv.visitTryCatchBlock(tryLabel, catchsLabel[0], catchsLabel[lastCatchBlockLabel], "java/lang/Exception");
        }

        mv.visitMaxs(0, 0);
        mv.visitEnd();

    }

    /**
     * Generate an invocation context object.
     * @param method intercepted method
     * @param interceptorType the type of method which is intercepted
     */
    private void generateClass(final EasyBeansEjbJarMethodMetadata method, final InterceptorType interceptorType) {
        EasyBeansInvocationContextGenerator genInvCtx = new EasyBeansInvocationContextGenerator(method, interceptorType);
        genInvCtx.generate();

        // Get all interceptors used and that are not defined in the bean
        for (IJClassInterceptor interceptor : genInvCtx.getAllInterceptors()) {
            String interceptorClassName = interceptor.getClassName();
            if (!interceptorClassName.equals(this.classAnnotationMetadata.getClassName())) {
                if (!this.beanInterceptors.contains(interceptorClassName)) {
                    this.beanInterceptors.add(interceptorClassName);
                }
            }
        }
        DefinedClass dc = new DefinedClass(genInvCtx.getGeneratedClassName().replace("/", "."), genInvCtx.getBytes());
        // this class will be defined later on the classloader
        this.definedClasses.add(dc);
        this.generatedTypes.add(interceptorType);
        // generate method calling generated EasyBeansInvocationContext impl
        generateCallToInvocationContext(method, genInvCtx, interceptorType);

    }

    /**
     * Generates a call to the method defined in the super class.
     * public int original$add(int i, int j) {
     *     return super.add(i, j);
     * }
     * @param method the annotation metadata of the method
     */
    private void generateCallSuperEncodedMethod(final EasyBeansEjbJarMethodMetadata method) {

        String generatedMethodName = MethodRenamer.encode(method.getMethodName());
        JMethod jMethod = method.getJMethod();
        MethodVisitor mv = this.cv.visitMethod(jMethod.getAccess(), generatedMethodName,
                jMethod.getDescriptor(), jMethod.getSignature(), jMethod.getExceptions());

        // Add some flags on the generated method
        CommonClassGenerator.addAnnotationsOnGeneratedMethod(mv);

        mv.visitCode();

        // Add bean (as first argument)
        mv.visitVarInsn(ALOAD, 0);

        // for each argument
        Type[] args = Type.getArgumentTypes(jMethod.getDescriptor());
        int methodArg = 1;
        for (Type type : args) {
            int opCode = CommonClassGenerator.putFieldLoadOpCode(type.getSort());
            mv.visitVarInsn(opCode, methodArg);
            // Double and Long are special parameters
            if (opCode == LLOAD || opCode == DLOAD) {
                methodArg++;
            }
            methodArg++;
        }

        // call super class method()
        mv.visitMethodInsn(INVOKESPECIAL, method.getClassMetadata().getSuperName(),
                jMethod.getName(), jMethod.getDescriptor());

        Type returnType = Type.getReturnType(jMethod.getDescriptor());
        CommonClassGenerator.addReturnType(returnType, mv);


        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }





    /**
     * Generates a default method for lifecycle method events.
     * It will call the methods in the super classes of the defined method.
     * @param classMetaData the metadata used to generate method metadata
     * @param interceptorType the type of intercepted method
     * @return the generated method metadata
     */
    private EasyBeansEjbJarMethodMetadata generateBeanLifeCycleMethod(final EasyBeansEjbJarClassMetadata classMetaData,
            final InterceptorType interceptorType) {
        String generatedMethodName = null;
        List<EasyBeansEjbJarMethodMetadata> existingLifecycleMethods = null;
        switch (interceptorType) {
            case AROUND_INVOKE:
            case DEP_INJECT:
            case TIMED_OBJECT:
                //Nothing to generate
                return null;
            case POST_CONSTRUCT:
                generatedMethodName = "beanPostConstruct$generated";
                existingLifecycleMethods = classMetaData.getPostConstructMethodsMetadata();
                break;
            case PRE_DESTROY:
                generatedMethodName = "beanPreDestroy$generated";
                existingLifecycleMethods = classMetaData.getPreDestroyMethodsMetadata();
                break;
            case PRE_PASSIVATE:
                generatedMethodName = "beanPrePassivate$generated";
                existingLifecycleMethods = classMetaData.getPrePassivateMethodsMetadata();
                break;
            case POST_ACTIVATE:
                generatedMethodName = "beanPostActivate$generated";
                existingLifecycleMethods = classMetaData.getPostActivateMethodsMetadata();
                break;
                default:
                    throw new RuntimeException("No generated method name found for interceptorType '" + interceptorType + "'");
        }


        // Generates the body of this method.
        MethodVisitor mv = this.cv.visitMethod(ACC_PUBLIC, generatedMethodName, "()V", null, null);

        // Add some flags on the generated method
        CommonClassGenerator.addAnnotationsOnGeneratedMethod(mv);

        mv.visitCode();
        // Call methods in their order (if any)
        if (existingLifecycleMethods != null) {
            for (EasyBeansEjbJarMethodMetadata method : existingLifecycleMethods) {
                // Inherited or not ?
                String clName = method.getClassMetadata().getClassName();
                mv.visitVarInsn(ALOAD, 0);
                int opcode = INVOKEVIRTUAL;
                if (method.isInherited()) {
                    clName = method.getOriginalClassMetadata().getClassName();
                    opcode = INVOKESPECIAL;
                }
                mv.visitMethodInsn(opcode, clName, method.getMethodName(), method.getJMethod().getDescriptor());
            }
        }


        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        // add method in the class metadata
        JMethod method = new JMethod(ACC_PUBLIC, generatedMethodName, "()V", null, null);
        EasyBeansEjbJarMethodMetadata generatedMetadata = new EasyBeansEjbJarMethodMetadata(method, classMetaData);

        // Set value
        switch (interceptorType) {
            case POST_CONSTRUCT:
                generatedMetadata.setPostConstruct(true);
                break;
            case PRE_DESTROY:
                generatedMetadata.setPreDestroy(true);
                break;
            case PRE_PASSIVATE:
                generatedMetadata.setPrePassivate(true);
                break;
            case POST_ACTIVATE:
                generatedMetadata.setPostActivate(true);
                break;
            default:
                    throw new RuntimeException("No generated method name found for interceptorType '" + interceptorType + "'");
        }

        classMetaData.addStandardMethodMetadata(generatedMetadata);
        return generatedMetadata;
    }

    /**
     * Check if this method is the injected method used for dependency injection.
     * @param jMethod object to check
     * @return true if the given method is the injected method used for dependency
     *         injection
     */
    private boolean isDependencyInjectionMethod(final JMethod jMethod) {
        return InjectionClassAdapter.INJECTED_METHOD.equals(jMethod.getName());
    }

    /**
     * Check if this method is injected or not by injection class adapter : No need to add interceptors on these methods.
     * @param jMethod object to check
     * @return true if the given method is injected by injection class adapter.
     */
    private boolean isInjectedMethod(final JMethod jMethod) {
        for (String method : InjectionClassAdapter.INJECTED_METHODS) {
            if (method.equals(jMethod.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param jMethod object to check
     * @return true if the given method is an intercepted method
     * (and business method) because lifecycle methods are not renamed
     */
    private boolean isInterceptedMethod(final JMethod jMethod) {
        // needs to be intercepted
        if (isDependencyInjectionMethod(jMethod)) {
            return this.classAnnotationMetadata.isBean();
        }

        // other injected methods are helper methods (and not business --> no need to intercept them)
        if (isInjectedMethod(jMethod)) {
            return false;
        }

        // get method metadata
        EasyBeansEjbJarMethodMetadata method = this.classAnnotationMetadata.getMethodMetadata(jMethod);
        if (method == null) {
            throw new IllegalStateException("Cannot find a method " + jMethod + " in class "
                    + this.classAnnotationMetadata.getClassName());
        }
        return method.isBusinessMethod();
    }

    /**
     * @param jMethod object to check
     * @return true if the given method is an interceptor method (ie AroundInvoke, PostConstruct, etc).
     */
    private boolean isInterceptorMethod(final JMethod jMethod) {
        // get method metadata
        EasyBeansEjbJarMethodMetadata method = this.classAnnotationMetadata.getMethodMetadata(jMethod);
        if (method == null) {
            throw new IllegalStateException("Cannot find a method " + jMethod + " in class "
                    + this.classAnnotationMetadata.getClassName());
        }
        return (method.isAroundInvoke() || method.isLifeCycleMethod());
    }


    /**
     * @return list of classes generated and that need to be defined in a
     *         classloader
     */
    public List<DefinedClass> getDefinedClasses() {
        return this.definedClasses;
    }
}
TOP

Related Classes of org.ow2.easybeans.enhancer.interceptors.InterceptorClassAdapter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.