All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.openejb.assembler.classic.InterceptorBindingBuilder Maven / Gradle / Ivy

/*
 * 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.OpenEJBException;
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 javax.interceptor.InvocationContext;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @version $Rev$ $Date$
 */
public class InterceptorBindingBuilder {

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

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

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

    private final ArrayList bindings;
    private final Map interceptors = new HashMap();

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

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

        for (final InterceptorInfo info : ejbJarInfo.interceptors) {
            Class clazz = null;
            try {
                clazz = Class.forName(info.clazz, true, cl);
            } catch (final ClassNotFoundException e) {
                throw new OpenEJBException("Interceptor class cannot be loaded: " + info.clazz);
            }
            final 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(final BeanContext beanContext, final EnterpriseBeanInfo beanInfo) {
        Class clazz = beanContext.getBeanClass();

        final 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.
             */
            final NamedMethodInfo info = new NamedMethodInfo();
            info.className = clazz.getName();
            final Method createMethod = beanContext.getCreateMethod();
            info.methodName = (createMethod != null) ? createMethod.getName(): "ejbCreate";
            info.methodParams = new ArrayList();

            try {
                final Method ejbcreate = MethodInfoUtil.toMethod(clazz, info);
                if (ejbcreate != null) {
                    final CallbackInfo ejbcreateAsPostConstruct = new CallbackInfo();
                    ejbcreateAsPostConstruct.className = ejbcreate.getDeclaringClass().getName();
                    ejbcreateAsPostConstruct.method = ejbcreate.getName();
                    beanInfo.postConstruct.add(ejbcreateAsPostConstruct);
                }
            } catch (final 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) {
            final 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 (final Method method : clazz.getDeclaredMethods()) {
                final List 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();
        }


        final List 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 createInterceptorDatas(final Method method, final String ejbName, final List bindings) {
        final List methodBindings = processBindings(method, ejbName, bindings);
        Collections.reverse(methodBindings);
        final List methodInterceptors = new ArrayList();

        for (final InterceptorBindingInfo info : methodBindings) {
            final List classes = info.interceptorOrder.size() > 0 ? info.interceptorOrder : info.interceptors;
            for (final String interceptorClassName : classes) {
                final 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 processBindings(final Method method, final String ejbName, final List bindings) {
        final List methodBindings = new ArrayList();

        // 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)
        //
        final Set excludes = new HashSet();
        for (final InterceptorBindingInfo info : bindings) {
            final Level level = level(info);

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

            final 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(final Method method, final String ejbName, final Level level, final 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;
        }

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


    /**
     * Used for getting the java.lang.reflect.Method objects for the following callbacks:
     * 

* - @PostConstruct void (InvocationContext) * - @PreDestroy void (InvocationContext) * - @PrePassivate void (InvocationContext) * - @PostActivate void (InvocationContext) * - @AroundInvoke Object (InvocationContext) throws Exception * - @AroundTimeout Object (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(final Class clazz, final List callbackInfos, final Set callbacks) { final List methods = new ArrayList(); for (final 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 (final NoSuchMethodException e) { // no-op } } } } catch (final 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 void () * - @PreDestroy void () * - @PrePassivate void () * - @PostActivate void () * - @AfterBegin void () * - @BeforeCompletion void () * - @AfterCompletion void (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(final Class clazz, final List callbackInfos, final Set callbacks, final Class... parameterTypes) { final List methods = new ArrayList(); for (final 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 (final 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 (final NoSuchMethodException e) { // no-op } } } } catch (final NoSuchMethodException e) { final 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, final String methodName, final Class... parameterTypes) throws NoSuchMethodException { NoSuchMethodException original = null; while (clazz != null) { try { final Method method = clazz.getDeclaredMethod(methodName, parameterTypes); return SetAccessible.on(method); } catch (final 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 { public int compare(final InterceptorBindingInfo a, final InterceptorBindingInfo b) { final Level levelA = level(a); final 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(final 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(final Level level, final 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 { public int compare(final Method m1, final Method m2) { final Class c1 = m1.getDeclaringClass(); final Class c2 = m2.getDeclaringClass(); if (c1.equals(c2)) { return 0; } if (c1.isAssignableFrom(c2)) { return -1; } return 1; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy