1 /*
2  * Copyright 2002-2010 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.springframework.aop.aspectj.annotation;
18 
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Modifier;
24 import java.util.HashMap;
25 import java.util.Map;
26 import java.util.StringTokenizer;
27 
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.aspectj.lang.annotation.After;
31 import org.aspectj.lang.annotation.AfterReturning;
32 import org.aspectj.lang.annotation.AfterThrowing;
33 import org.aspectj.lang.annotation.Around;
34 import org.aspectj.lang.annotation.Aspect;
35 import org.aspectj.lang.annotation.Before;
36 import org.aspectj.lang.annotation.Pointcut;
37 import org.aspectj.lang.reflect.AjType;
38 import org.aspectj.lang.reflect.AjTypeSystem;
39 import org.aspectj.lang.reflect.PerClauseKind;
40 
41 import org.springframework.aop.aspectj.AspectJExpressionPointcut;
42 import org.springframework.aop.framework.AopConfigException;
43 import org.springframework.core.ParameterNameDiscoverer;
44 import org.springframework.core.PrioritizedParameterNameDiscoverer;
45 import org.springframework.core.annotation.AnnotationUtils;
46 import org.springframework.util.StringUtils;
47 
48 /**
49  * Abstract base class for factories that can create Spring AOP Advisors
50  * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax.
51  *
52  * <p>This class handles annotation parsing and validation functionality.
53  * It does not actually generate Spring AOP Advisors, which is deferred to subclasses.
54  *
55  * @author Rod Johnson
56  * @author Adrian Colyer
57  * @author Juergen Hoeller
58  * @since 2.0
59  */
60 public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
61 
62 	protected static final ParameterNameDiscoverer ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER =
63 			new AspectJAnnotationParameterNameDiscoverer();
64 
65 	private static final String AJC_MAGIC = "ajc$";
66 
67 
68 	/**
69 	 * Find and return the first AspectJ annotation on the given method
70 	 * (there <i>should</i> only be one anyway...)
71 	 */
72 	@SuppressWarnings("unchecked")
findAspectJAnnotationOnMethod(Method method)73 	protected static AspectJAnnotation findAspectJAnnotationOnMethod(Method method) {
74 		Class<? extends Annotation>[] classesToLookFor = new Class[] {
75 				Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};
76 		for (Class<? extends Annotation> c : classesToLookFor) {
77 			AspectJAnnotation foundAnnotation = findAnnotation(method, c);
78 			if (foundAnnotation != null) {
79 				return foundAnnotation;
80 			}
81 		}
82 		return null;
83 	}
84 
findAnnotation(Method method, Class<A> toLookFor)85 	private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
86 		A result = AnnotationUtils.findAnnotation(method, toLookFor);
87 		if (result != null) {
88 			return new AspectJAnnotation<A>(result);
89 		}
90 		else {
91 			return null;
92 		}
93 	}
94 
95 
96 	/** Logger available to subclasses */
97 	protected final Log logger = LogFactory.getLog(getClass());
98 
99 	protected final ParameterNameDiscoverer parameterNameDiscoverer;
100 
101 
AbstractAspectJAdvisorFactory()102 	protected AbstractAspectJAdvisorFactory() {
103 		PrioritizedParameterNameDiscoverer prioritizedParameterNameDiscoverer = new PrioritizedParameterNameDiscoverer();
104 		prioritizedParameterNameDiscoverer.addDiscoverer(ASPECTJ_ANNOTATION_PARAMETER_NAME_DISCOVERER);
105 		this.parameterNameDiscoverer = prioritizedParameterNameDiscoverer;
106 	}
107 
108 	/**
109 	 * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
110 	 * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
111 	 * is that aspects written in the code-style (AspectJ language) also have the annotation present
112 	 * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
113 	 */
isAspect(Class<?> clazz)114 	public boolean isAspect(Class<?> clazz) {
115 		return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
116 	}
117 
hasAspectAnnotation(Class<?> clazz)118 	private boolean hasAspectAnnotation(Class<?> clazz) {
119 		return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
120 	}
121 
122 	/**
123 	 * We need to detect this as "code-style" AspectJ aspects should not be
124 	 * interpreted by Spring AOP.
125 	 */
compiledByAjc(Class<?> clazz)126 	private boolean compiledByAjc(Class<?> clazz) {
127 		// The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
128 		// annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
129 		// an implementation detail of the AspectJ compiler.
130 		for (Field field : clazz.getDeclaredFields()) {
131 			if (field.getName().startsWith(AJC_MAGIC)) {
132 				return true;
133 			}
134 		}
135 		return false;
136 	}
137 
validate(Class<?> aspectClass)138 	public void validate(Class<?> aspectClass) throws AopConfigException {
139 		// If the parent has the annotation and isn't abstract it's an error
140 		if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null &&
141 				!Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) {
142 			throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" +
143 					aspectClass.getSuperclass().getName() + "]");
144 		}
145 
146 		AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
147 		if (!ajType.isAspect()) {
148 			throw new NotAnAtAspectException(aspectClass);
149 		}
150 		if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) {
151 			throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " +
152 					"This is not supported in Spring AOP.");
153 		}
154 		if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) {
155 			throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " +
156 					"This is not supported in Spring AOP.");
157 		}
158 	}
159 
160 	/**
161 	 * The pointcut and advice annotations both have an "argNames" member which contains a
162 	 * comma-separated list of the argument names. We use this (if non-empty) to build the
163 	 * formal parameters for the pointcut.
164 	 */
createPointcutExpression( Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames)165 	protected AspectJExpressionPointcut createPointcutExpression(
166 			Method annotatedMethod, Class declarationScope, String[] pointcutParameterNames) {
167 
168 		Class<?> [] pointcutParameterTypes = new Class<?>[0];
169 		if (pointcutParameterNames != null) {
170 			pointcutParameterTypes = extractPointcutParameterTypes(pointcutParameterNames,annotatedMethod);
171 		}
172 
173 		AspectJExpressionPointcut ajexp =
174 				new AspectJExpressionPointcut(declarationScope,pointcutParameterNames,pointcutParameterTypes);
175 		ajexp.setLocation(annotatedMethod.toString());
176 		return ajexp;
177 	}
178 
179 	/**
180 	 * Create the pointcut parameters needed by aspectj based on the given argument names
181 	 * and the argument types that are available from the adviceMethod. Needs to take into
182 	 * account (ignore) any JoinPoint based arguments as these are not pointcut context but
183 	 * rather part of the advice execution context (thisJoinPoint, thisJoinPointStaticPart)
184 	 */
extractPointcutParameterTypes(String[] argNames, Method adviceMethod)185 	private Class<?>[] extractPointcutParameterTypes(String[] argNames, Method adviceMethod) {
186 		Class<?>[] ret = new Class<?>[argNames.length];
187 		Class<?>[] paramTypes = adviceMethod.getParameterTypes();
188 		if (argNames.length > paramTypes.length) {
189 			throw new IllegalStateException("Expecting at least " + argNames.length +
190 					" arguments in the advice declaration, but only found " + paramTypes.length);
191 		}
192 		// Make the simplifying assumption for now that all of the JoinPoint based arguments
193 		// come first in the advice declaration.
194 		int typeOffset = paramTypes.length - argNames.length;
195 		for (int i = 0; i < ret.length; i++) {
196 			ret[i] = paramTypes[i + typeOffset];
197 		}
198 		return ret;
199 	}
200 
201 
202 	protected enum AspectJAnnotationType {
203 		AtPointcut,
204 		AtBefore,
205 		AtAfter,
206 		AtAfterReturning,
207 		AtAfterThrowing,
208 		AtAround
209 	}
210 
211 
212 	/**
213 	 * Class modelling an AspectJ annotation, exposing its type enumeration and
214 	 * pointcut String.
215 	 */
216 	protected static class AspectJAnnotation<A extends Annotation> {
217 
218 		private static final String[] EXPRESSION_PROPERTIES = new String[] {"value", "pointcut"};
219 
220 		private static Map<Class, AspectJAnnotationType> annotationTypes =
221 				new HashMap<Class, AspectJAnnotationType>();
222 
223 		static {
annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut)224 			annotationTypes.put(Pointcut.class,AspectJAnnotationType.AtPointcut);
annotationTypes.put(After.class,AspectJAnnotationType.AtAfter)225 			annotationTypes.put(After.class,AspectJAnnotationType.AtAfter);
annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning)226 			annotationTypes.put(AfterReturning.class,AspectJAnnotationType.AtAfterReturning);
annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing)227 			annotationTypes.put(AfterThrowing.class,AspectJAnnotationType.AtAfterThrowing);
annotationTypes.put(Around.class,AspectJAnnotationType.AtAround)228 			annotationTypes.put(Around.class,AspectJAnnotationType.AtAround);
annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore)229 			annotationTypes.put(Before.class,AspectJAnnotationType.AtBefore);
230 		}
231 
232 		private final A annotation;
233 
234 		private final AspectJAnnotationType annotationType;
235 
236 		private final String pointcutExpression;
237 
238 		private final String argumentNames;
239 
AspectJAnnotation(A annotation)240 		public AspectJAnnotation(A annotation) {
241 			this.annotation = annotation;
242 			this.annotationType = determineAnnotationType(annotation);
243 			// We know these methods exist with the same name on each object,
244 			// but need to invoke them reflectively as there isn't a common interface.
245 			try {
246 				this.pointcutExpression = resolveExpression(annotation);
247 				this.argumentNames = (String) annotation.getClass().getMethod("argNames").invoke(annotation);
248 			}
249 			catch (Exception ex) {
250 				throw new IllegalArgumentException(annotation + " cannot be an AspectJ annotation", ex);
251 			}
252 		}
253 
determineAnnotationType(A annotation)254 		private AspectJAnnotationType determineAnnotationType(A annotation) {
255 			for (Class type : annotationTypes.keySet()) {
256 				if (type.isInstance(annotation)) {
257 					return annotationTypes.get(type);
258 				}
259 			}
260 			throw new IllegalStateException("Unknown annotation type: " + annotation.toString());
261 		}
262 
resolveExpression(A annotation)263 		private String resolveExpression(A annotation) throws Exception {
264 			String expression = null;
265 			for (String methodName : EXPRESSION_PROPERTIES) {
266 				Method method;
267 				try {
268 					method = annotation.getClass().getDeclaredMethod(methodName);
269 				}
270 				catch (NoSuchMethodException ex) {
271 					method = null;
272 				}
273 				if (method != null) {
274 					String candidate = (String) method.invoke(annotation);
275 					if (StringUtils.hasText(candidate)) {
276 						expression = candidate;
277 					}
278 				}
279 			}
280 			return expression;
281 		}
282 
getAnnotationType()283 		public AspectJAnnotationType getAnnotationType() {
284 			return this.annotationType;
285 		}
286 
getAnnotation()287 		public A getAnnotation() {
288 			return this.annotation;
289 		}
290 
getPointcutExpression()291 		public String getPointcutExpression() {
292 			return this.pointcutExpression;
293 		}
294 
getArgumentNames()295 		public String getArgumentNames() {
296 			return this.argumentNames;
297 		}
298 
299 		@Override
toString()300 		public String toString() {
301 			return this.annotation.toString();
302 		}
303 	}
304 
305 
306 	/**
307 	 * ParameterNameDiscoverer implementation that analyzes the arg names
308 	 * specified at the AspectJ annotation level.
309 	 */
310 	private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
311 
getParameterNames(Method method)312 		public String[] getParameterNames(Method method) {
313 			if (method.getParameterTypes().length == 0) {
314 				return new String[0];
315 			}
316 			AspectJAnnotation annotation = findAspectJAnnotationOnMethod(method);
317 			if (annotation == null) {
318 				return null;
319 			}
320 			StringTokenizer strTok = new StringTokenizer(annotation.getArgumentNames(), ",");
321 			if (strTok.countTokens() > 0) {
322 				String[] names = new String[strTok.countTokens()];
323 				for (int i = 0; i < names.length; i++) {
324 					names[i] = strTok.nextToken();
325 				}
326 				return names;
327 			}
328 			else {
329 				return null;
330 			}
331 		}
332 
getParameterNames(Constructor ctor)333 		public String[] getParameterNames(Constructor ctor) {
334 			throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice");
335 		}
336 	}
337 
338 }
339