001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.aop.aspectj.annotation;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Constructor;
021import java.lang.reflect.Field;
022import java.lang.reflect.Method;
023import java.lang.reflect.Modifier;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.StringTokenizer;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.aspectj.lang.annotation.After;
031import org.aspectj.lang.annotation.AfterReturning;
032import org.aspectj.lang.annotation.AfterThrowing;
033import org.aspectj.lang.annotation.Around;
034import org.aspectj.lang.annotation.Aspect;
035import org.aspectj.lang.annotation.Before;
036import org.aspectj.lang.annotation.Pointcut;
037import org.aspectj.lang.reflect.AjType;
038import org.aspectj.lang.reflect.AjTypeSystem;
039import org.aspectj.lang.reflect.PerClauseKind;
040
041import org.springframework.aop.framework.AopConfigException;
042import org.springframework.core.ParameterNameDiscoverer;
043import org.springframework.core.annotation.AnnotationUtils;
044import org.springframework.util.StringUtils;
045
046/**
047 * Abstract base class for factories that can create Spring AOP Advisors
048 * given AspectJ classes from classes honoring the AspectJ 5 annotation syntax.
049 *
050 * <p>This class handles annotation parsing and validation functionality.
051 * It does not actually generate Spring AOP Advisors, which is deferred to subclasses.
052 *
053 * @author Rod Johnson
054 * @author Adrian Colyer
055 * @author Juergen Hoeller
056 * @since 2.0
057 */
058public abstract class AbstractAspectJAdvisorFactory implements AspectJAdvisorFactory {
059
060        private static final String AJC_MAGIC = "ajc$";
061
062        private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
063                        Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};
064
065
066        /** Logger available to subclasses */
067        protected final Log logger = LogFactory.getLog(getClass());
068
069        protected final ParameterNameDiscoverer parameterNameDiscoverer = new AspectJAnnotationParameterNameDiscoverer();
070
071
072        /**
073         * We consider something to be an AspectJ aspect suitable for use by the Spring AOP system
074         * if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test
075         * is that aspects written in the code-style (AspectJ language) also have the annotation present
076         * when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.
077         */
078        @Override
079        public boolean isAspect(Class<?> clazz) {
080                return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
081        }
082
083        private boolean hasAspectAnnotation(Class<?> clazz) {
084                return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
085        }
086
087        /**
088         * We need to detect this as "code-style" AspectJ aspects should not be
089         * interpreted by Spring AOP.
090         */
091        private boolean compiledByAjc(Class<?> clazz) {
092                // The AJTypeSystem goes to great lengths to provide a uniform appearance between code-style and
093                // annotation-style aspects. Therefore there is no 'clean' way to tell them apart. Here we rely on
094                // an implementation detail of the AspectJ compiler.
095                for (Field field : clazz.getDeclaredFields()) {
096                        if (field.getName().startsWith(AJC_MAGIC)) {
097                                return true;
098                        }
099                }
100                return false;
101        }
102
103        @Override
104        public void validate(Class<?> aspectClass) throws AopConfigException {
105                // If the parent has the annotation and isn't abstract it's an error
106                if (aspectClass.getSuperclass().getAnnotation(Aspect.class) != null &&
107                                !Modifier.isAbstract(aspectClass.getSuperclass().getModifiers())) {
108                        throw new AopConfigException("[" + aspectClass.getName() + "] cannot extend concrete aspect [" +
109                                        aspectClass.getSuperclass().getName() + "]");
110                }
111
112                AjType<?> ajType = AjTypeSystem.getAjType(aspectClass);
113                if (!ajType.isAspect()) {
114                        throw new NotAnAtAspectException(aspectClass);
115                }
116                if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOW) {
117                        throw new AopConfigException(aspectClass.getName() + " uses percflow instantiation model: " +
118                                        "This is not supported in Spring AOP.");
119                }
120                if (ajType.getPerClause().getKind() == PerClauseKind.PERCFLOWBELOW) {
121                        throw new AopConfigException(aspectClass.getName() + " uses percflowbelow instantiation model: " +
122                                        "This is not supported in Spring AOP.");
123                }
124        }
125
126        /**
127         * Find and return the first AspectJ annotation on the given method
128         * (there <i>should</i> only be one anyway...).
129         */
130        @SuppressWarnings("unchecked")
131        protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
132                for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
133                        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
134                        if (foundAnnotation != null) {
135                                return foundAnnotation;
136                        }
137                }
138                return null;
139        }
140
141        private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
142                A result = AnnotationUtils.findAnnotation(method, toLookFor);
143                if (result != null) {
144                        return new AspectJAnnotation<A>(result);
145                }
146                else {
147                        return null;
148                }
149        }
150
151
152        /**
153         * Enum for AspectJ annotation types.
154         * @see AspectJAnnotation#getAnnotationType()
155         */
156        protected enum AspectJAnnotationType {
157
158                AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing
159        }
160
161
162        /**
163         * Class modelling an AspectJ annotation, exposing its type enumeration and
164         * pointcut String.
165         */
166        protected static class AspectJAnnotation<A extends Annotation> {
167
168                private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"};
169
170                private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap =
171                                new HashMap<Class<?>, AspectJAnnotationType>(8);
172
173                static {
174                        annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
175                        annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
176                        annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
177                        annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
178                        annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
179                        annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
180                }
181
182                private final A annotation;
183
184                private final AspectJAnnotationType annotationType;
185
186                private final String pointcutExpression;
187
188                private final String argumentNames;
189
190                public AspectJAnnotation(A annotation) {
191                        this.annotation = annotation;
192                        this.annotationType = determineAnnotationType(annotation);
193                        try {
194                                this.pointcutExpression = resolveExpression(annotation);
195                                this.argumentNames = (String) AnnotationUtils.getValue(annotation, "argNames");
196                        }
197                        catch (Exception ex) {
198                                throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
199                        }
200                }
201
202                private AspectJAnnotationType determineAnnotationType(A annotation) {
203                        AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
204                        if (type != null) {
205                                return type;
206                        }
207                        throw new IllegalStateException("Unknown annotation type: " + annotation);
208                }
209
210                private String resolveExpression(A annotation) {
211                        for (String attributeName : EXPRESSION_ATTRIBUTES) {
212                                String candidate = (String) AnnotationUtils.getValue(annotation, attributeName);
213                                if (StringUtils.hasText(candidate)) {
214                                        return candidate;
215                                }
216                        }
217                        throw new IllegalStateException("Failed to resolve expression: " + annotation);
218                }
219
220                public AspectJAnnotationType getAnnotationType() {
221                        return this.annotationType;
222                }
223
224                public A getAnnotation() {
225                        return this.annotation;
226                }
227
228                public String getPointcutExpression() {
229                        return this.pointcutExpression;
230                }
231
232                public String getArgumentNames() {
233                        return this.argumentNames;
234                }
235
236                @Override
237                public String toString() {
238                        return this.annotation.toString();
239                }
240        }
241
242
243        /**
244         * ParameterNameDiscoverer implementation that analyzes the arg names
245         * specified at the AspectJ annotation level.
246         */
247        private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
248
249                @Override
250                public String[] getParameterNames(Method method) {
251                        if (method.getParameterTypes().length == 0) {
252                                return new String[0];
253                        }
254                        AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method);
255                        if (annotation == null) {
256                                return null;
257                        }
258                        StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ",");
259                        if (nameTokens.countTokens() > 0) {
260                                String[] names = new String[nameTokens.countTokens()];
261                                for (int i = 0; i < names.length; i++) {
262                                        names[i] = nameTokens.nextToken();
263                                }
264                                return names;
265                        }
266                        else {
267                                return null;
268                        }
269                }
270
271                @Override
272                public String[] getParameterNames(Constructor<?> ctor) {
273                        throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice");
274                }
275        }
276
277}