blob: a552678c64e10229d2b43ffde68df0bd00641db4 [file] [log] [blame]
/*
* 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 09:09:44 +0000 (Thu, 04 Aug 2011) $
*/
public class ReflectionInvocationContext implements InvocationContext {
private final Iterator<Interceptor> interceptors;
private final Object target;
private final Method method;
private final Object[] parameters;
private final Map<String, Object> contextData = new TreeMap<String, Object>();
private final Class<?>[] parameterTypes;
private final Operation operation;
public ReflectionInvocationContext(Operation operation, List<Interceptor> 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<String, Object> 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+")";
}
}