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

org.apache.openejb.core.interceptor.ReflectionInvocationContext Maven / Gradle / Ivy

There is a newer version: 4.7.5
Show newest version
/*
 * 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.core.interceptor;

import org.apache.openejb.core.Operation;
import org.apache.openejb.util.Classes;

import javax.interceptor.InvocationContext;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.TreeMap;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

/**
 * @version $Rev: 1153797 $ $Date: 2011-08-04 02:09:44 -0700 (Thu, 04 Aug 2011) $
 */
public class ReflectionInvocationContext implements InvocationContext {
    private final Iterator interceptors;
    private final Object target;
    private final Method method;
    private final Object[] parameters;
    private final Map contextData = new TreeMap();
    private final Class[] parameterTypes;

    private final Operation operation;

    public ReflectionInvocationContext(Operation operation, List interceptors, Object target, Method method, Object... parameters) {
        if (operation == null) throw new NullPointerException("operation is null");
        if (interceptors == null) throw new NullPointerException("interceptors is null");
        if (target == null) throw new NullPointerException("target is null");

        this.operation = operation;
        this.interceptors = interceptors.iterator();
        this.target = target;
        this.method = method;
        this.parameters = parameters;

        if (method == null) {
            parameterTypes = new Class[0];
        } else {
            parameterTypes = method.getParameterTypes();
        }
    }

    public Object getTimer() {
        if (operation.equals(Operation.TIMEOUT)) {
            return parameters[0];
        }
        return null;
    }

    public Object getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getParameters() {
        //TODO Need to figure out what is going on with afterCompletion call back here ?
        if (operation.isCallback() && !operation.equals(Operation.AFTER_COMPLETION) && !operation.equals(Operation.TIMEOUT)) {
            throw new IllegalStateException(getIllegalParameterAccessMessage());
        }
        return this.parameters;
    }

    private String getIllegalParameterAccessMessage() {
        String m = "Callback methods cannot access parameters.";
        m += "  Callback Type: "+operation;
        if (method!= null){
            m += ", Target Method: " + method.getName();
        }
        if (target != null){
            m += ", Target Bean: "+target.getClass().getName();
        }
        return m;
    }

    public void setParameters(Object[] parameters) {
        if (operation.isCallback() && !operation.equals(Operation.TIMEOUT)) {
            throw new IllegalStateException(getIllegalParameterAccessMessage());
        }
        if (parameters == null) throw new IllegalArgumentException("parameters is null");
        if (parameters.length != this.parameters.length) {
            throw new IllegalArgumentException("Expected " + this.parameters.length + " parameters, but only got " + parameters.length + " parameters");
        }
        for (int i = 0; i < parameters.length; i++) {
            Object parameter = parameters[i];
            Class parameterType = parameterTypes[i];

            if (parameter == null) {
                if (parameterType.isPrimitive()) {
                    throw new IllegalArgumentException("Expected parameter " + i + " to be primitive type " + parameterType.getName() +
                        ", but got a parameter that is null");
                }
            } else {            
            	//check that types are applicable
            	Class actual = Classes.deprimitivize(parameterType);
            	Class given  = Classes.deprimitivize(parameter.getClass());
            	
            	if (!actual.isAssignableFrom(given)) {
                    throw new IllegalArgumentException("Expected parameter " + i + " to be of type " + parameterType.getName() +
                            ", but got a parameter of type " + parameter.getClass().getName());            		
            	}
            }
        }
        System.arraycopy(parameters, 0, this.parameters, 0, parameters.length);
    }

    public Map getContextData() {
        return contextData;
    }

    private Invocation next() {
        if (interceptors.hasNext()) {
            Interceptor interceptor = interceptors.next();
            Object nextInstance = interceptor.getInstance();
            Method nextMethod = interceptor.getMethod();

            if (nextMethod.getParameterTypes().length == 1 && nextMethod.getParameterTypes()[0] == InvocationContext.class) {
                return new InterceptorInvocation(nextInstance, nextMethod, this);
            } else {
                return new LifecycleInvocation(nextInstance, nextMethod, this, parameters);
            }
        } else if (method != null) {
            //EJB 3.1, it is allowed that timeout method does not have parameter Timer.class,
            //However, while invoking the timeout method, the timer value is passed, as it is also required by InnvocationContext.getTimer() method
            Object[] methodParameters;
            if (operation.equals(Operation.TIMEOUT) && method.getParameterTypes().length == 0) {
                methodParameters = new Object[0];
            } else {
                methodParameters = parameters;
            }
            return new BeanInvocation(target, method, methodParameters);
        } else {
            return new NoOpInvocation();
        }
    }

    public Object proceed() throws Exception {
        // The bulk of the logic of this method has intentionally been moved
        // out so stepping through a large stack in a debugger can be done quickly.
        // Simply put one break point on 'next.invoke()' or one inside that method.
        try {
            Invocation next = next();
            return next.invoke();
        } catch (InvocationTargetException e) {
            throw unwrapInvocationTargetException(e);
        }
    }

    private abstract static class Invocation {
        private final Method method;
        private final Object[] args;
        private final Object target;

        public Invocation(Object target, Method method, Object[] args) {
            this.target = target;
            this.method = method;
            this.args = args;
        }
        public Object invoke() throws Exception {
            
            Object value = method.invoke(target, args);
            return value;
        }


        public String toString() {
            return method.getDeclaringClass().getName() + "." + method.getName();
        }
    }

    private static class BeanInvocation extends Invocation {
        public BeanInvocation(Object target, Method method, Object[] args) {
            super(target, method, args);
        }
    }

    private static class InterceptorInvocation extends Invocation {
        public InterceptorInvocation(Object target, Method method, InvocationContext invocationContext) {
            super(target, method, new Object[] {invocationContext});
        }
    }

    private static class LifecycleInvocation extends Invocation {
        private final InvocationContext invocationContext;

        public LifecycleInvocation(Object target, Method method, InvocationContext invocationContext, Object[] args) {
            super(target, method, args);
            this.invocationContext = invocationContext;
        }

        public Object invoke() throws Exception {
            // invoke the callback
            super.invoke();

            // we need to call proceed so callbacks in subclasses get invoked
            Object value = invocationContext.proceed();
            return value;
        }
    }

    private static class NoOpInvocation extends Invocation {
        public NoOpInvocation() {
            super(null, null, null);
        }

        public Object invoke() throws IllegalAccessException, InvocationTargetException {
            return null;
        }
    }

    // todo verify excpetion types

    /**
     * Business method interceptors can only throw exception allowed by the target business method.
     * Lifecycle interceptors can only throw RuntimeException.
     * @param e the invocation target exception of a reflection method invoke
     * @return the cause of the exception
     * @throws AssertionError if the cause is not an Exception or Error.
     */
    private Exception unwrapInvocationTargetException(InvocationTargetException e) {
        Throwable cause = e.getCause();
        if (cause == null) {
            return e;
        } else if (cause instanceof Exception) {
            return (Exception) cause;
        } else if (cause instanceof Error) {
            throw (Error) cause;
        } else {
            throw new AssertionError(cause);
        }
    }

    public String toString() {
        String methodName = (method != null)? method.getName(): null;

        return "InvocationContext(operation=" + operation + ", target="+target.getClass().getName()+", method="+methodName+")";
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy