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;
018
019import java.io.IOException;
020import java.io.ObjectInputStream;
021import java.lang.reflect.Method;
022import java.lang.reflect.Proxy;
023import java.util.Arrays;
024import java.util.HashSet;
025import java.util.Map;
026import java.util.Set;
027import java.util.concurrent.ConcurrentHashMap;
028
029import org.aopalliance.intercept.MethodInvocation;
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.aspectj.weaver.patterns.NamePattern;
033import org.aspectj.weaver.reflect.ReflectionWorld.ReflectionWorldException;
034import org.aspectj.weaver.reflect.ShadowMatchImpl;
035import org.aspectj.weaver.tools.ContextBasedMatcher;
036import org.aspectj.weaver.tools.FuzzyBoolean;
037import org.aspectj.weaver.tools.JoinPointMatch;
038import org.aspectj.weaver.tools.MatchingContext;
039import org.aspectj.weaver.tools.PointcutDesignatorHandler;
040import org.aspectj.weaver.tools.PointcutExpression;
041import org.aspectj.weaver.tools.PointcutParameter;
042import org.aspectj.weaver.tools.PointcutParser;
043import org.aspectj.weaver.tools.PointcutPrimitive;
044import org.aspectj.weaver.tools.ShadowMatch;
045
046import org.springframework.aop.ClassFilter;
047import org.springframework.aop.IntroductionAwareMethodMatcher;
048import org.springframework.aop.MethodMatcher;
049import org.springframework.aop.ProxyMethodInvocation;
050import org.springframework.aop.framework.autoproxy.ProxyCreationContext;
051import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
052import org.springframework.aop.support.AbstractExpressionPointcut;
053import org.springframework.aop.support.AopUtils;
054import org.springframework.beans.factory.BeanFactory;
055import org.springframework.beans.factory.BeanFactoryAware;
056import org.springframework.beans.factory.BeanFactoryUtils;
057import org.springframework.beans.factory.FactoryBean;
058import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
059import org.springframework.beans.factory.config.ConfigurableBeanFactory;
060import org.springframework.lang.Nullable;
061import org.springframework.util.Assert;
062import org.springframework.util.ClassUtils;
063import org.springframework.util.ObjectUtils;
064import org.springframework.util.StringUtils;
065
066/**
067 * Spring {@link org.springframework.aop.Pointcut} implementation
068 * that uses the AspectJ weaver to evaluate a pointcut expression.
069 *
070 * <p>The pointcut expression value is an AspectJ expression. This can
071 * reference other pointcuts and use composition and other operations.
072 *
073 * <p>Naturally, as this is to be processed by Spring AOP's proxy-based model,
074 * only method execution pointcuts are supported.
075 *
076 * @author Rob Harrop
077 * @author Adrian Colyer
078 * @author Rod Johnson
079 * @author Juergen Hoeller
080 * @author Ramnivas Laddad
081 * @author Dave Syer
082 * @since 2.0
083 */
084@SuppressWarnings("serial")
085public class AspectJExpressionPointcut extends AbstractExpressionPointcut
086                implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
087
088        private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();
089
090        static {
091                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
092                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
093                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
094                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
095                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
096                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
097                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
098                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
099                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
100                SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
101        }
102
103
104        private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class);
105
106        @Nullable
107        private Class<?> pointcutDeclarationScope;
108
109        private String[] pointcutParameterNames = new String[0];
110
111        private Class<?>[] pointcutParameterTypes = new Class<?>[0];
112
113        @Nullable
114        private BeanFactory beanFactory;
115
116        @Nullable
117        private transient ClassLoader pointcutClassLoader;
118
119        @Nullable
120        private transient PointcutExpression pointcutExpression;
121
122        private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<>(32);
123
124
125        /**
126         * Create a new default AspectJExpressionPointcut.
127         */
128        public AspectJExpressionPointcut() {
129        }
130
131        /**
132         * Create a new AspectJExpressionPointcut with the given settings.
133         * @param declarationScope the declaration scope for the pointcut
134         * @param paramNames the parameter names for the pointcut
135         * @param paramTypes the parameter types for the pointcut
136         */
137        public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
138                this.pointcutDeclarationScope = declarationScope;
139                if (paramNames.length != paramTypes.length) {
140                        throw new IllegalStateException(
141                                        "Number of pointcut parameter names must match number of pointcut parameter types");
142                }
143                this.pointcutParameterNames = paramNames;
144                this.pointcutParameterTypes = paramTypes;
145        }
146
147
148        /**
149         * Set the declaration scope for the pointcut.
150         */
151        public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
152                this.pointcutDeclarationScope = pointcutDeclarationScope;
153        }
154
155        /**
156         * Set the parameter names for the pointcut.
157         */
158        public void setParameterNames(String... names) {
159                this.pointcutParameterNames = names;
160        }
161
162        /**
163         * Set the parameter types for the pointcut.
164         */
165        public void setParameterTypes(Class<?>... types) {
166                this.pointcutParameterTypes = types;
167        }
168
169        @Override
170        public void setBeanFactory(BeanFactory beanFactory) {
171                this.beanFactory = beanFactory;
172        }
173
174
175        @Override
176        public ClassFilter getClassFilter() {
177                obtainPointcutExpression();
178                return this;
179        }
180
181        @Override
182        public MethodMatcher getMethodMatcher() {
183                obtainPointcutExpression();
184                return this;
185        }
186
187
188        /**
189         * Check whether this pointcut is ready to match,
190         * lazily building the underlying AspectJ pointcut expression.
191         */
192        private PointcutExpression obtainPointcutExpression() {
193                if (getExpression() == null) {
194                        throw new IllegalStateException("Must set property 'expression' before attempting to match");
195                }
196                if (this.pointcutExpression == null) {
197                        this.pointcutClassLoader = determinePointcutClassLoader();
198                        this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
199                }
200                return this.pointcutExpression;
201        }
202
203        /**
204         * Determine the ClassLoader to use for pointcut evaluation.
205         */
206        @Nullable
207        private ClassLoader determinePointcutClassLoader() {
208                if (this.beanFactory instanceof ConfigurableBeanFactory) {
209                        return ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader();
210                }
211                if (this.pointcutDeclarationScope != null) {
212                        return this.pointcutDeclarationScope.getClassLoader();
213                }
214                return ClassUtils.getDefaultClassLoader();
215        }
216
217        /**
218         * Build the underlying AspectJ pointcut expression.
219         */
220        private PointcutExpression buildPointcutExpression(@Nullable ClassLoader classLoader) {
221                PointcutParser parser = initializePointcutParser(classLoader);
222                PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];
223                for (int i = 0; i < pointcutParameters.length; i++) {
224                        pointcutParameters[i] = parser.createPointcutParameter(
225                                        this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
226                }
227                return parser.parsePointcutExpression(replaceBooleanOperators(resolveExpression()),
228                                this.pointcutDeclarationScope, pointcutParameters);
229        }
230
231        private String resolveExpression() {
232                String expression = getExpression();
233                Assert.state(expression != null, "No expression set");
234                return expression;
235        }
236
237        /**
238         * Initialize the underlying AspectJ pointcut parser.
239         */
240        private PointcutParser initializePointcutParser(@Nullable ClassLoader classLoader) {
241                PointcutParser parser = PointcutParser
242                                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
243                                                SUPPORTED_PRIMITIVES, classLoader);
244                parser.registerPointcutDesignatorHandler(new BeanPointcutDesignatorHandler());
245                return parser;
246        }
247
248
249        /**
250         * If a pointcut expression has been specified in XML, the user cannot
251         * write {@code and} as "&&" (though &amp;&amp; will work).
252         * We also allow {@code and} between two pointcut sub-expressions.
253         * <p>This method converts back to {@code &&} for the AspectJ pointcut parser.
254         */
255        private String replaceBooleanOperators(String pcExpr) {
256                String result = StringUtils.replace(pcExpr, " and ", " && ");
257                result = StringUtils.replace(result, " or ", " || ");
258                result = StringUtils.replace(result, " not ", " ! ");
259                return result;
260        }
261
262
263        /**
264         * Return the underlying AspectJ pointcut expression.
265         */
266        public PointcutExpression getPointcutExpression() {
267                return obtainPointcutExpression();
268        }
269
270        @Override
271        public boolean matches(Class<?> targetClass) {
272                PointcutExpression pointcutExpression = obtainPointcutExpression();
273                try {
274                        try {
275                                return pointcutExpression.couldMatchJoinPointsInType(targetClass);
276                        }
277                        catch (ReflectionWorldException ex) {
278                                logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
279                                // Actually this is still a "maybe" - treat the pointcut as dynamic if we don't know enough yet
280                                PointcutExpression fallbackExpression = getFallbackPointcutExpression(targetClass);
281                                if (fallbackExpression != null) {
282                                        return fallbackExpression.couldMatchJoinPointsInType(targetClass);
283                                }
284                        }
285                }
286                catch (Throwable ex) {
287                        logger.debug("PointcutExpression matching rejected target class", ex);
288                }
289                return false;
290        }
291
292        @Override
293        public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
294                obtainPointcutExpression();
295                ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
296
297                // Special handling for this, target, @this, @target, @annotation
298                // in Spring - we can optimize since we know we have exactly this class,
299                // and there will never be matching subclass at runtime.
300                if (shadowMatch.alwaysMatches()) {
301                        return true;
302                }
303                else if (shadowMatch.neverMatches()) {
304                        return false;
305                }
306                else {
307                        // the maybe case
308                        if (hasIntroductions) {
309                                return true;
310                        }
311                        // A match test returned maybe - if there are any subtype sensitive variables
312                        // involved in the test (this, target, at_this, at_target, at_annotation) then
313                        // we say this is not a match as in Spring there will never be a different
314                        // runtime subtype.
315                        RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
316                        return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass));
317                }
318        }
319
320        @Override
321        public boolean matches(Method method, Class<?> targetClass) {
322                return matches(method, targetClass, false);
323        }
324
325        @Override
326        public boolean isRuntime() {
327                return obtainPointcutExpression().mayNeedDynamicTest();
328        }
329
330        @Override
331        public boolean matches(Method method, Class<?> targetClass, Object... args) {
332                obtainPointcutExpression();
333                ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
334
335                // Bind Spring AOP proxy to AspectJ "this" and Spring AOP target to AspectJ target,
336                // consistent with return of MethodInvocationProceedingJoinPoint
337                ProxyMethodInvocation pmi = null;
338                Object targetObject = null;
339                Object thisObject = null;
340                try {
341                        MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
342                        targetObject = mi.getThis();
343                        if (!(mi instanceof ProxyMethodInvocation)) {
344                                throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
345                        }
346                        pmi = (ProxyMethodInvocation) mi;
347                        thisObject = pmi.getProxy();
348                }
349                catch (IllegalStateException ex) {
350                        // No current invocation...
351                        if (logger.isDebugEnabled()) {
352                                logger.debug("Could not access current invocation - matching with limited context: " + ex);
353                        }
354                }
355
356                try {
357                        JoinPointMatch joinPointMatch = shadowMatch.matchesJoinPoint(thisObject, targetObject, args);
358
359                        /*
360                         * Do a final check to see if any this(TYPE) kind of residue match. For
361                         * this purpose, we use the original method's (proxy method's) shadow to
362                         * ensure that 'this' is correctly checked against. Without this check,
363                         * we get incorrect match on this(TYPE) where TYPE matches the target
364                         * type but not 'this' (as would be the case of JDK dynamic proxies).
365                         * <p>See SPR-2979 for the original bug.
366                         */
367                        if (pmi != null && thisObject != null) {  // there is a current invocation
368                                RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(getShadowMatch(method, method));
369                                if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) {
370                                        return false;
371                                }
372                                if (joinPointMatch.matches()) {
373                                        bindParameters(pmi, joinPointMatch);
374                                }
375                        }
376
377                        return joinPointMatch.matches();
378                }
379                catch (Throwable ex) {
380                        if (logger.isDebugEnabled()) {
381                                logger.debug("Failed to evaluate join point for arguments " + Arrays.asList(args) +
382                                                " - falling back to non-match", ex);
383                        }
384                        return false;
385                }
386        }
387
388        @Nullable
389        protected String getCurrentProxiedBeanName() {
390                return ProxyCreationContext.getCurrentProxiedBeanName();
391        }
392
393
394        /**
395         * Get a new pointcut expression based on a target class's loader rather than the default.
396         */
397        @Nullable
398        private PointcutExpression getFallbackPointcutExpression(Class<?> targetClass) {
399                try {
400                        ClassLoader classLoader = targetClass.getClassLoader();
401                        if (classLoader != null && classLoader != this.pointcutClassLoader) {
402                                return buildPointcutExpression(classLoader);
403                        }
404                }
405                catch (Throwable ex) {
406                        logger.debug("Failed to create fallback PointcutExpression", ex);
407                }
408                return null;
409        }
410
411        private RuntimeTestWalker getRuntimeTestWalker(ShadowMatch shadowMatch) {
412                if (shadowMatch instanceof DefensiveShadowMatch) {
413                        return new RuntimeTestWalker(((DefensiveShadowMatch) shadowMatch).primary);
414                }
415                return new RuntimeTestWalker(shadowMatch);
416        }
417
418        private void bindParameters(ProxyMethodInvocation invocation, JoinPointMatch jpm) {
419                // Note: Can't use JoinPointMatch.getClass().getName() as the key, since
420                // Spring AOP does all the matching at a join point, and then all the invocations
421                // under this scenario, if we just use JoinPointMatch as the key, then
422                // 'last man wins' which is not what we want at all.
423                // Using the expression is guaranteed to be safe, since 2 identical expressions
424                // are guaranteed to bind in exactly the same way.
425                invocation.setUserAttribute(resolveExpression(), jpm);
426        }
427
428        private ShadowMatch getTargetShadowMatch(Method method, Class<?> targetClass) {
429                Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
430                if (targetMethod.getDeclaringClass().isInterface()) {
431                        // Try to build the most specific interface possible for inherited methods to be
432                        // considered for sub-interface matches as well, in particular for proxy classes.
433                        // Note: AspectJ is only going to take Method.getDeclaringClass() into account.
434                        Set<Class<?>> ifcs = ClassUtils.getAllInterfacesForClassAsSet(targetClass);
435                        if (ifcs.size() > 1) {
436                                try {
437                                        Class<?> compositeInterface = ClassUtils.createCompositeInterface(
438                                                        ClassUtils.toClassArray(ifcs), targetClass.getClassLoader());
439                                        targetMethod = ClassUtils.getMostSpecificMethod(targetMethod, compositeInterface);
440                                }
441                                catch (IllegalArgumentException ex) {
442                                        // Implemented interfaces probably expose conflicting method signatures...
443                                        // Proceed with original target method.
444                                }
445                        }
446                }
447                return getShadowMatch(targetMethod, method);
448        }
449
450        private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
451                // Avoid lock contention for known Methods through concurrent access...
452                ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
453                if (shadowMatch == null) {
454                        synchronized (this.shadowMatchCache) {
455                                // Not found - now check again with full lock...
456                                PointcutExpression fallbackExpression = null;
457                                shadowMatch = this.shadowMatchCache.get(targetMethod);
458                                if (shadowMatch == null) {
459                                        Method methodToMatch = targetMethod;
460                                        try {
461                                                try {
462                                                        shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
463                                                }
464                                                catch (ReflectionWorldException ex) {
465                                                        // Failed to introspect target method, probably because it has been loaded
466                                                        // in a special ClassLoader. Let's try the declaring ClassLoader instead...
467                                                        try {
468                                                                fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
469                                                                if (fallbackExpression != null) {
470                                                                        shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
471                                                                }
472                                                        }
473                                                        catch (ReflectionWorldException ex2) {
474                                                                fallbackExpression = null;
475                                                        }
476                                                }
477                                                if (targetMethod != originalMethod && (shadowMatch == null ||
478                                                                (shadowMatch.neverMatches() && Proxy.isProxyClass(targetMethod.getDeclaringClass())))) {
479                                                        // Fall back to the plain original method in case of no resolvable match or a
480                                                        // negative match on a proxy class (which doesn't carry any annotations on its
481                                                        // redeclared methods).
482                                                        methodToMatch = originalMethod;
483                                                        try {
484                                                                shadowMatch = obtainPointcutExpression().matchesMethodExecution(methodToMatch);
485                                                        }
486                                                        catch (ReflectionWorldException ex) {
487                                                                // Could neither introspect the target class nor the proxy class ->
488                                                                // let's try the original method's declaring class before we give up...
489                                                                try {
490                                                                        fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
491                                                                        if (fallbackExpression != null) {
492                                                                                shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
493                                                                        }
494                                                                }
495                                                                catch (ReflectionWorldException ex2) {
496                                                                        fallbackExpression = null;
497                                                                }
498                                                        }
499                                                }
500                                        }
501                                        catch (Throwable ex) {
502                                                // Possibly AspectJ 1.8.10 encountering an invalid signature
503                                                logger.debug("PointcutExpression matching rejected target method", ex);
504                                                fallbackExpression = null;
505                                        }
506                                        if (shadowMatch == null) {
507                                                shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
508                                        }
509                                        else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
510                                                shadowMatch = new DefensiveShadowMatch(shadowMatch,
511                                                                fallbackExpression.matchesMethodExecution(methodToMatch));
512                                        }
513                                        this.shadowMatchCache.put(targetMethod, shadowMatch);
514                                }
515                        }
516                }
517                return shadowMatch;
518        }
519
520
521        @Override
522        public boolean equals(@Nullable Object other) {
523                if (this == other) {
524                        return true;
525                }
526                if (!(other instanceof AspectJExpressionPointcut)) {
527                        return false;
528                }
529                AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other;
530                return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
531                                ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
532                                ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
533                                ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
534        }
535
536        @Override
537        public int hashCode() {
538                int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression());
539                hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope);
540                hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames);
541                hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes);
542                return hashCode;
543        }
544
545        @Override
546        public String toString() {
547                StringBuilder sb = new StringBuilder("AspectJExpressionPointcut: (");
548                for (int i = 0; i < this.pointcutParameterTypes.length; i++) {
549                        sb.append(this.pointcutParameterTypes[i].getName());
550                        sb.append(" ");
551                        sb.append(this.pointcutParameterNames[i]);
552                        if ((i+1) < this.pointcutParameterTypes.length) {
553                                sb.append(", ");
554                        }
555                }
556                sb.append(") ");
557                if (getExpression() != null) {
558                        sb.append(getExpression());
559                }
560                else {
561                        sb.append("<pointcut expression not set>");
562                }
563                return sb.toString();
564        }
565
566        //---------------------------------------------------------------------
567        // Serialization support
568        //---------------------------------------------------------------------
569
570        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
571                // Rely on default serialization, just initialize state after deserialization.
572                ois.defaultReadObject();
573
574                // Initialize transient fields.
575                // pointcutExpression will be initialized lazily by checkReadyToMatch()
576                this.shadowMatchCache = new ConcurrentHashMap<>(32);
577        }
578
579
580        /**
581         * Handler for the Spring-specific {@code bean()} pointcut designator
582         * extension to AspectJ.
583         * <p>This handler must be added to each pointcut object that needs to
584         * handle the {@code bean()} PCD. Matching context is obtained
585         * automatically by examining a thread local variable and therefore a matching
586         * context need not be set on the pointcut.
587         */
588        private class BeanPointcutDesignatorHandler implements PointcutDesignatorHandler {
589
590                private static final String BEAN_DESIGNATOR_NAME = "bean";
591
592                @Override
593                public String getDesignatorName() {
594                        return BEAN_DESIGNATOR_NAME;
595                }
596
597                @Override
598                public ContextBasedMatcher parse(String expression) {
599                        return new BeanContextMatcher(expression);
600                }
601        }
602
603
604        /**
605         * Matcher class for the BeanNamePointcutDesignatorHandler.
606         * <p>Dynamic match tests for this matcher always return true,
607         * since the matching decision is made at the proxy creation time.
608         * For static match tests, this matcher abstains to allow the overall
609         * pointcut to match even when negation is used with the bean() pointcut.
610         */
611        private class BeanContextMatcher implements ContextBasedMatcher {
612
613                private final NamePattern expressionPattern;
614
615                public BeanContextMatcher(String expression) {
616                        this.expressionPattern = new NamePattern(expression);
617                }
618
619                @Override
620                @SuppressWarnings("rawtypes")
621                @Deprecated
622                public boolean couldMatchJoinPointsInType(Class someClass) {
623                        return (contextMatch(someClass) == FuzzyBoolean.YES);
624                }
625
626                @Override
627                @SuppressWarnings("rawtypes")
628                @Deprecated
629                public boolean couldMatchJoinPointsInType(Class someClass, MatchingContext context) {
630                        return (contextMatch(someClass) == FuzzyBoolean.YES);
631                }
632
633                @Override
634                public boolean matchesDynamically(MatchingContext context) {
635                        return true;
636                }
637
638                @Override
639                public FuzzyBoolean matchesStatically(MatchingContext context) {
640                        return contextMatch(null);
641                }
642
643                @Override
644                public boolean mayNeedDynamicTest() {
645                        return false;
646                }
647
648                private FuzzyBoolean contextMatch(@Nullable Class<?> targetType) {
649                        String advisedBeanName = getCurrentProxiedBeanName();
650                        if (advisedBeanName == null) {  // no proxy creation in progress
651                                // abstain; can't return YES, since that will make pointcut with negation fail
652                                return FuzzyBoolean.MAYBE;
653                        }
654                        if (BeanFactoryUtils.isGeneratedBeanName(advisedBeanName)) {
655                                return FuzzyBoolean.NO;
656                        }
657                        if (targetType != null) {
658                                boolean isFactory = FactoryBean.class.isAssignableFrom(targetType);
659                                return FuzzyBoolean.fromBoolean(
660                                                matchesBean(isFactory ? BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName : advisedBeanName));
661                        }
662                        else {
663                                return FuzzyBoolean.fromBoolean(matchesBean(advisedBeanName) ||
664                                                matchesBean(BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName));
665                        }
666                }
667
668                private boolean matchesBean(String advisedBeanName) {
669                        return BeanFactoryAnnotationUtils.isQualifierMatch(
670                                        this.expressionPattern::matches, advisedBeanName, beanFactory);
671                }
672        }
673
674
675        private static class DefensiveShadowMatch implements ShadowMatch {
676
677                private final ShadowMatch primary;
678
679                private final ShadowMatch other;
680
681                public DefensiveShadowMatch(ShadowMatch primary, ShadowMatch other) {
682                        this.primary = primary;
683                        this.other = other;
684                }
685
686                @Override
687                public boolean alwaysMatches() {
688                        return this.primary.alwaysMatches();
689                }
690
691                @Override
692                public boolean maybeMatches() {
693                        return this.primary.maybeMatches();
694                }
695
696                @Override
697                public boolean neverMatches() {
698                        return this.primary.neverMatches();
699                }
700
701                @Override
702                public JoinPointMatch matchesJoinPoint(Object thisObject, Object targetObject, Object[] args) {
703                        try {
704                                return this.primary.matchesJoinPoint(thisObject, targetObject, args);
705                        }
706                        catch (ReflectionWorldException ex) {
707                                return this.other.matchesJoinPoint(thisObject, targetObject, args);
708                        }
709                }
710
711                @Override
712                public void setMatchingContext(MatchingContext aMatchContext) {
713                        this.primary.setMatchingContext(aMatchContext);
714                        this.other.setMatchingContext(aMatchContext);
715                }
716        }
717
718}