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.lang.Nullable;
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        @Nullable
132        protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
133                for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
134                        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
135                        if (foundAnnotation != null) {
136                                return foundAnnotation;
137                        }
138                }
139                return null;
140        }
141
142        @Nullable
143        private static <A extends Annotation> AspectJAnnotation<A> findAnnotation(Method method, Class<A> toLookFor) {
144                A result = AnnotationUtils.findAnnotation(method, toLookFor);
145                if (result != null) {
146                        return new AspectJAnnotation<>(result);
147                }
148                else {
149                        return null;
150                }
151        }
152
153
154        /**
155         * Enum for AspectJ annotation types.
156         * @see AspectJAnnotation#getAnnotationType()
157         */
158        protected enum AspectJAnnotationType {
159
160                AtPointcut, AtAround, AtBefore, AtAfter, AtAfterReturning, AtAfterThrowing
161        }
162
163
164        /**
165         * Class modelling an AspectJ annotation, exposing its type enumeration and
166         * pointcut String.
167         * @param <A> the annotation type
168         */
169        protected static class AspectJAnnotation<A extends Annotation> {
170
171                private static final String[] EXPRESSION_ATTRIBUTES = new String[] {"pointcut", "value"};
172
173                private static Map<Class<?>, AspectJAnnotationType> annotationTypeMap = new HashMap<>(8);
174
175                static {
176                        annotationTypeMap.put(Pointcut.class, AspectJAnnotationType.AtPointcut);
177                        annotationTypeMap.put(Around.class, AspectJAnnotationType.AtAround);
178                        annotationTypeMap.put(Before.class, AspectJAnnotationType.AtBefore);
179                        annotationTypeMap.put(After.class, AspectJAnnotationType.AtAfter);
180                        annotationTypeMap.put(AfterReturning.class, AspectJAnnotationType.AtAfterReturning);
181                        annotationTypeMap.put(AfterThrowing.class, AspectJAnnotationType.AtAfterThrowing);
182                }
183
184                private final A annotation;
185
186                private final AspectJAnnotationType annotationType;
187
188                private final String pointcutExpression;
189
190                private final String argumentNames;
191
192                public AspectJAnnotation(A annotation) {
193                        this.annotation = annotation;
194                        this.annotationType = determineAnnotationType(annotation);
195                        try {
196                                this.pointcutExpression = resolveExpression(annotation);
197                                Object argNames = AnnotationUtils.getValue(annotation, "argNames");
198                                this.argumentNames = (argNames instanceof String ? (String) argNames : "");
199                        }
200                        catch (Exception ex) {
201                                throw new IllegalArgumentException(annotation + " is not a valid AspectJ annotation", ex);
202                        }
203                }
204
205                private AspectJAnnotationType determineAnnotationType(A annotation) {
206                        AspectJAnnotationType type = annotationTypeMap.get(annotation.annotationType());
207                        if (type != null) {
208                                return type;
209                        }
210                        throw new IllegalStateException("Unknown annotation type: " + annotation);
211                }
212
213                private String resolveExpression(A annotation) {
214                        for (String attributeName : EXPRESSION_ATTRIBUTES) {
215                                Object val = AnnotationUtils.getValue(annotation, attributeName);
216                                if (val instanceof String) {
217                                        String str = (String) val;
218                                        if (!str.isEmpty()) {
219                                                return str;
220                                        }
221                                }
222                        }
223                        throw new IllegalStateException("Failed to resolve expression: " + annotation);
224                }
225
226                public AspectJAnnotationType getAnnotationType() {
227                        return this.annotationType;
228                }
229
230                public A getAnnotation() {
231                        return this.annotation;
232                }
233
234                public String getPointcutExpression() {
235                        return this.pointcutExpression;
236                }
237
238                public String getArgumentNames() {
239                        return this.argumentNames;
240                }
241
242                @Override
243                public String toString() {
244                        return this.annotation.toString();
245                }
246        }
247
248
249        /**
250         * ParameterNameDiscoverer implementation that analyzes the arg names
251         * specified at the AspectJ annotation level.
252         */
253        private static class AspectJAnnotationParameterNameDiscoverer implements ParameterNameDiscoverer {
254
255                @Override
256                @Nullable
257                public String[] getParameterNames(Method method) {
258                        if (method.getParameterCount() == 0) {
259                                return new String[0];
260                        }
261                        AspectJAnnotation<?> annotation = findAspectJAnnotationOnMethod(method);
262                        if (annotation == null) {
263                                return null;
264                        }
265                        StringTokenizer nameTokens = new StringTokenizer(annotation.getArgumentNames(), ",");
266                        if (nameTokens.countTokens() > 0) {
267                                String[] names = new String[nameTokens.countTokens()];
268                                for (int i = 0; i < names.length; i++) {
269                                        names[i] = nameTokens.nextToken();
270                                }
271                                return names;
272                        }
273                        else {
274                                return null;
275                        }
276                }
277
278                @Override
279                @Nullable
280                public String[] getParameterNames(Constructor<?> ctor) {
281                        throw new UnsupportedOperationException("Spring AOP cannot handle constructor advice");
282                }
283        }
284
285}