001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.aop.framework;
018:
019: import java.lang.reflect.AccessibleObject;
020: import java.lang.reflect.Method;
021: import java.util.HashMap;
022: import java.util.List;
023: import java.util.Map;
024:
025: import org.aopalliance.intercept.MethodInterceptor;
026: import org.aopalliance.intercept.MethodInvocation;
027:
028: import org.springframework.aop.ProxyMethodInvocation;
029: import org.springframework.aop.support.AopUtils;
030:
031: /**
032: * Spring's implementation of the AOP Alliance
033: * {@link org.aopalliance.intercept.MethodInvocation} interface,
034: * implementing the extended
035: * {@link org.springframework.aop.ProxyMethodInvocation} interface.
036: *
037: * <p>Invokes the target object using reflection. Subclasses can override the
038: * {@link #invokeJoinpoint()} method to change this behavior, so this is also
039: * a useful base class for more specialized MethodInvocation implementations.
040: *
041: * <p>It is possible to clone an invocation, to invoke {@link #proceed()}
042: * repeatedly (once per clone), using the {@link #invocableClone()} method.
043: * It is also possible to attach custom attributes to the invocation,
044: * using the {@link #setUserAttribute} / {@link #getUserAttribute} methods.
045: *
046: * <p><b>NOTE:</b> This class is considered internal and should not be
047: * directly accessed. The sole reason for it being public is compatibility
048: * with existing framework integrations (e.g. Pitchfork). For any other
049: * purposes, use the {@link ProxyMethodInvocation} interface instead.
050: *
051: * @author Rod Johnson
052: * @author Juergen Hoeller
053: * @author Adrian Colyer
054: * @see #invokeJoinpoint
055: * @see #proceed
056: * @see #invocableClone
057: * @see #setUserAttribute
058: * @see #getUserAttribute
059: */
060: public class ReflectiveMethodInvocation implements
061: ProxyMethodInvocation, Cloneable {
062:
063: protected final Object proxy;
064:
065: protected final Object target;
066:
067: protected final Method method;
068:
069: protected Object[] arguments;
070:
071: private final Class targetClass;
072:
073: /**
074: * Lazily initialized map of user-specific attributes for this invocation.
075: */
076: private Map userAttributes;
077:
078: /**
079: * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
080: * that need dynamic checks.
081: */
082: protected final List interceptorsAndDynamicMethodMatchers;
083:
084: /**
085: * Index from 0 of the current interceptor we're invoking.
086: * -1 until we invoke: then the current interceptor.
087: */
088: private int currentInterceptorIndex = -1;
089:
090: /**
091: * Construct a new ReflectiveMethodInvocation with the given arguments.
092: * @param proxy the proxy object that the invocation was made on
093: * @param target the target object to invoke
094: * @param method the method to invoke
095: * @param arguments the arguments to invoke the method with
096: * @param targetClass the target class, for MethodMatcher invocations
097: * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
098: * along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
099: * MethodMatchers included in this struct must already have been found to have matched
100: * as far as was possibly statically. Passing an array might be about 10% faster,
101: * but would complicate the code. And it would work only for static pointcuts.
102: */
103: protected ReflectiveMethodInvocation(Object proxy, Object target,
104: Method method, Object[] arguments, Class targetClass,
105: List interceptorsAndDynamicMethodMatchers) {
106:
107: this .proxy = proxy;
108: this .target = target;
109: this .targetClass = targetClass;
110: this .method = method;
111: this .arguments = arguments;
112: this .interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
113: }
114:
115: public final Object getProxy() {
116: return this .proxy;
117: }
118:
119: public final Object getThis() {
120: return this .target;
121: }
122:
123: public final AccessibleObject getStaticPart() {
124: return this .method;
125: }
126:
127: /**
128: * Return the method invoked on the proxied interface.
129: * May or may not correspond with a method invoked on an underlying
130: * implementation of that interface.
131: */
132: public final Method getMethod() {
133: return this .method;
134: }
135:
136: public final Object[] getArguments() {
137: return (this .arguments != null ? this .arguments : new Object[0]);
138: }
139:
140: public void setArguments(Object[] arguments) {
141: this .arguments = arguments;
142: }
143:
144: public Object proceed() throws Throwable {
145: // We start with an index of -1 and increment early.
146: if (this .currentInterceptorIndex == this .interceptorsAndDynamicMethodMatchers
147: .size() - 1) {
148: return invokeJoinpoint();
149: }
150:
151: Object interceptorOrInterceptionAdvice = this .interceptorsAndDynamicMethodMatchers
152: .get(++this .currentInterceptorIndex);
153: if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
154: // Evaluate dynamic method matcher here: static part will already have
155: // been evaluated and found to match.
156: InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
157: if (dm.methodMatcher.matches(this .method, this .targetClass,
158: this .arguments)) {
159: return dm.interceptor.invoke(this );
160: } else {
161: // Dynamic matching failed.
162: // Skip this interceptor and invoke the next in the chain.
163: return proceed();
164: }
165: } else {
166: // It's an interceptor, so we just invoke it: The pointcut will have
167: // been evaluated statically before this object was constructed.
168: return ((MethodInterceptor) interceptorOrInterceptionAdvice)
169: .invoke(this );
170: }
171: }
172:
173: /**
174: * Invoke the joinpoint using reflection.
175: * Subclasses can override this to use custom invocation.
176: * @return the return value of the joinpoint
177: * @throws Throwable if invoking the joinpoint resulted in an exception
178: */
179: protected Object invokeJoinpoint() throws Throwable {
180: return AopUtils.invokeJoinpointUsingReflection(this .target,
181: this .method, this .arguments);
182: }
183:
184: /**
185: * This implementation returns a shallow copy of this invocation object,
186: * including an independent copy of the original arguments array.
187: * <p>We want a shallow copy in this case: We want to use the same interceptor
188: * chain and other object references, but we want an independent value for the
189: * current interceptor index.
190: * @see java.lang.Object#clone()
191: */
192: public MethodInvocation invocableClone() {
193: Object[] cloneArguments = null;
194: if (this .arguments != null) {
195: // Build an independent copy of the arguments array.
196: cloneArguments = new Object[this .arguments.length];
197: System.arraycopy(this .arguments, 0, cloneArguments, 0,
198: this .arguments.length);
199: }
200: return invocableClone(cloneArguments);
201: }
202:
203: /**
204: * This implementation returns a shallow copy of this invocation object,
205: * using the given arguments array for the clone.
206: * <p>We want a shallow copy in this case: We want to use the same interceptor
207: * chain and other object references, but we want an independent value for the
208: * current interceptor index.
209: * @see java.lang.Object#clone()
210: */
211: public MethodInvocation invocableClone(Object[] arguments) {
212: // Force initialization of the user attributes Map,
213: // for having a shared Map reference in the clone.
214: if (this .userAttributes == null) {
215: this .userAttributes = new HashMap();
216: }
217:
218: // Create the MethodInvocation clone.
219: try {
220: ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
221: clone.arguments = arguments;
222: return clone;
223: } catch (CloneNotSupportedException ex) {
224: throw new IllegalStateException(
225: "Should be able to clone object of type ["
226: + getClass() + "]: " + ex);
227: }
228: }
229:
230: public void setUserAttribute(String key, Object value) {
231: if (value != null) {
232: if (this .userAttributes == null) {
233: this .userAttributes = new HashMap();
234: }
235: this .userAttributes.put(key, value);
236: } else {
237: if (this .userAttributes != null) {
238: this .userAttributes.remove(key);
239: }
240: }
241: }
242:
243: public Object getUserAttribute(String key) {
244: return (this .userAttributes != null ? this .userAttributes
245: .get(key) : null);
246: }
247:
248: /**
249: * Return user attributes associated with this invocation.
250: * This method provides an invocation-bound alternative to a ThreadLocal.
251: * <p>This map is initialized lazily and is not used in the AOP framework itself.
252: * @return any user attributes associated with this invocation
253: * (never <code>null</code>)
254: */
255: public Map getUserAttributes() {
256: if (this .userAttributes == null) {
257: this .userAttributes = new HashMap();
258: }
259: return this .userAttributes;
260: }
261:
262: public String toString() {
263: // Don't do toString on target, it may be proxied.
264: StringBuffer sb = new StringBuffer(
265: "ReflectiveMethodInvocation: ");
266: sb.append(this .method).append("; ");
267: if (this .target == null) {
268: sb.append("target is null");
269: } else {
270: sb.append("target is of class [").append(
271: this .target.getClass().getName()).append(']');
272: }
273: return sb.toString();
274: }
275:
276: }
|