1 /**
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one or more
4 * contributor license agreements. See the NOTICE file distributed with
5 * this work for additional information regarding copyright ownership.
6 * The ASF licenses this file to You under the Apache License, Version 2.0
7 * (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package org.apache.openejb.core.interceptor;
19
20 import org.apache.openejb.core.Operation;
21
22 import javax.interceptor.InvocationContext;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.List;
26 import java.util.TreeMap;
27 import java.lang.reflect.Method;
28 import java.lang.reflect.InvocationTargetException;
29
30 /**
31 * @version $Rev: 634462 $ $Date: 2008-03-06 15:45:47 -0800 (Thu, 06 Mar 2008) $
32 */
33 public class ReflectionInvocationContext implements InvocationContext {
34 private final Iterator<Interceptor> interceptors;
35 private final Object target;
36 private final Method method;
37 private final Object[] parameters;
38 private final Map<String, Object> contextData = new TreeMap<String, Object>();
39 private final Class<?>[] parameterTypes;
40
41 private final Operation operation;
42
43 public ReflectionInvocationContext(Operation operation, List<Interceptor> interceptors, Object target, Method method, Object... parameters) {
44 if (operation == null) throw new NullPointerException("operation is null");
45 if (interceptors == null) throw new NullPointerException("interceptors is null");
46 if (target == null) throw new NullPointerException("target is null");
47
48 this.operation = operation;
49 this.interceptors = interceptors.iterator();
50 this.target = target;
51 this.method = method;
52 this.parameters = parameters;
53
54 if (method == null) {
55 parameterTypes = new Class[0];
56 } else {
57 parameterTypes = method.getParameterTypes();
58 }
59 }
60
61 public Object getTarget() {
62 return target;
63 }
64
65 public Method getMethod() {
66 return method;
67 }
68
69 public Object[] getParameters() {
70 if (operation.isCallback()) {
71 throw new IllegalStateException(getIllegalParameterAccessMessage());
72 }
73 return parameters.clone();
74 }
75
76 private String getIllegalParameterAccessMessage() {
77 String m = "Callback methods cannot access parameters.";
78 m += " Callback Type: "+operation;
79 if (method!= null){
80 m += ", Target Method: " + method.getName();
81 }
82 if (target != null){
83 m += ", Target Bean: "+target.getClass().getName();
84 }
85 return m;
86 }
87
88 public void setParameters(Object[] parameters) {
89 if (operation.isCallback()) {
90 throw new IllegalStateException(getIllegalParameterAccessMessage());
91 }
92 if (parameters == null) throw new NullPointerException("parameters is null");
93 if (parameters.length != this.parameters.length) {
94 throw new IllegalArgumentException("Expected " + this.parameters.length + " parameters, but only got " + parameters.length + " parameters");
95 }
96 // for (int i = 0; i < parameters.length; i++) {
97 // Object parameter = parameters[i];
98 // Class<?> parameterType = parameterTypes[i];
99 //
100 // if (parameter == null) {
101 // if (parameterType.isPrimitive()) {
102 // throw new IllegalArgumentException("Expected parameter " + i + " to be primitive type " + parameterType.getName() +
103 // ", but got a parameter that is null");
104 // }
105 // } else if (!parameterType.isInstance(parameter)) {
106 // throw new IllegalArgumentException("Expected parameter " + i + " to be of type " + parameterType.getName() +
107 // ", but got a parameter of type " + parameter.getClass().getName());
108 // }
109 // }
110 System.arraycopy(parameters, 0, this.parameters, 0, parameters.length);
111 }
112
113 public Map<String, Object> getContextData() {
114 return contextData;
115 }
116
117 private Invocation next() {
118 if (interceptors.hasNext()) {
119 Interceptor interceptor = interceptors.next();
120 Object nextInstance = interceptor.getInstance();
121 Method nextMethod = interceptor.getMethod();
122
123 if (nextMethod.getParameterTypes().length > 0){
124 return new InterceptorInvocation(nextInstance, nextMethod, this);
125 } else {
126 return new LifecycleInvocation(nextInstance, nextMethod, this);
127 }
128 } else if (method != null) {
129 return new BeanInvocation(target, method, parameters);
130 } else {
131 return new NoOpInvocation();
132 }
133 }
134
135 public Object proceed() throws Exception {
136 // The bulk of the logic of this method has intentionally been moved
137 // out so stepping through a large stack in a debugger can be done quickly.
138 // Simply put one break point on 'next.invoke()' or one inside that method.
139 try {
140 Invocation next = next();
141 return next.invoke();
142 } catch (InvocationTargetException e) {
143 throw unwrapInvocationTargetException(e);
144 }
145 }
146
147 private abstract static class Invocation {
148 private final Method method;
149 private final Object[] args;
150 private final Object target;
151
152 public Invocation(Object target, Method method, Object[] args) {
153 this.target = target;
154 this.method = method;
155 this.args = args;
156 }
157 public Object invoke() throws Exception {
158 Object value = method.invoke(target, args);
159 return value;
160 }
161
162 public String toString() {
163 return method.getDeclaringClass().getName() + "." + method.getName();
164 }
165 }
166
167 private static class BeanInvocation extends Invocation {
168 public BeanInvocation(Object target, Method method, Object[] args) {
169 super(target, method, args);
170 }
171 }
172
173 private static class InterceptorInvocation extends Invocation {
174 public InterceptorInvocation(Object target, Method method, InvocationContext invocationContext) {
175 super(target, method, new Object[] {invocationContext});
176 }
177 }
178
179 private static class LifecycleInvocation extends Invocation {
180 private final InvocationContext invocationContext;
181
182 public LifecycleInvocation(Object target, Method method, InvocationContext invocationContext) {
183 super(target, method, new Object[] {});
184 this.invocationContext = invocationContext;
185 }
186
187 public Object invoke() throws Exception {
188 // invoke the callback
189 super.invoke();
190
191 // we need to call proceed so callbacks in subclasses get invoked
192 Object value = invocationContext.proceed();
193 return value;
194 }
195 }
196
197 private static class NoOpInvocation extends Invocation {
198 public NoOpInvocation() {
199 super(null, null, null);
200 }
201
202 public Object invoke() throws IllegalAccessException, InvocationTargetException {
203 return null;
204 }
205 }
206
207 // todo verify excpetion types
208
209 /**
210 * Business method interceptors can only throw exception allowed by the target business method.
211 * Lifecycle interceptors can only throw RuntimeException.
212 * @param e the invocation target exception of a reflection method invoke
213 * @return the cause of the exception
214 * @throws AssertionError if the cause is not an Exception or Error.
215 */
216 private Exception unwrapInvocationTargetException(InvocationTargetException e) {
217 Throwable cause = e.getCause();
218 if (cause == null) {
219 return e;
220 } else if (cause instanceof Exception) {
221 return (Exception) cause;
222 } else if (cause instanceof Error) {
223 throw (Error) cause;
224 } else {
225 throw new AssertionError(cause);
226 }
227 }
228
229 public String toString() {
230 String methodName = (method != null)? method.getName(): null;
231
232 return "InvocationContext(operation=" + operation + ", target="+target.getClass().getName()+", method="+methodName+")";
233 }
234 }