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: * @author Rod Johnson
047: * @author Juergen Hoeller
048: * @author Adrian Colyer
049: * @see #invokeJoinpoint
050: * @see #proceed
051: * @see #invocableClone
052: * @see #setUserAttribute
053: * @see #getUserAttribute
054: */
055: public class ReflectiveMethodInvocation implements
056: ProxyMethodInvocation, Cloneable {
057:
058: protected final Object proxy;
059:
060: protected final Object target;
061:
062: protected final Method method;
063:
064: protected Object[] arguments;
065:
066: private final Class targetClass;
067:
068: /**
069: * Lazily initialized map of user-specific attributes for this invocation.
070: */
071: private Map userAttributes;
072:
073: /**
074: * List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
075: * that need dynamic checks.
076: */
077: protected final List interceptorsAndDynamicMethodMatchers;
078:
079: /**
080: * Index from 0 of the current interceptor we're invoking.
081: * -1 until we invoke: then the current interceptor.
082: */
083: private int currentInterceptorIndex = -1;
084:
085: /**
086: * Construct a new ReflectiveMethodInvocation with the given arguments.
087: * @param proxy the proxy object that the invocation was made on
088: * @param target the target object to invoke
089: * @param method the method to invoke
090: * @param arguments the arguments to invoke the method with
091: * @param targetClass the target class, for MethodMatcher invocations
092: * @param interceptorsAndDynamicMethodMatchers interceptors that should be applied,
093: * along with any InterceptorAndDynamicMethodMatchers that need evaluation at runtime.
094: * MethodMatchers included in this struct must already have been found to have matched
095: * as far as was possibly statically. Passing an array might be about 10% faster,
096: * but would complicate the code. And it would work only for static pointcuts.
097: */
098: public ReflectiveMethodInvocation(Object proxy, Object target,
099: Method method, Object[] arguments, Class targetClass,
100: List interceptorsAndDynamicMethodMatchers) {
101:
102: this .proxy = proxy;
103: this .target = target;
104: this .targetClass = targetClass;
105: this .method = method;
106: this .arguments = arguments;
107: this .interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
108: }
109:
110: public final Object getProxy() {
111: return this .proxy;
112: }
113:
114: public final Object getThis() {
115: return this .target;
116: }
117:
118: public final AccessibleObject getStaticPart() {
119: return this .method;
120: }
121:
122: /**
123: * Return the method invoked on the proxied interface.
124: * May or may not correspond with a method invoked on an underlying
125: * implementation of that interface.
126: */
127: public final Method getMethod() {
128: return this .method;
129: }
130:
131: public final Object[] getArguments() {
132: return (this .arguments != null ? this .arguments : new Object[0]);
133: }
134:
135: public void setArguments(Object[] arguments) {
136: this .arguments = arguments;
137: }
138:
139: public Object proceed() throws Throwable {
140: // We start with an index of -1 and increment early.
141: if (this .currentInterceptorIndex == this .interceptorsAndDynamicMethodMatchers
142: .size() - 1) {
143: return invokeJoinpoint();
144: }
145:
146: Object interceptorOrInterceptionAdvice = this .interceptorsAndDynamicMethodMatchers
147: .get(++this .currentInterceptorIndex);
148: if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
149: // Evaluate dynamic method matcher here: static part will already have
150: // been evaluated and found to match.
151: InterceptorAndDynamicMethodMatcher dm = (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
152: if (dm.methodMatcher.matches(this .method, this .targetClass,
153: this .arguments)) {
154: return dm.interceptor.invoke(this );
155: } else {
156: // Dynamic matching failed.
157: // Skip this interceptor and invoke the next in the chain.
158: return proceed();
159: }
160: } else {
161: // It's an interceptor, so we just invoke it: The pointcut will have
162: // been evaluated statically before this object was constructed.
163: return ((MethodInterceptor) interceptorOrInterceptionAdvice)
164: .invoke(this );
165: }
166: }
167:
168: /**
169: * Invoke the joinpoint using reflection.
170: * Subclasses can override this to use custom invocation.
171: * @return the return value of the joinpoint
172: * @throws Throwable if invoking the joinpoint resulted in an exception
173: */
174: protected Object invokeJoinpoint() throws Throwable {
175: return AopUtils.invokeJoinpointUsingReflection(this .target,
176: this .method, this .arguments);
177: }
178:
179: /**
180: * This implementation returns a shallow copy of this invocation object,
181: * including an independent copy of the original arguments array.
182: * <p>We want a shallow copy in this case: We want to use the same interceptor
183: * chain and other object references, but we want an independent value for the
184: * current interceptor index.
185: * @see java.lang.Object#clone()
186: */
187: public MethodInvocation invocableClone() {
188: Object[] cloneArguments = null;
189: if (this .arguments != null) {
190: // Build an independent copy of the arguments array.
191: cloneArguments = new Object[this .arguments.length];
192: System.arraycopy(this .arguments, 0, cloneArguments, 0,
193: this .arguments.length);
194: }
195: return invocableClone(cloneArguments);
196: }
197:
198: /**
199: * This implementation returns a shallow copy of this invocation object,
200: * using the given arguments array for the clone.
201: * <p>We want a shallow copy in this case: We want to use the same interceptor
202: * chain and other object references, but we want an independent value for the
203: * current interceptor index.
204: * @see java.lang.Object#clone()
205: */
206: public MethodInvocation invocableClone(Object[] arguments) {
207: // Force initialization of the user attributes Map,
208: // for having a shared Map reference in the clone.
209: if (this .userAttributes == null) {
210: this .userAttributes = new HashMap();
211: }
212:
213: // Create the MethodInvocation clone.
214: try {
215: ReflectiveMethodInvocation clone = (ReflectiveMethodInvocation) clone();
216: clone.arguments = arguments;
217: return clone;
218: } catch (CloneNotSupportedException ex) {
219: throw new IllegalStateException(
220: "Should be able to clone object of type ["
221: + getClass() + "]: " + ex);
222: }
223: }
224:
225: public void setUserAttribute(String key, Object value) {
226: if (value != null) {
227: if (this .userAttributes == null) {
228: this .userAttributes = new HashMap();
229: }
230: this .userAttributes.put(key, value);
231: } else {
232: if (this .userAttributes != null) {
233: this .userAttributes.remove(key);
234: }
235: }
236: }
237:
238: public Object getUserAttribute(String key) {
239: return (this .userAttributes != null ? this .userAttributes
240: .get(key) : null);
241: }
242:
243: /**
244: * Return user attributes associated with this invocation.
245: * This method provides an invocation-bound alternative to a ThreadLocal.
246: * <p>This map is initialized lazily and is not used in the AOP framework itself.
247: * @return any user attributes associated with this invocation
248: * (never <code>null</code>)
249: */
250: public Map getUserAttributes() {
251: if (this .userAttributes == null) {
252: this .userAttributes = new HashMap();
253: }
254: return this .userAttributes;
255: }
256:
257: public String toString() {
258: // Don't do toString on target, it may be proxied.
259: StringBuffer sb = new StringBuffer(
260: "ReflectiveMethodInvocation: ");
261: sb.append(this .method).append("; ");
262: if (this .target == null) {
263: sb.append("target is null");
264: } else {
265: sb.append("target is of class [").append(
266: this .target.getClass().getName()).append(']');
267: }
268: return sb.toString();
269: }
270:
271: }
|