Package org.apache.openejb.assembler.classic

Source Code of org.apache.openejb.assembler.classic.InterceptorBindingBuilder$IntercpetorBindingComparator

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openejb.assembler.classic;

import org.apache.openejb.BeanContext;
import org.apache.openejb.core.interceptor.InterceptorData;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.SetAccessible;
import org.apache.openejb.OpenEJBException;

import javax.interceptor.InvocationContext;
import java.util.Comparator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.HashSet;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
* @version $Rev: 1311060 $ $Date: 2012-04-08 10:40:46 -0700 (Sun, 08 Apr 2012) $
*/
public class InterceptorBindingBuilder {

    public static final Logger logger = Logger.getInstance(LogCategory.OPENEJB_STARTUP, InterceptorBindingBuilder.class.getPackage().getName());
    private final List<InterceptorBindingInfo> packageAndClassBindings;

    public static enum Level {
        PACKAGE, ANNOTATION_CLASS, CLASS, ANNOTATION_METHOD, OVERLOADED_METHOD, EXACT_METHOD
    }

    public static enum Type {
        ADDITION_OR_LOWER_EXCLUSION, SAME_LEVEL_EXCLUSION, SAME_AND_LOWER_EXCLUSION, EXPLICIT_ORDERING
    }

    private final EjbJarInfo ejbJarInfo;
    private final ArrayList<InterceptorBindingInfo> bindings;
    private final Map<String, InterceptorData> interceptors =  new HashMap<String, InterceptorData>();

    public InterceptorBindingBuilder(ClassLoader cl, EjbJarInfo ejbJarInfo) throws OpenEJBException {
        this.ejbJarInfo = ejbJarInfo;
        bindings = new ArrayList<InterceptorBindingInfo>(ejbJarInfo.interceptorBindings);
        Collections.sort(bindings, new IntercpetorBindingComparator());
        Collections.reverse(bindings);

        packageAndClassBindings = new ArrayList<InterceptorBindingInfo>();
        for (InterceptorBindingInfo binding : bindings) {
            Level level = level(binding);
            if (level == Level.PACKAGE || level == Level.CLASS || level == Level.ANNOTATION_CLASS){
                packageAndClassBindings.add(binding);
            }
        }

        for (InterceptorInfo info : ejbJarInfo.interceptors) {
            Class<?> clazz = null;
            try {
                clazz = Class.forName(info.clazz, true, cl);
            } catch (ClassNotFoundException e) {
                throw new OpenEJBException("Interceptor class cannot be loaded: "+info.clazz);
            }
            InterceptorData interceptor = new InterceptorData(clazz);

            toMethods(clazz, info.aroundInvoke, interceptor.getAroundInvoke());
            toMethods(clazz, info.postActivate, interceptor.getPostActivate());
            toMethods(clazz, info.prePassivate, interceptor.getPrePassivate());
            toMethods(clazz, info.postConstruct, interceptor.getPostConstruct());
            toMethods(clazz, info.preDestroy, interceptor.getPreDestroy());
            toMethods(clazz, info.afterBegin, interceptor.getAfterBegin());
            toMethods(clazz, info.beforeCompletion, interceptor.getBeforeCompletion());
            toMethods(clazz, info.afterCompletion, interceptor.getAfterCompletion());
            toMethods(clazz, info.aroundTimeout, interceptor.getAroundTimeout());
            interceptors.put(info.clazz, interceptor);
        }
    }

    public void build(BeanContext beanContext, EnterpriseBeanInfo beanInfo) {
        Class<?> clazz = beanContext.getBeanClass();

        InterceptorData beanAsInterceptor = new InterceptorData(clazz);
       
       

        if (beanInfo instanceof StatelessBeanInfo || beanInfo instanceof MessageDrivenBeanInfo) {
            /*
             * 4.3.10.2 and 4.5.8
             * If the stateless session bean or MDB instance has an ejbCreate method,
             * the container treats the ejbCreate method as the instance’s PostConstruct method,
             *  and, in this case, the PostConstruct annotation (or deployment descriptor metadata)
             *  can only be applied to the bean’s ejbCreate method.
             */
            NamedMethodInfo info = new NamedMethodInfo();
            info.className = clazz.getName();
            info.methodName = "ejbCreate";
            info.methodParams = new ArrayList<String>();
           
            try {
                Method ejbcreate = MethodInfoUtil.toMethod(clazz, info);
                if (ejbcreate != null) {
                    CallbackInfo ejbcreateAsPostConstruct = new CallbackInfo();
                    ejbcreateAsPostConstruct.className = ejbcreate.getDeclaringClass().getName();
                    ejbcreateAsPostConstruct.method = "ejbCreate";
                    beanInfo.postConstruct.add(ejbcreateAsPostConstruct);
                }
            } catch (IllegalStateException e) {
                // there's no ejbCreate method in stateless bean.
            }

        }

        toMethods(clazz, beanInfo.aroundInvoke, beanAsInterceptor.getAroundInvoke());
        toCallback(clazz, beanInfo.postConstruct, beanAsInterceptor.getPostConstruct());
        toCallback(clazz, beanInfo.preDestroy, beanAsInterceptor.getPreDestroy());

        if (beanInfo instanceof StatefulBeanInfo) {
            StatefulBeanInfo stateful = (StatefulBeanInfo) beanInfo;
            toCallback(clazz, stateful.postActivate, beanAsInterceptor.getPostActivate());
            toCallback(clazz, stateful.prePassivate, beanAsInterceptor.getPrePassivate());

            toCallback(clazz, stateful.afterBegin, beanAsInterceptor.getAfterBegin());
            toCallback(clazz, stateful.beforeCompletion, beanAsInterceptor.getBeforeCompletion());
            toCallback(clazz, stateful.afterCompletion, beanAsInterceptor.getAfterCompletion(), boolean.class);
        } else {
            toMethods(clazz, beanInfo.aroundTimeout, beanAsInterceptor.getAroundTimeout());
        }

      
        while (clazz != null && clazz != Object.class) {
            for (Method method : clazz.getDeclaredMethods()) {
                List<InterceptorData> methodInterceptors = createInterceptorDatas(method, beanInfo.ejbName, this.bindings);
                // The bean itself gets to intercept too and is always last.
                methodInterceptors.add(beanAsInterceptor);
                beanContext.setMethodInterceptors(method, methodInterceptors);
            }
            clazz = clazz.getSuperclass();
        }
       

        List<InterceptorData> callbackInterceptorDatas = createInterceptorDatas(null, beanInfo.ejbName, this.packageAndClassBindings);

        // The bean itself gets to intercept too and is always last.
        callbackInterceptorDatas.add(beanAsInterceptor);

        beanContext.setCallbackInterceptors(callbackInterceptorDatas);
    }

    private List<InterceptorData> createInterceptorDatas(Method method, String ejbName, List<InterceptorBindingInfo> bindings) {
        List<InterceptorBindingInfo> methodBindings = processBindings(method, ejbName, bindings);
        Collections.reverse(methodBindings);
        List<InterceptorData> methodInterceptors = new ArrayList<InterceptorData>();

        for (InterceptorBindingInfo info : methodBindings) {
            List<String> classes = (info.interceptorOrder.size() > 0) ? info.interceptorOrder : info.interceptors;
            for (String interceptorClassName : classes) {
                InterceptorData interceptorData = interceptors.get(interceptorClassName);
                if (interceptorData == null){
                    logger.warning("InterceptorBinding references non-existent (undeclared) interceptor: " + interceptorClassName);
                    continue;
                }
                methodInterceptors.add(interceptorData);
            }
        }
        return methodInterceptors;
    }


    private List<InterceptorBindingInfo> processBindings(Method method, String ejbName, List<InterceptorBindingInfo> bindings){
        List<InterceptorBindingInfo> methodBindings = new ArrayList<InterceptorBindingInfo>();

        // The only critical thing to understand in this loop is that
        // the bindings have already been sorted high to low (first to last)
        // in this order:
        //
        // Primary sort is "level":
        //   (highest/first)
        //    - Method-level bindings with params
        //    - Method-level with no params
        //    - Method-level bindings with params from annotations
        //    - Class-level
        //    - Class-level from annotation
        //    - Package-level (aka. default level)
        //   (lowest/last)
        //
        // They've been secondarily sorted *within* these levels by "type":
        //
        //   (highest)
        //    - Explicit order
        //    - Exclude applying to current and lower levels
        //    - Exclude applying to just current level
        //    - Any addition for current level and/or exclusion for a lower level
        //   (lowest)
        //
        Set<Level> excludes = new HashSet<Level>();
        for (InterceptorBindingInfo info : bindings) {
            Level level = level(info);

            if (!implies(method, ejbName, level, info)) continue;

            Type type = type(level, info);

            if (type == Type.EXPLICIT_ORDERING && !excludes.contains(level)){

                methodBindings.add(info);
                // An explicit ordering trumps all other bindings of the same level or below
                // (even other explicit order bindings).  Any bindings that were higher than
                // this one still apply (and will have already been applied).
                //
                // So we keep what we have, add this last binding and we're done with this method.
                return methodBindings;
            }

            if (type == Type.SAME_AND_LOWER_EXCLUSION){
                // We're done as the only things that will come after this will be
                // at the same or lower level and we've just been told to exclude them.
                // Nothing more to do for this method.
                return methodBindings;
            }

            if (type == Type.SAME_LEVEL_EXCLUSION){
                excludes.add(level);
            }

            if (!excludes.contains(level)) methodBindings.add(info);

            if (info.excludeClassInterceptors) {
                excludes.add(Level.CLASS);
                excludes.add(Level.ANNOTATION_CLASS);
            }
            if (info.excludeDefaultInterceptors) excludes.add(Level.PACKAGE);
        }
        return methodBindings;
    }

    private boolean implies(Method method, String ejbName, Level level, InterceptorBindingInfo info) {
        if (level == Level.PACKAGE) return true;
        if (!ejbName.equals(info.ejbName)) return false;
        if (level == Level.CLASS || level == Level.ANNOTATION_CLASS) return true;

        NamedMethodInfo methodInfo = info.method;
        return MethodInfoUtil.matches(method, methodInfo);
    }


    /**
     * Used for getting the java.lang.reflect.Method objects for the following callbacks:
     *
     *  - @PostConstruct <any-scope> void <method-name>(InvocationContext)
     *  - @PreDestroy <any-scope> void <method-name>(InvocationContext)
     *  - @PrePassivate <any-scope> void <method-name>(InvocationContext)
     *  - @PostActivate <any-scope> void <method-name>(InvocationContext)
     *  - @AroundInvoke <any-scope> Object <method-name>(InvocationContext) throws Exception
     *  - @AroundTimeout <any-scope> Object <method-name>(InvocationContext) throws Exception
     *
     * @param clazz
     * @param callbackInfos the raw CallbackInfo objects
     * @param callbacks the collection where the created methods will be placed
     */
    private void toMethods(Class<?> clazz, List<CallbackInfo> callbackInfos, Set<Method> callbacks) {
        List<Method> methods = new ArrayList<Method>();

        for (CallbackInfo callbackInfo : callbackInfos) {
            try {
                Method method = getMethod(clazz, callbackInfo.method, InvocationContext.class);
                if (callbackInfo.className == null && method.getDeclaringClass().equals(clazz) && !methods.contains(method)){
                    methods.add(method);
                }
                if (method.getDeclaringClass().getName().equals(callbackInfo.className) && !methods.contains(method)){
                    methods.add(method);
                else {
                    // check for a private method on the declared class

                    // find declared class
                    Class<?> c = clazz;
                    while (c != null && !c.getName().equals(callbackInfo.className)) c = c.getSuperclass();

                    // get callback method
                    if (c != null) {
                        try {
                            method = getMethod(c, callbackInfo.method, InvocationContext.class);
                            // make sure it is private
                            if (Modifier.isPrivate(method.getModifiers()) && !methods.contains(method)) {
                                SetAccessible.on(method);
                                methods.add(method);
                            }
                        } catch (NoSuchMethodException e) {
                        }
                    }
                }
            } catch (NoSuchMethodException e) {
                logger.warning("Interceptor method not found (skipping): public Object " + callbackInfo.method + "(InvocationContext); in class " + clazz.getName());
            }
        }
        Collections.sort(methods, new MethodCallbackComparator());

        callbacks.addAll(methods);
    }

    /**
     * Used for getting the java.lang.reflect.Method objects for the following callbacks:
     *
     *  - @PostConstruct <any-scope> void <method-name>()
     *  - @PreDestroy <any-scope> void <method-name>()
     *  - @PrePassivate <any-scope> void <method-name>()
     *  - @PostActivate <any-scope> void <method-name>()
     *  - @AfterBegin <any-scope> void <method-name>()
     *  - @BeforeCompletion <any-scope> void <method-name>()
     *  - @AfterCompletion <any-scope> void <method-name>(boolean)
     *
     * These apply to the bean class only, interceptor methods use InvocationContext as
     * a parameter.  The toMethods method is used for those.
     *
     * @param clazz
     * @param callbackInfos
     * @param callbacks
     */
    private void toCallback(Class<?> clazz, List<CallbackInfo> callbackInfos, Set<Method> callbacks, Class<?>... parameterTypes) {
        List<Method> methods = new ArrayList<Method>();

        for (CallbackInfo callbackInfo : callbackInfos) {
            Class<?> usedClazz = clazz;
            if (clazz.isInterface() && !callbackInfo.className.equals(clazz.getName())) { // dynamic mbean for instance
                try {
                    usedClazz = clazz.getClassLoader().loadClass(callbackInfo.className);
                } catch (ClassNotFoundException e) {
                    // ignored
                }
            }

            try {
                Method method = getMethod(usedClazz, callbackInfo.method, parameterTypes);
                if (callbackInfo.className == null && !methods.contains(method)){
                    methods.add(method);
                } else if (method.getDeclaringClass().getName().equals(callbackInfo.className) && !methods.contains(method)){
                    methods.add(method);
                } else {
                    // check for a private method on the declared class

                    // find declared class
                    Class<?> c = clazz;
                    while (c != null && !c.getName().equals(callbackInfo.className)) c = c.getSuperclass();

                    // get callback method
                    if (c != null) {
                        try {
                            method = c.getDeclaredMethod(callbackInfo.method);
                            // make sure it is private
                            if (Modifier.isPrivate(method.getModifiers()) && !methods.contains(method)) {
                                SetAccessible.on(method);
                                methods.add(method);
                            }
                        } catch (NoSuchMethodException e) {
                        }
                    }
                }
            } catch (NoSuchMethodException e) {
                String message = "Bean Callback method not found (skipping): public void " + callbackInfo.method + "(); in class " + clazz.getName();
                logger.warning(message);
                throw new IllegalStateException(message, e);
            }
        }
        Collections.sort(methods, new MethodCallbackComparator());
        callbacks.addAll(methods);
    }

    /**
     * Used by toMethods and toCallbacks to find the nearest java.lang.reflect.Method with the given
     * name and parameters.  Callbacks can be private so class.getMethod() cannot be used.  Searching
     * starts by looking in the specified class, if the method is not found searching continues with
     * the immediate parent and continues recurssively until the method is found or java.lang.Object
     * is reached.  If the method is not found a NoSuchMethodException is thrown.
     *
     * @param clazz
     * @param methodName
     * @param parameterTypes
     * @return
     * @throws NoSuchMethodException if the method is not found in this class or any of its parent classes
     */
    private Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
        NoSuchMethodException original = null;
        while (clazz != null){
            try {
                Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
                return SetAccessible.on(method);
            } catch (NoSuchMethodException e) {
                if (original == null) original = e;
            }
            clazz = clazz.getSuperclass();
        }
        throw original;
    }

    // -------------------------------------------------------------------
    // Methods for sorting the bindings and callbacks
    // -------------------------------------------------------------------

    public static class IntercpetorBindingComparator implements Comparator<InterceptorBindingInfo> {
        public int compare(InterceptorBindingInfo a, InterceptorBindingInfo b) {
            Level levelA = level(a);
            Level levelB = level(b);

            if (levelA != levelB) return levelA.ordinal() - levelB.ordinal();

            // Now resort to secondary sorting.

            return type(levelA, a).ordinal() - type(levelB, b).ordinal();
        }
    }

    private static Level level(InterceptorBindingInfo info) {
        if (info.ejbName.equals("*")) {
            return Level.PACKAGE;
        }

        if (info.method == null) {
            return info.className == null ? Level.CLASS : Level.ANNOTATION_CLASS;
        }

        if (info.method.methodParams == null) {
            return Level.OVERLOADED_METHOD;
        }

        return info.className == null ? Level.EXACT_METHOD : Level.ANNOTATION_METHOD;
    }

    private static Type type(Level level, InterceptorBindingInfo info) {
        if (info.interceptorOrder.size() > 0) {
            return Type.EXPLICIT_ORDERING;
        }

        if ((level == Level.CLASS  || level == Level.ANNOTATION_CLASS) && info.excludeClassInterceptors && info.excludeDefaultInterceptors) {
            return Type.SAME_AND_LOWER_EXCLUSION;
        }

        if ((level == Level.CLASS  || level == Level.ANNOTATION_CLASS) && info.excludeClassInterceptors) {
            return Type.SAME_LEVEL_EXCLUSION;
        }

        return Type.ADDITION_OR_LOWER_EXCLUSION;
    }

    public static class MethodCallbackComparator implements Comparator<Method> {
        public int compare(Method m1, Method m2) {
            Class<?> c1 = m1.getDeclaringClass();
            Class<?> c2 = m2.getDeclaringClass();
            if (c1.equals(c2)) return 0;
            if (c1.isAssignableFrom(c2)) return -1;
            return 1;
        }
    }

}
TOP

Related Classes of org.apache.openejb.assembler.classic.InterceptorBindingBuilder$IntercpetorBindingComparator

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.