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

com.sun.ejb.containers.interceptors.InterceptorManager Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 * Portions Copyright [2016-2021] [Payara Foundation and/or its affiliates]
 */

package com.sun.ejb.containers.interceptors;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.annotation.PostConstruct;
import jakarta.ejb.EJBException;
import jakarta.interceptor.InvocationContext;

import com.sun.ejb.EJBUtils;
import com.sun.ejb.containers.BaseContainer;
import com.sun.ejb.containers.EJBContextImpl;
import com.sun.enterprise.container.common.spi.util.InterceptorInfo;
import com.sun.enterprise.deployment.EjbInterceptor;
import com.sun.enterprise.deployment.InterceptorDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor;
import com.sun.enterprise.deployment.LifecycleCallbackDescriptor.CallbackType;
import com.sun.enterprise.deployment.MethodDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbDescriptor;
import org.glassfish.ejb.deployment.descriptor.EjbSessionDescriptor;


/**
 * UserInterceptorsManager manages UserInterceptors. There
 * is one instance of InterceptorManager per container.
 *
 * @author Mahesh Kannan
 */
public class InterceptorManager {


    // The following are set when initializing EJB interceptors
    private BaseContainer container;
    private EjbDescriptor ejbDesc;

    // Set when initializing interceptors for a non-ejb
    private InterceptorInfo interceptorInfo;

    private ClassLoader loader;

    private Class beanClass;

    private String beanClassName;

    private Logger _logger;

    private Class[] interceptorClasses;
    
    private Class[] serializableInterceptorClasses;

    private Map instanceIndexMap
            = new HashMap<>();

    private boolean interceptorsExists;

    private String[] pre30LCMethodNames;

    private Class[] lcAnnotationClasses;

    private CallbackChainImpl[] callbackChain;

    // Optionally specified delegate to be set on SystemInterceptorProxy
    private Object runtimeInterceptor;

    List frameworkInterceptors = new LinkedList<>();


    public InterceptorManager(Logger _logger, BaseContainer container,
                              Class[] lcAnnotationClasses, String[] pre30LCMethodNames)
            throws Exception {
        this._logger = _logger;
        this.container = container;
        this.lcAnnotationClasses = lcAnnotationClasses;
        this.pre30LCMethodNames = pre30LCMethodNames;

        ejbDesc = container.getEjbDescriptor();
        loader = container.getClassLoader();
        beanClassName = ejbDesc.getEjbImplClassName();

        this.beanClass = loader.loadClass(beanClassName);
        frameworkInterceptors = ejbDesc.getFrameworkInterceptors();

        buildEjbInterceptorChain();

        if (_logger.isLoggable(Level.FINE)) {
            _logger.log(Level.FINE, "InterceptorManager: " + toString());
        }
    }

    public InterceptorManager(Logger _logger, ClassLoader classLoader, String className,
                              InterceptorInfo interceptorInfo)
            throws Exception {
        this._logger = _logger;


        loader = classLoader;
        beanClassName = className;

        this.beanClass = loader.loadClass(beanClassName);

        this.interceptorInfo = interceptorInfo;
        if( interceptorInfo.getSupportRuntimeDelegate() ) {
            frameworkInterceptors.add(SystemInterceptorProxy.createInterceptorDesc());
        }

        buildInterceptorChain(interceptorInfo);

    }

    // Used when InterceptorManager should instantiate interceptor instances
    // Alternatively, if the caller needs to instantiate the instances, call
    // getInterceptorClasses() and then initializeInterceptorInstances(Object[])
    public Object[] createInterceptorInstances() {
        int size = serializableInterceptorClasses.length;
        Object[] interceptors = new Object[size];

        int newInstanceSize = serializableInterceptorClasses.length;

        for (int index = 0; index < newInstanceSize; index++) {

            Class clazz = serializableInterceptorClasses[index];
            try {
                interceptors[index] = clazz.newInstance();
            } catch (IllegalAccessException illEx) {
                throw new RuntimeException(illEx);
            } catch (InstantiationException instEx) {
                throw new RuntimeException(instEx);
            }

        }

        initializeInterceptorInstances(interceptors);

        return interceptors;
    }

    public Class[] getInterceptorClasses() {
        return serializableInterceptorClasses;
    }

    public void initializeInterceptorInstances(Object[] interceptorInstances) {

        for (int index = 0; index < interceptorInstances.length; index++) {
            Class clazz = serializableInterceptorClasses[index];

            if( SystemInterceptorProxy.class.isAssignableFrom(clazz) && (runtimeInterceptor != null) ) {
                ((SystemInterceptorProxy)interceptorInstances[index]).setDelegate(runtimeInterceptor);
            }
        }
    }


    /**
     * Called sometime after original interceptor initialization.
     * Install the given interceptor class instance before any application
     * level interceptors.
     * param o  instance of an interceptor class
     */
    public void registerRuntimeInterceptor(Object o) {

        runtimeInterceptor = o;

    }

    public InterceptorManager.InterceptorChain getAroundInvokeChain(
            MethodDescriptor mDesc, Method beanMethod) {

        ArrayList interceptors = new ArrayList<>();

        for(InterceptorDescriptor interceptor : frameworkInterceptors) {
            Set aroundInvokeDescs =
                interceptor.getAroundInvokeDescriptors();
            if(aroundInvokeDescs.isEmpty() ) {
                continue;
            }

            List orderedAIInterceptors = null;
            Class interceptorClass = interceptor.getInterceptorClass();
            ClassLoader classLoaderToUse = (interceptorClass != null) ?
                        interceptorClass.getClassLoader() : loader;
            try {
                orderedAIInterceptors = interceptor.getOrderedAroundInvokeDescriptors(classLoaderToUse);
            } catch (Exception e) {
                throw new IllegalStateException("No AroundInvokeIntercetpors found "
                + " on class " + interceptor.getInterceptorClassName(), e);
            }
            addAroundInvokeInterceptors(interceptors, interceptor,
                orderedAIInterceptors, interceptor.getInterceptorClassName(),
                    classLoaderToUse);
        }


        List list = (ejbDesc != null) ?
                ejbDesc.getAroundInvokeInterceptors(mDesc) :
                interceptorInfo.getAroundInvokeInterceptors(beanMethod);


        for (InterceptorDescriptor interceptor : list) {
            String className = interceptor.getInterceptorClassName();
            Set aroundInvokeDescs = 
                interceptor.getAroundInvokeDescriptors();
            if(aroundInvokeDescs.isEmpty() ) {
                continue;
            }

            List orderedAIInterceptors = null;
            try {
                orderedAIInterceptors = interceptor.getOrderedAroundInvokeDescriptors(loader);
            } catch (Exception e) {
               throw new IllegalStateException("No AroundInvokeIntercetpors found "
                   + " on class " + className, e); 
            }
            addAroundInvokeInterceptors(interceptors, interceptor,
                    orderedAIInterceptors, className, loader);
        }

        AroundInvokeInterceptor[] inter = interceptors.toArray(
                new AroundInvokeInterceptor[interceptors.size()]);
        return new AroundInvokeChainImpl(inter);
    }

    public InterceptorManager.InterceptorChain getAroundTimeoutChain(
            MethodDescriptor mDesc, @SuppressWarnings("unused") Method beanMethod) {


        ArrayList interceptors =
                new ArrayList<>();


        for(InterceptorDescriptor interceptor : frameworkInterceptors) {
            Set aroundTimeoutDescs =
                interceptor.getAroundTimeoutDescriptors();
            if(aroundTimeoutDescs.isEmpty() ) {
                continue;
            }

            List orderedAIInterceptors = null;
            Class interceptorClass = interceptor.getInterceptorClass();
            ClassLoader classLoaderToUse = (interceptorClass != null) ?
                        interceptorClass.getClassLoader() : loader;
            try {
                orderedAIInterceptors = interceptor.getOrderedAroundTimeoutDescriptors(classLoaderToUse);
            } catch (Exception e) {
                throw new IllegalStateException("No AroundTimeoutIntercetpors found "
                + " on class " + interceptor.getInterceptorClassName(), e);
            }
            addAroundInvokeInterceptors(interceptors, interceptor,
                orderedAIInterceptors, interceptor.getInterceptorClassName(),
                    classLoaderToUse);
        }

        List list = (ejbDesc != null) ?
                ejbDesc.getAroundTimeoutInterceptors(mDesc) :
                new LinkedList<>();

        for (EjbInterceptor interceptor : list) {
            String className = interceptor.getInterceptorClassName();
            Set aroundTimeoutDescs = 
                interceptor.getAroundTimeoutDescriptors();
            if(aroundTimeoutDescs.isEmpty() ) {
                continue;
            }

            List orderedATInterceptors;
            try {
                orderedATInterceptors = interceptor.getOrderedAroundTimeoutDescriptors(loader);
            } catch (Exception e) {
               throw new IllegalStateException("No AroundTimeoutIntercetpors found "
                   + " on class " + className, e); 
            }
            addAroundInvokeInterceptors(interceptors, interceptor, 
                    orderedATInterceptors, className, loader);
        }

        AroundInvokeInterceptor[] inter = interceptors.toArray(
                new AroundInvokeInterceptor[interceptors.size()]);
        return new AroundInvokeChainImpl(inter);
    }

    private void addAroundInvokeInterceptors(
            List interceptors,
            InterceptorDescriptor interceptor,
            List orderedInterceptors, 
            String className,
            ClassLoader classLoaderToUse) {

        // if interceptor already has the class loaded use its classloader
        if (interceptor.getInterceptorClass() != null) {
            classLoaderToUse = interceptor.getInterceptorClass().getClassLoader();
        }
        for(LifecycleCallbackDescriptor desc : orderedInterceptors) {
            Method method = null;
            try {
                method = desc.getLifecycleCallbackMethodObject(classLoaderToUse);
            } catch(Exception e) {
               throw new IllegalStateException("No callback method of name " +
                       desc.getLifecycleCallbackMethod() 
               + " found on class " + className, e); 
            }

            if (interceptor.getFromBeanClass()) {
                interceptors.add(new BeanAroundInvokeInterceptor(method));
            } else {
                Integer bigInt = instanceIndexMap.get(className);
                int index = (bigInt == null) ? -1 : bigInt;
                if (index == -1) {
                    throw new IllegalStateException(getInternalErrorString(className));
                }
                Class clazz = interceptorClasses[index];
                _logger.log(Level.FINE, "*[md.getDeclaredMethod() => " 
                            + method + " FOR CLAZZ: " + clazz);  
                interceptors.add(new AroundInvokeInterceptor(index, method));
            }
        }
    }

    public boolean hasInterceptors() {
        return this.interceptorsExists;
    }

    public Object intercept(InterceptorManager.InterceptorChain chain,
                            AroundInvokeContext ctx)
            throws Throwable {
        return chain.invokeNext(0, ctx);
    }

    public boolean intercept(CallbackType eventType, EJBContextImpl ctx)
            throws Throwable {

        return intercept(eventType, ctx.getEJB(), ctx.getInterceptorInstances(), ctx);
    }

    public boolean intercept(CallbackType eventType, Object targetObject,
                             Object[] interceptorInstances)
            throws Throwable {
        return intercept(eventType, targetObject, interceptorInstances, null);
    }

    public boolean intercept(CallbackType eventType, Object targetObject,
                             Object[] interceptorInstances, EJBContextImpl ctx)
            throws Throwable {

        CallbackChainImpl chain = null;
        CallbackInvocationContext invContext = null;
        switch (eventType) {
            case AROUND_CONSTRUCT:
                chain = callbackChain[eventType.ordinal()];
                if (container != null) {
                    invContext = new CallbackInvocationContext(beanClass, 
                            interceptorInstances, chain, eventType, container, ctx);
                } else {
                    invContext = new CallbackInvocationContext(beanClass, 
                            interceptorInstances, chain, eventType, interceptorInfo);
                }
                if (chain != null) {
                    chain.invokeNext(0, invContext);
                }
                break;
            case POST_CONSTRUCT:
            case PRE_PASSIVATE:
            case POST_ACTIVATE:
            case PRE_DESTROY:
                chain = callbackChain[eventType.ordinal()];
                invContext = new CallbackInvocationContext(targetObject, 
                        interceptorInstances, chain, eventType);
                if (chain != null) {
                    chain.invokeNext(0, invContext);
                }
                break;
            default:
                throw new IllegalStateException("Invalid event type");
        }

        return true;
    }

    Object getTargetInstance() {
        return interceptorInfo.getTargetObjectInstance();
    }

    private void buildEjbInterceptorChain()
            throws Exception {

        Set> listOfClasses = new HashSet<>();
        for (EjbInterceptor ejbi : ejbDesc.getInterceptorClasses()) {
            
            // PAYARA-826 if the interceptor class is available use it
            Class interceptorClass = ejbi.getInterceptorClass();
            if (interceptorClass == null) {
                String className = ejbi.getInterceptorClassName();
                interceptorClass = loader.loadClass(className);
            }
            listOfClasses.add(interceptorClass);
        }

        // Add framework interceptors to list, but check for existence of
        // class before attempting to load it via application class loader
        for(InterceptorDescriptor frameworkInterceptor : frameworkInterceptors) {
            Class clazz = frameworkInterceptor.getInterceptorClass();
            if( clazz == null ) {
                clazz = loader.loadClass(frameworkInterceptor.getInterceptorClassName());
            }
            listOfClasses.add(clazz);

        }

        initInterceptorClasses(listOfClasses);

        interceptorsExists = (listOfClasses.size() > 0) ||
            ejbDesc.hasAroundInvokeMethod() || ejbDesc.hasAroundTimeoutMethod();

        initEjbCallbackIndices();
    }

    private void buildInterceptorChain(InterceptorInfo interceptorInfo)
            throws Exception {


        Set> listOfClasses = new HashSet<>();
        for(String name : interceptorInfo.getInterceptorClassNames()) {
            listOfClasses.add( loader.loadClass(name));
        }

        // Add framework interceptors to list, but check for existence of
        // class before attempting to load it via application class loader
        for(InterceptorDescriptor frameworkInterceptor : frameworkInterceptors) {
            Class clazz = frameworkInterceptor.getInterceptorClass();
            if( clazz == null ) {
                clazz = loader.loadClass(frameworkInterceptor.getInterceptorClassName());
            }
            listOfClasses.add(clazz);
        }

        initInterceptorClasses(listOfClasses);

        interceptorsExists = (listOfClasses.size() > 0) ||
            interceptorInfo.getHasTargetClassAroundInvoke();

        int size = CallbackType.values().length;
        callbackChain = new CallbackChainImpl[size];

        initCallbackIndices(interceptorInfo.getAroundConstructInterceptors(), CallbackType.AROUND_CONSTRUCT);
        initCallbackIndices(interceptorInfo.getPostConstructInterceptors(), CallbackType.POST_CONSTRUCT);
        initCallbackIndices(interceptorInfo.getPreDestroyInterceptors(), CallbackType.PRE_DESTROY);

    }


    private void initInterceptorClasses(Set> classes) throws  Exception {

        int size = classes.size();
        interceptorClasses = new Class[size];
        serializableInterceptorClasses = new Class[size];
        int index = 0;
        for (Class interClass : classes) {

            interceptorClasses[index] = interClass;
            serializableInterceptorClasses[index] = interClass;
            instanceIndexMap.put(interClass.getName(), index);
            if (!Serializable.class.isAssignableFrom(interClass)) {

                serializableInterceptorClasses[index] = 
                    EJBUtils.loadGeneratedSerializableClass(interClass.getClassLoader(),
                            interClass.getName());

            }
            index++;
        }


        // bean class is never accessed from instanceIndexMap so it's
        // never added.
    }

    private void initEjbCallbackIndices()
            throws ClassNotFoundException, Exception {

        int size = CallbackType.values().length;
        @SuppressWarnings("unchecked")
        List[] callbacks = new ArrayList[size];
        boolean scanFor2xLifecycleMethods = true;
        int numPostConstructFrameworkCallbacks = 0;
        for (CallbackType eventType : CallbackType.values()) {
            int index = eventType.ordinal();
            callbacks[index] = new ArrayList<>();
            boolean scanForCallbacks = true;
            if (! (ejbDesc instanceof EjbSessionDescriptor)) {
                if ((eventType == CallbackType.PRE_PASSIVATE) ||
                        (eventType == CallbackType.POST_ACTIVATE)) {
                    scanForCallbacks = false;
                }
            }

            if (scanForCallbacks) {

                // Make sure any framework interceptors are first
                for(InterceptorDescriptor callback : frameworkInterceptors) {
                    if( callback.hasCallbackDescriptor(eventType)) {
                        Class interceptorClass = callback.getInterceptorClass();
                        ClassLoader classLoaderToUse = (interceptorClass != null) ?
                                interceptorClass.getClassLoader() : loader;
                        List inters = createCallbackInterceptors(eventType, callback,
                                classLoaderToUse);
                        for (CallbackInterceptor inter : inters) {
                            callbacks[index].add(inter);
                            if( eventType == CallbackType.POST_CONSTRUCT) {
                                numPostConstructFrameworkCallbacks++;
                            }
                        }
                    }
                }

                List callbackList = ejbDesc.getCallbackInterceptors(eventType);

                // If there are any application-specified callbacks in metadata, there's no need
                // to scan for the old 2.x style lifecycle methods
                if( callbackList.size() > 0 ) {
                    scanFor2xLifecycleMethods = false;
                }

                for (EjbInterceptor callback : callbackList) {
                    List inters = createCallbackInterceptors(eventType, callback);
                    for (CallbackInterceptor inter : inters) {
                        callbacks[index].add(inter);
                    }
                }
            }

        }

        if (scanFor2xLifecycleMethods) {
            load2xLifecycleMethods(callbacks);
        }
        
        //The next set of lines are to handle the case where
        //  the app doesn't have a @PostConstruct or it 
        //  doesn't implement the EntrerpriseBean interface
        //  In this case we scan for ejbCreate() for MDBs and SLSBs
        boolean lookForEjbCreateMethod = container.scanForEjbCreateMethod();
        
        if (lookForEjbCreateMethod) {
            loadOnlyEjbCreateMethod(callbacks, numPostConstructFrameworkCallbacks);
        }
        
        callbackChain = new CallbackChainImpl[size];
        for (CallbackType eventType : CallbackType.values()) {
            int index = eventType.ordinal();
            CallbackInterceptor[] interceptors = callbacks[index].toArray(new CallbackInterceptor[callbacks[index].size()]);
            callbackChain[index] = new CallbackChainImpl(interceptors);
        }

    }

    private void initCallbackIndices(List callbackList,
                                     CallbackType callbackType) throws Exception {

        ArrayList callbacks = new ArrayList<>();

        int index = callbackType.ordinal();

        for(InterceptorDescriptor callback : frameworkInterceptors) {
            if( callback.hasCallbackDescriptor(callbackType)) {
                Class interceptorClass = callback.getInterceptorClass();
                ClassLoader classLoaderToUse = (interceptorClass != null) ?
                        interceptorClass.getClassLoader() : loader;
                List inters = createCallbackInterceptors(callbackType, callback,
                        classLoaderToUse);
                for (CallbackInterceptor inter : inters) {
                    callbacks.add(inter);
                }
            }
        }

        for (InterceptorDescriptor callback : callbackList) {
            List inters = createCallbackInterceptors(callbackType, callback);
            for (CallbackInterceptor inter : inters) {
                callbacks.add(inter);
            }
        }

        // move above callbackChain = new CallbackChainImpl[size];

        CallbackInterceptor[] interceptors = callbacks.toArray(new CallbackInterceptor[callbacks.size()]);
        callbackChain[index] = new CallbackChainImpl(interceptors);

    }

    private List createCallbackInterceptors(CallbackType eventType,
                                                          InterceptorDescriptor inter) throws Exception {
        // Create callback interceptors using loader provided at initialization time
        return createCallbackInterceptors(eventType, inter, loader);

    }

    private List createCallbackInterceptors(CallbackType eventType,
                                                          InterceptorDescriptor inter,
                                                          ClassLoader classLoaderToUse) throws Exception {
        List callbackList = new ArrayList<>();
        
        List orderedCallbackMethods = 
            inter.getOrderedCallbackDescriptors(eventType, classLoaderToUse);

        String className = inter.getInterceptorClassName();

        for (LifecycleCallbackDescriptor callbackDesc : orderedCallbackMethods) {
            Method method = null;
            try {
                method = callbackDesc.getLifecycleCallbackMethodObject(classLoaderToUse);
            } catch(Exception e) {
                throw new IllegalStateException("No callback method of name " +
                   callbackDesc.getLifecycleCallbackMethod() 
                   + " found on class " + className, e);
	        }

            CallbackInterceptor interceptor = null;
            if (inter.getFromBeanClass()) {
                interceptor = new BeanCallbackInterceptor(method);
            } else {
                Integer bigInt = instanceIndexMap.get(className);
                int index = (bigInt == null) ? -1 : bigInt;
                if (index == -1) {
                    throw new IllegalStateException(getInternalErrorString(className));
                }
                interceptor = new CallbackInterceptor(index, method);
            }

            callbackList.add(interceptor);
        }

        return callbackList;
    }


    private void load2xLifecycleMethods(List[] metaArray) {

        if (jakarta.ejb.EnterpriseBean.class.isAssignableFrom(beanClass)) {
            int sz = lcAnnotationClasses.length;
            for (int i = 0; i < sz; i++) {
                if (pre30LCMethodNames[i] == null) {
                    continue;
                }
                try {
                    Method method = beanClass.getMethod(
                            pre30LCMethodNames[i], (Class[]) null);
                    if (method != null) {    
                        CallbackInterceptor meta =
                                new BeanCallbackInterceptor(method);
                        metaArray[i].add(meta);
                        _logger.log(Level.FINE, "**## bean has 2.x LM: " + meta);
                    }
                } catch (NoSuchMethodException nsmEx) {
                    //TODO: Log exception
                    //Error for a 2.x bean????
                }
            }
        }
    }
    
    //TODO: load2xLifecycleMethods and loadOnlyEjbCreateMethod can be 
    //  refactored to use a common method.
    private void loadOnlyEjbCreateMethod(
            List[] metaArray,
            int numPostConstructFrameworkCallbacks) {
        int sz = lcAnnotationClasses.length;
        for (int i = 0; i < sz; i++) {
            if (lcAnnotationClasses[i] != PostConstruct.class) {
                continue;
            }

            boolean needToScan = true;
            if (metaArray[i] != null) {
                List al = metaArray[i];
                needToScan =  (al.size() == numPostConstructFrameworkCallbacks);
            }
            
            if (! needToScan) {
                // We already have found a @PostConstruct method
                // So just ignore any ejbCreate() method
                break;
            }
            try {
                Method method = beanClass.getMethod(pre30LCMethodNames[i], (Class[]) null);
                if (method != null) {
                    CallbackInterceptor meta = new BeanCallbackInterceptor(method);
                    metaArray[i].add(meta);
                    _logger.log(Level.FINE,
                            "**##[ejbCreate] bean has 2.x style ejbCreate: " + meta);
                }
            } catch (NoSuchMethodException nsmEx) {
                // TODO: Log exception
                //Error for a 2.x bean????
            }
        }
    }

    @Override
    public String toString() {
        StringBuilder sbldr = new StringBuilder();
        sbldr.append("##########################################################\n");
        sbldr.append("InterceptorManager<").append(beanClassName).append("> has ")
                .append(interceptorClasses.length).append(" interceptors");
        sbldr.append("\n\tbeanClassName: ").append(beanClassName);
        sbldr.append("\n\tInterceptors: ");
        for (Class clazz : interceptorClasses) {
            sbldr.append("\n\t\t").append(clazz.getName());
        }
        if (lcAnnotationClasses != null) {
            // lcAnnotationClasses are available for EJBs only
            sbldr.append("\n\tCallback Interceptors: ");
            for (int i = 0; i < lcAnnotationClasses.length; i++) {
                CallbackChainImpl chain = callbackChain[i];
                sbldr.append("\n\t").append(i)
                        .append(": ").append(lcAnnotationClasses[i]);
                sbldr.append("\n\t\t").append(chain.toString());
            }
        }
        sbldr.append("\n");
        sbldr.append("##########################################################\n");
        return sbldr.toString();
    }

    private String getInternalErrorString(String className) {
        StringBuilder sbldr = new StringBuilder("Internal error: ");
        sbldr.append(" className: ").append(className)
                .append(" is neither a bean class (")
                .append(beanClassName).append(") nor an ")
                .append("interceptor class (");
        for (Class cn : interceptorClasses) {
            sbldr.append(cn.getName()).append("; ");
        }
        sbldr.append(")");
        _logger.log(Level.INFO, "++ : " + sbldr.toString());
        return sbldr.toString();
    }

    public interface AroundInvokeContext extends InvocationContext {

        public Object[] getInterceptorInstances();

       /**
        * Called from Interceptor Chain to invoke the actual bean method.
        * This method must throw any exception from the bean method *as is*,
        * without being wrapped in an InvocationTargetException.  The exception
        * thrown from this method will be propagated through the application's
        * interceptor code, so it must not be changed in order for any exception
        * handling logic in that code to function properly.
        */
        public  Object invokeBeanMethod()
            throws Throwable;

    }

    public interface InterceptorChain {
	public Object invokeNext(int index, AroundInvokeContext invCtx)
	    throws Throwable;
    }



}

class AroundInvokeChainImpl
        implements InterceptorManager.InterceptorChain {
    enum ChainType {
        METHOD, CALLBACK}


    protected AroundInvokeInterceptor[] interceptors;
    protected int size;


    protected AroundInvokeChainImpl(AroundInvokeInterceptor[] interceptors) {

        this.interceptors = interceptors;
        this.size = (interceptors == null) ? 0 : interceptors.length;
    }

    @Override
    public Object invokeNext(int index, InterceptorManager.AroundInvokeContext inv)
            throws Throwable {
        return ( index < size ) ?
            interceptors[index].intercept(inv) :
            inv.invokeBeanMethod();
    }

    @Override
    public String toString() {
        StringBuilder bldr = new StringBuilder();
        for (AroundInvokeInterceptor inter : interceptors) {
            bldr.append("\n\t").append(inter);
        }

        return bldr.toString();
    }
}


class AroundInvokeInterceptor {
    protected int index;
    protected Method method;

    AroundInvokeInterceptor(int index, Method method) {
        this.index = index;
        this.method = method;

        try {
            final Method finalM = method;
            if(System.getSecurityManager() == null) {
                if (!finalM.isAccessible()) {
                    finalM.setAccessible(true);
                }
            } else {
                PrivilegedExceptionAction action = () -> {
                    if (!finalM.isAccessible()) {
                        finalM.setAccessible(true);
                    }
                    return null;
                };
                AccessController.doPrivileged(action);
            }
        } catch(Exception e) {
            throw new EJBException(e);
        }

    }

    Object intercept(final InterceptorManager.AroundInvokeContext invCtx) throws Throwable {
        try {
            final Object[] interceptors = invCtx.getInterceptorInstances();

            if( System.getSecurityManager() != null ) {
            // Wrap actual value insertion in doPrivileged to
            // allow for private/protected field access.
                PrivilegedExceptionAction action = () -> method.invoke(interceptors[index], invCtx);
                return AccessController.doPrivileged(action);
            }
            return method.invoke(interceptors[index], invCtx);
        } catch (java.lang.reflect.InvocationTargetException invEx) {
            throw invEx.getCause();
        } catch (java.security.PrivilegedActionException paEx) {
            Throwable th = paEx.getCause();
            if (th.getCause() != null) {
                throw th.getCause();
            }
            throw th;
        }
    }

    @Override
    public String toString() {
        return "[" + index + "]: " + method;
    }

}

class BeanAroundInvokeInterceptor
        extends AroundInvokeInterceptor {

    BeanAroundInvokeInterceptor(Method method) {
        super(-1, method);
    }

    @Override
    Object intercept(final InterceptorManager.AroundInvokeContext invCtx) throws Throwable {
        try {
            if( System.getSecurityManager() != null ) {
                // Wrap actual value insertion in doPrivileged to
                // allow for private/protected field access.
                PrivilegedExceptionAction action = () -> method.invoke(invCtx.getTarget(), invCtx);
                return AccessController.doPrivileged(action);
            }
            return method.invoke(invCtx.getTarget(), invCtx);
        } catch (java.lang.reflect.InvocationTargetException invEx) {
            throw invEx.getCause();
        } catch (java.security.PrivilegedActionException paEx) {
            Throwable th = paEx.getCause();
            if (th.getCause() != null) {
                throw th.getCause();
            }
            throw th;
        }
    }
}

class CallbackInterceptor {
    protected int index;
    protected Method method;

    CallbackInterceptor(int index, Method method) {
        this.index = index;
        this.method = method;

        try {
            final Method finalM = method;
            if(System.getSecurityManager() == null) {
                if (!finalM.isAccessible()) {
                    finalM.setAccessible(true);
                }
            } else {
                PrivilegedExceptionAction action = () -> {
                    if (!finalM.isAccessible()) {
                        finalM.setAccessible(true);
                    }
                    return null;
                };
                AccessController.doPrivileged(action);
            }
        } catch(Exception e) {
            throw new EJBException(e);
        }

    }

    Object intercept(final CallbackInvocationContext invContext) 
        throws Throwable {
        try {

            final Object[] interceptors = invContext
                    .getInterceptorInstances();

            if( System.getSecurityManager() != null ) {
                // Wrap actual value insertion in doPrivileged to
                // allow for private/protected field access.
                PrivilegedExceptionAction action = () -> method.invoke(interceptors[index], invContext);
                return AccessController.doPrivileged(action);
            }
            return method.invoke(interceptors[index], invContext);
        } catch (java.lang.reflect.InvocationTargetException invEx) {
            throw invEx.getCause();
        } catch (java.security.PrivilegedActionException paEx) {
            Throwable th = paEx.getCause();
            if (th.getCause() != null) {
                throw th.getCause();
            }
            throw th;
        }
    }

    boolean isBeanCallback() {
        return false;
    }

    @Override
    public String toString() {
        return "callback[" + index + "]: " + method;
    }
}

class BeanCallbackInterceptor
        extends CallbackInterceptor {
    private static final Object[] NULL_ARGS = null;

    BeanCallbackInterceptor(Method method) {
        super(-1, method);
    }

    @Override
    Object intercept(final CallbackInvocationContext invContext) 
        throws Throwable {
        try {

            if( System.getSecurityManager() != null ) {
            // Wrap actual value insertion in doPrivileged to
            // allow for private/protected field access.
                PrivilegedExceptionAction action = () -> {
                    method.invoke(invContext.getTarget(), NULL_ARGS);
                    return null;
                };
                AccessController.doPrivileged(action);
            } else {
                method.invoke(invContext.getTarget(), NULL_ARGS);
            }
            return invContext.proceed();

        } catch (java.lang.reflect.InvocationTargetException invEx) {
            throw invEx.getCause();
        } catch (java.security.PrivilegedActionException paEx) {
            Throwable th = paEx.getCause();
            if (th.getCause() != null) {
                throw th.getCause();
            }
            throw th;
        }
    }

    @Override
    boolean isBeanCallback() {
        return true;
    }

    @Override
    public String toString() {
        return "beancallback[" + index + "]: " + method;
    }
}