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.aspectj.annotation;
018:
019: import java.lang.annotation.Annotation;
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.Field;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Modifier;
024: import java.util.HashMap;
025: import java.util.Map;
026: import java.util.StringTokenizer;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.aspectj.lang.annotation.After;
031: import org.aspectj.lang.annotation.AfterReturning;
032: import org.aspectj.lang.annotation.AfterThrowing;
033: import org.aspectj.lang.annotation.Around;
034: import org.aspectj.lang.annotation.Aspect;
035: import org.aspectj.lang.annotation.Before;
036: import org.aspectj.lang.annotation.Pointcut;
037: import org.aspectj.lang.reflect.AjType;
038: import org.aspectj.lang.reflect.AjTypeSystem;
039: import org.aspectj.lang.reflect.PerClauseKind;
040:
041: import org.springframework.aop.aspectj.AspectJExpressionPointcut;
042: import org.springframework.aop.framework.AopConfigException;
043: import org.springframework.core.ParameterNameDiscoverer;
044: import org.springframework.core.PrioritizedParameterNameDiscoverer;
045: import org.springframework.core.annotation.AnnotationUtils;
046: import org.springframework.util.StringUtils;
047:
048: /**
049: * Abstract base class for factories that can create Spring AOP Advisors
050: * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax.
051: *
052: * <p>This class handles annotation parsing and validation functionality.
053: * It does not actually generate Spring AOP Advisors, which is deferred to subclasses.
054: *
055: * @author Rod Johnson
056: * @author Adrian Colyer
057: * @author Juergen Hoeller
058: * @since 2.0
059: */
060: public abstract class AbstractAspectJAdvisorFactory implements
061: AspectJAdvisorFactory {
062:
063: protected static final ParameterNameDiscoverer ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER = new AspectJAnnotationParameterNameDiscoverer();
064:
065: private static final String AJC_MAGIC = "ajc$";
066:
067: /**
068: * Find and return the first AspectJ annotation on the given method
069: * (there <i>should</i> only be one anyway...)
070: */
071: protected static AspectJAnnotation findAspectJAnnotationOnMethod(
072: Method aMethod) {
073: Class<? extends Annotation>[] classesToLookFor = (Class<? extends Annotation>[]) new Class[] {
074: Before.class, Around.class, After.class,
075: AfterReturning.class, AfterThrowing.class,
076: Pointcut.class };
077: for (Class<? extends Annotation> c : classesToLookFor) {
078: AspectJAnnotation foundAnnotation = findAnnotation(aMethod,
079: c);
080: if (foundAnnotation != null) {
081: return foundAnnotation;
082: }
083: }
084: return null;
085: }
086:
087: private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(
088: Method method, Class<A> toLookFor) {
089: A result = AnnotationUtils.findAnnotation(method, toLookFor);
090: if (result != null) {
091: return new AspectJAnnotation<A>(result);
092: } else {
093: return null;
094: }
095: }
096:
097: /** Logger available to subclasses */
098: protected final Log logger = LogFactory.getLog(getClass());
099:
100: protected final ParameterNameDiscoverer parameterNameDiscoverer;
101:
102: protected AbstractAspectJAdvisorFactory() {
103: PrioritizedParameterNameDiscoverer prioritizedParameterNameDiscoverer = new PrioritizedParameterNameDiscoverer();
104: prioritizedParameterNameDiscoverer
105: .addDiscoverer(ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER);
106: this .parameterNameDiscoverer = prioritizedParameterNameDiscoverer;
107: }
108:
109: /**
110: * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
111: * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
112: * is that aspects written in the code-style (AspectJ language) also have the annotation present
113: * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
114: */
115: public boolean isAspect(Class<?> clazz) {
116: return (AjTypeSystem.getAjType(clazz).isAspect()
117: && hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
118: }
119:
120: private boolean hasAspectAnnotation(Class<?> clazz) {
121: return clazz.isAnnotationPresent(Aspect.class);
122: }
123:
124: /**
125: * We need to detect this as "code-style" AspectJ aspects should not be
126: * interpreted by Spring AOP.
127: */
128: private boolean compiledByAjc(Class<?> clazz) {
129: // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
130: // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
131: // an implementation detail of the AspectJ compiler.
132: for (Field field : clazz.getDeclaredFields()) {
133: if (field.getName().startsWith(AJC_MAGIC)) {
134: return true;
135: }
136: }
137: return false;
138: }
139:
140: public void validate(Class<?> aspectClass)
141: throws AopConfigException {
142: // If the parent has the annotation and isn't abstract it's an error
143: if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null
144: && !Modifier.isAbstract(aspectClass.getSuperclass()
145: .getModifiers())) {
146: throw new AopConfigException("[" + aspectClass.getName()
147: + "] cannot extend concrete aspect ["
148: + aspectClass.getSuperclass().getName() + "]");
149: }
150:
151: AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
152: if (!ajType.isAspect()) {
153: throw new NotAnAtAspectException(aspectClass);
154: }
155: if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) {
156: throw new AopConfigException(aspectClass.getName()
157: + " uses percflow instantiation model: "
158: + "This is not supported in Spring AOP.");
159: }
160: if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) {
161: throw new AopConfigException(aspectClass.getName()
162: + " uses percflowbelow instantiation model: "
163: + "This is not supported in Spring AOP.");
164: }
165: }
166:
167: /**
168: * The pointcut and advice annotations both have an "argNames" member which contains a
169: * comma-separated list of the argument names. We use this (if non-empty) to build the
170: * formal parameters for the pointcut.
171: */
172: protected AspectJExpressionPointcut createPointcutExpression(
173: Method annotatedMethod, Class declarationScope,
174: String[] pointcutParameterNames) {
175:
176: Class<?>[] pointcutParameterTypes = new Class<?>[0];
177: if (pointcutParameterNames != null) {
178: pointcutParameterTypes = extractPointcutParameterTypes(
179: pointcutParameterNames, annotatedMethod);
180: }
181:
182: AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(
183: declarationScope, pointcutParameterNames,
184: pointcutParameterTypes);
185: ajexp.setLocation(annotatedMethod.toString());
186: return ajexp;
187: }
188:
189: /**
190: * Create the pointcut parameters needed by aspectj based on the given argument names
191: * and the argument types that are available from the adviceMethod. Needs to take into
192: * account (ignore) any JoinPoint based arguments as these are not pointcut context but
193: * rather part of the advice execution context (thisJoinPoint, thisJoinPointStaticPart)
194: */
195: private Class<?>[] extractPointcutParameterTypes(String[] argNames,
196: Method adviceMethod) {
197: Class<?>[] ret = new Class<?>[argNames.length];
198: Class<?>[] paramTypes = adviceMethod.getParameterTypes();
199: if (argNames.length > paramTypes.length) {
200: throw new IllegalStateException(
201: "Expecting at least "
202: + argNames.length
203: + " arguments in the advice declaration, but only found "
204: + paramTypes.length);
205: }
206: // Make the simplifying assumption for now that all of the JoinPoint based arguments
207: // come first in the advice declaration.
208: int typeOffset = paramTypes.length - argNames.length;
209: for (int i = 0; i < ret.length; i++) {
210: ret[i] = paramTypes[i + typeOffset];
211: }
212: return ret;
213: }
214:
215: protected enum AspectJAnnotationType {
216: AtPointcut, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing, AtAround
217: };
218:
219: /**
220: * Class modelling an AspectJ annotation, exposing its type enumeration and
221: * pointcut String.
222: */
223: protected static class AspectJAnnotation<A extends Annotation> {
224:
225: private static Map<Class, AspectJAnnotationType> annotationTypes = new HashMap<Class, AspectJAnnotationType>();
226:
227: private static final String[] EXPRESSION_PROPERTIES = new String[] {
228: "value", "pointcut" };
229:
230: static {
231: annotationTypes.put(Pointcut.class,
232: AspectJAnnotationType.AtPointcut);
233: annotationTypes.put(After.class,
234: AspectJAnnotationType.AtAfter);
235: annotationTypes.put(AfterReturning.class,
236: AspectJAnnotationType.AtAfterReturning);
237: annotationTypes.put(AfterThrowing.class,
238: AspectJAnnotationType.AtAfterThrowing);
239: annotationTypes.put(Around.class,
240: AspectJAnnotationType.AtAround);
241: annotationTypes.put(Before.class,
242: AspectJAnnotationType.AtBefore);
243: }
244:
245: private final A annotation;
246: private AspectJAnnotationType annotationType;
247: private final String expression;
248: private final String argNames;
249:
250: public AspectJAnnotation(A aspectjAnnotation) {
251: this .annotation = aspectjAnnotation;
252: for (Class type : annotationTypes.keySet()) {
253: if (type.isInstance(this .annotation)) {
254: this .annotationType = annotationTypes.get(type);
255: break;
256: }
257: }
258: if (this .annotationType == null) {
259: throw new IllegalStateException(
260: "Unknown annotation type: "
261: + this .annotation.toString());
262: }
263:
264: // We know these methods exist with the same name on each object,
265: // but need to invoke them reflectively as there isn't a common interface.
266: try {
267: this .expression = resolveExpression();
268: this .argNames = (String) this .annotation.getClass()
269: .getMethod("argNames", (Class[]) null).invoke(
270: this .annotation);
271: } catch (Exception ex) {
272: throw new IllegalArgumentException(aspectjAnnotation
273: + " cannot be an AspectJ annotation", ex);
274: }
275: }
276:
277: private String resolveExpression() throws Exception {
278: String expression = null;
279: for (int i = 0; i < EXPRESSION_PROPERTIES.length; i++) {
280: String methodName = EXPRESSION_PROPERTIES[i];
281: Method method;
282: try {
283: method = this .annotation.getClass()
284: .getDeclaredMethod(methodName);
285: } catch (NoSuchMethodException ex) {
286: method = null;
287: }
288: if (method != null) {
289: String candidate = (String) method
290: .invoke(this .annotation);
291: if (StringUtils.hasText(candidate)) {
292: expression = candidate;
293: }
294: }
295: }
296: return expression;
297: }
298:
299: public AspectJAnnotationType getAnnotationType() {
300: return this .annotationType;
301: }
302:
303: public A getAnnotation() {
304: return this .annotation;
305: }
306:
307: public String getPointcutExpression() {
308: return this .expression;
309: }
310:
311: public String getArgNames() {
312: return this .argNames;
313: }
314:
315: public String toString() {
316: return this .annotation.toString();
317: }
318: }
319:
320: /**
321: * ParameterNameDiscoverer implementation that analyzes the arg names
322: * specified at the AspectJ annotation level.
323: */
324: private static class AspectJAnnotationParameterNameDiscoverer
325: implements ParameterNameDiscoverer {
326:
327: public String[] getParameterNames(Method method) {
328: if (method.getParameterTypes().length == 0) {
329: return new String[0];
330: }
331: AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method);
332: if (annotation == null) {
333: return null;
334: }
335: StringTokenizer strTok = new StringTokenizer(annotation
336: .getArgNames(), ",");
337: if (strTok.countTokens() > 0) {
338: String[] names = new String[strTok.countTokens()];
339: for (int i = 0; i < names.length; i++) {
340: names[i] = strTok.nextToken();
341: }
342: return names;
343: } else {
344: return null;
345: }
346: }
347:
348: public String[] getParameterNames(Constructor ctor) {
349: throw new UnsupportedOperationException(
350: "Spring AOP cannot handle constructor advice");
351: }
352: }
353:
354: }
|