001/*
002 * Copyright 2002-2016 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.io.IOException;
020import java.io.ObjectInputStream;
021import java.io.Serializable;
022
023import org.aspectj.lang.annotation.Aspect;
024import org.aspectj.lang.reflect.AjType;
025import org.aspectj.lang.reflect.AjTypeSystem;
026import org.aspectj.lang.reflect.PerClauseKind;
027
028import org.springframework.aop.Pointcut;
029import org.springframework.aop.aspectj.AspectJExpressionPointcut;
030import org.springframework.aop.aspectj.TypePatternClassFilter;
031import org.springframework.aop.framework.AopConfigException;
032import org.springframework.aop.support.ComposablePointcut;
033
034/**
035 * Metadata for an AspectJ aspect class, with an additional Spring AOP pointcut
036 * for the per clause.
037 *
038 * <p>Uses AspectJ 5 AJType reflection API, enabling us to work with different
039 * AspectJ instantiation models such as "singleton", "pertarget" and "perthis".
040 *
041 * @author Rod Johnson
042 * @author Juergen Hoeller
043 * @since 2.0
044 * @see org.springframework.aop.aspectj.AspectJExpressionPointcut
045 */
046@SuppressWarnings("serial")
047public class AspectMetadata implements Serializable {
048
049        /**
050         * The name of this aspect as defined to Spring (the bean name) -
051         * allows us to determine if two pieces of advice come from the
052         * same aspect and hence their relative precedence.
053         */
054        private final String aspectName;
055
056        /**
057         * The aspect class, stored separately for re-resolution of the
058         * corresponding AjType on deserialization.
059         */
060        private final Class<?> aspectClass;
061
062        /**
063         * AspectJ reflection information (AspectJ 5 / Java 5 specific).
064         * Re-resolved on deserialization since it isn't serializable itself.
065         */
066        private transient AjType<?> ajType;
067
068        /**
069         * Spring AOP pointcut corresponding to the per clause of the
070         * aspect. Will be the Pointcut.TRUE canonical instance in the
071         * case of a singleton, otherwise an AspectJExpressionPointcut.
072         */
073        private final Pointcut perClausePointcut;
074
075
076        /**
077         * Create a new AspectMetadata instance for the given aspect class.
078         * @param aspectClass the aspect class
079         * @param aspectName the name of the aspect
080         */
081        public AspectMetadata(Class<?> aspectClass, String aspectName) {
082                this.aspectName = aspectName;
083
084                Class<?> currClass = aspectClass;
085                AjType<?> ajType = null;
086                while (currClass != Object.class) {
087                        AjType<?> ajTypeToCheck = AjTypeSystem.getAjType(currClass);
088                        if (ajTypeToCheck.isAspect()) {
089                                ajType = ajTypeToCheck;
090                                break;
091                        }
092                        currClass = currClass.getSuperclass();
093                }
094                if (ajType == null) {
095                        throw new IllegalArgumentException("Class '" + aspectClass.getName() + "' is not an @AspectJ aspect");
096                }
097                if (ajType.getDeclarePrecedence().length > 0) {
098                        throw new IllegalArgumentException("DeclarePrecendence not presently supported in Spring AOP");
099                }
100                this.aspectClass = ajType.getJavaClass();
101                this.ajType = ajType;
102
103                switch (this.ajType.getPerClause().getKind()) {
104                        case SINGLETON:
105                                this.perClausePointcut = Pointcut.TRUE;
106                                return;
107                        case PERTARGET:
108                        case PERTHIS:
109                                AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut();
110                                ajexp.setLocation(aspectClass.getName());
111                                ajexp.setExpression(findPerClause(aspectClass));
112                                ajexp.setPointcutDeclarationScope(aspectClass);
113                                this.perClausePointcut = ajexp;
114                                return;
115                        case PERTYPEWITHIN:
116                                // Works with a type pattern
117                                this.perClausePointcut = new ComposablePointcut(new TypePatternClassFilter(findPerClause(aspectClass)));
118                                return;
119                        default:
120                                throw new AopConfigException(
121                                                "PerClause " + ajType.getPerClause().getKind() + " not supported by Spring AOP for " + aspectClass);
122                }
123        }
124
125        /**
126         * Extract contents from String of form {@code pertarget(contents)}.
127         */
128        private String findPerClause(Class<?> aspectClass) {
129                String str = aspectClass.getAnnotation(Aspect.class).value();
130                str = str.substring(str.indexOf('(') + 1);
131                str = str.substring(0, str.length() - 1);
132                return str;
133        }
134
135
136        /**
137         * Return AspectJ reflection information.
138         */
139        public AjType<?> getAjType() {
140                return this.ajType;
141        }
142
143        /**
144         * Return the aspect class.
145         */
146        public Class<?> getAspectClass() {
147                return this.aspectClass;
148        }
149
150        /**
151         * Return the aspect name.
152         */
153        public String getAspectName() {
154                return this.aspectName;
155        }
156
157        /**
158         * Return a Spring pointcut expression for a singleton aspect.
159         * (e.g. {@code Pointcut.TRUE} if it's a singleton).
160         */
161        public Pointcut getPerClausePointcut() {
162                return this.perClausePointcut;
163        }
164
165        /**
166         * Return whether the aspect is defined as "perthis" or "pertarget".
167         */
168        public boolean isPerThisOrPerTarget() {
169                PerClauseKind kind = getAjType().getPerClause().getKind();
170                return (kind == PerClauseKind.PERTARGET || kind == PerClauseKind.PERTHIS);
171        }
172
173        /**
174         * Return whether the aspect is defined as "pertypewithin".
175         */
176        public boolean isPerTypeWithin() {
177                PerClauseKind kind = getAjType().getPerClause().getKind();
178                return (kind == PerClauseKind.PERTYPEWITHIN);
179        }
180
181        /**
182         * Return whether the aspect needs to be lazily instantiated.
183         */
184        public boolean isLazilyInstantiated() {
185                return (isPerThisOrPerTarget() || isPerTypeWithin());
186        }
187
188
189        private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {
190                inputStream.defaultReadObject();
191                this.ajType = AjTypeSystem.getAjType(this.aspectClass);
192        }
193
194}