1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.aop.aspectj;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.lang.reflect.Method;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import org.aopalliance.intercept.MethodInvocation;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.aspectj.weaver.BCException;
31 import org.aspectj.weaver.patterns.NamePattern;
32 import org.aspectj.weaver.reflect.ReflectionWorld.ReflectionWorldException;
33 import org.aspectj.weaver.reflect.ShadowMatchImpl;
34 import org.aspectj.weaver.tools.ContextBasedMatcher;
35 import org.aspectj.weaver.tools.FuzzyBoolean;
36 import org.aspectj.weaver.tools.JoinPointMatch;
37 import org.aspectj.weaver.tools.MatchingContext;
38 import org.aspectj.weaver.tools.PointcutDesignatorHandler;
39 import org.aspectj.weaver.tools.PointcutExpression;
40 import org.aspectj.weaver.tools.PointcutParameter;
41 import org.aspectj.weaver.tools.PointcutParser;
42 import org.aspectj.weaver.tools.PointcutPrimitive;
43 import org.aspectj.weaver.tools.ShadowMatch;
44
45 import org.springframework.aop.ClassFilter;
46 import org.springframework.aop.IntroductionAwareMethodMatcher;
47 import org.springframework.aop.MethodMatcher;
48 import org.springframework.aop.ProxyMethodInvocation;
49 import org.springframework.aop.framework.autoproxy.ProxyCreationContext;
50 import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
51 import org.springframework.aop.support.AbstractExpressionPointcut;
52 import org.springframework.aop.support.AopUtils;
53 import org.springframework.beans.factory.BeanFactory;
54 import org.springframework.beans.factory.BeanFactoryAware;
55 import org.springframework.beans.factory.BeanFactoryUtils;
56 import org.springframework.beans.factory.FactoryBean;
57 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
58 import org.springframework.util.ClassUtils;
59 import org.springframework.util.ObjectUtils;
60 import org.springframework.util.StringUtils;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80 @SuppressWarnings("serial")
81 public class AspectJExpressionPointcut extends AbstractExpressionPointcut
82 implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
83
84 private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<PointcutPrimitive>();
85
86 static {
87 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
88 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
89 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
90 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
91 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
92 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
93 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
94 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
95 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
96 SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
97 }
98
99
100 private static final Log logger = LogFactory.getLog(AspectJExpressionPointcut.class);
101
102 private Class<?> pointcutDeclarationScope;
103
104 private String[] pointcutParameterNames = new String[0];
105
106 private Class<?>[] pointcutParameterTypes = new Class<?>[0];
107
108 private BeanFactory beanFactory;
109
110 private transient ClassLoader pointcutClassLoader;
111
112 private transient PointcutExpression pointcutExpression;
113
114 private transient Map<Method, ShadowMatch> shadowMatchCache = new ConcurrentHashMap<Method, ShadowMatch>(32);
115
116
117
118
119
120 public AspectJExpressionPointcut() {
121 }
122
123
124
125
126
127
128
129 public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
130 this.pointcutDeclarationScope = declarationScope;
131 if (paramNames.length != paramTypes.length) {
132 throw new IllegalStateException(
133 "Number of pointcut parameter names must match number of pointcut parameter types");
134 }
135 this.pointcutParameterNames = paramNames;
136 this.pointcutParameterTypes = paramTypes;
137 }
138
139
140
141
142
143 public void setPointcutDeclarationScope(Class<?> pointcutDeclarationScope) {
144 this.pointcutDeclarationScope = pointcutDeclarationScope;
145 }
146
147
148
149
150 public void setParameterNames(String... names) {
151 this.pointcutParameterNames = names;
152 }
153
154
155
156
157 public void setParameterTypes(Class<?>... types) {
158 this.pointcutParameterTypes = types;
159 }
160
161 @Override
162 public void setBeanFactory(BeanFactory beanFactory) {
163 this.beanFactory = beanFactory;
164 }
165
166
167 @Override
168 public ClassFilter getClassFilter() {
169 checkReadyToMatch();
170 return this;
171 }
172
173 @Override
174 public MethodMatcher getMethodMatcher() {
175 checkReadyToMatch();
176 return this;
177 }
178
179
180
181
182
183
184 private void checkReadyToMatch() {
185 if (getExpression() == null) {
186 throw new IllegalStateException("Must set property 'expression' before attempting to match");
187 }
188 if (this.pointcutExpression == null) {
189 this.pointcutClassLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?
190 ((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() :
191 ClassUtils.getDefaultClassLoader());
192 this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
193 }
194 }
195
196
197
198
199 private PointcutExpression buildPointcutExpression(ClassLoader classLoader) {
200 PointcutParser parser = initializePointcutParser(classLoader);
201 PointcutParameter[] pointcutParameters = new PointcutParameter[this.pointcutParameterNames.length];
202 for (int i = 0; i < pointcutParameters.length; i++) {
203 pointcutParameters[i] = parser.createPointcutParameter(
204 this.pointcutParameterNames[i], this.pointcutParameterTypes[i]);
205 }
206 return parser.parsePointcutExpression(
207 replaceBooleanOperators(getExpression()),
208 this.pointcutDeclarationScope, pointcutParameters);
209 }
210
211
212
213
214 private PointcutParser initializePointcutParser(ClassLoader cl) {
215 PointcutParser parser = PointcutParser
216 .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
217 SUPPORTED_PRIMITIVES, cl);
218 parser.registerPointcutDesignatorHandler(new BeanNamePointcutDesignatorHandler());
219 return parser;
220 }
221
222
223
224
225
226
227
228
229 private String replaceBooleanOperators(String pcExpr) {
230 String result = StringUtils.replace(pcExpr, " and ", " && ");
231 result = StringUtils.replace(result, " or ", " || ");
232 result = StringUtils.replace(result, " not ", " ! ");
233 return result;
234 }
235
236
237
238
239
240 public PointcutExpression getPointcutExpression() {
241 checkReadyToMatch();
242 return this.pointcutExpression;
243 }
244
245 @Override
246 public boolean matches(Class<?> targetClass) {
247 checkReadyToMatch();
248 try {
249 try {
250 return this.pointcutExpression.couldMatchJoinPointsInType(targetClass);
251 }
252 catch (ReflectionWorldException ex) {
253 logger.debug("PointcutExpression matching rejected target class - trying fallback expression", ex);
254
255 PointcutExpression fallbackExpression = getFallbackPointcutExpression(targetClass);
256 if (fallbackExpression != null) {
257 return fallbackExpression.couldMatchJoinPointsInType(targetClass);
258 }
259 }
260 }
261 catch (BCException ex) {
262 logger.debug("PointcutExpression matching rejected target class", ex);
263 }
264 return false;
265 }
266
267 @Override
268 public boolean matches(Method method, Class<?> targetClass, boolean beanHasIntroductions) {
269 checkReadyToMatch();
270 Method targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
271 ShadowMatch shadowMatch = getShadowMatch(targetMethod, method);
272
273
274
275
276 if (shadowMatch.alwaysMatches()) {
277 return true;
278 }
279 else if (shadowMatch.neverMatches()) {
280 return false;
281 }
282 else {
283
284 if (beanHasIntroductions) {
285 return true;
286 }
287
288
289
290
291 RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
292 return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass));
293 }
294 }
295
296 @Override
297 public boolean matches(Method method, Class<?> targetClass) {
298 return matches(method, targetClass, false);
299 }
300
301 @Override
302 public boolean isRuntime() {
303 checkReadyToMatch();
304 return this.pointcutExpression.mayNeedDynamicTest();
305 }
306
307 @Override
308 public boolean matches(Method method, Class<?> targetClass, Object[] args) {
309 checkReadyToMatch();
310 ShadowMatch shadowMatch = getShadowMatch(AopUtils.getMostSpecificMethod(method, targetClass), method);
311 ShadowMatch originalShadowMatch = getShadowMatch(method, method);
312
313
314
315 ProxyMethodInvocation pmi = null;
316 Object targetObject = null;
317 Object thisObject = null;
318 try {
319 MethodInvocation mi = ExposeInvocationInterceptor.currentInvocation();
320 targetObject = mi.getThis();
321 if (!(mi instanceof ProxyMethodInvocation)) {
322 throw new IllegalStateException("MethodInvocation is not a Spring ProxyMethodInvocation: " + mi);
323 }
324 pmi = (ProxyMethodInvocation) mi;
325 thisObject = pmi.getProxy();
326 }
327 catch (IllegalStateException ex) {
328
329
330 logger.debug("Couldn't access current invocation - matching with limited context: " + ex);
331 }
332
333 JoinPointMatch joinPointMatch = shadowMatch.matchesJoinPoint(thisObject, targetObject, args);
334
335
336
337
338
339
340
341
342
343 if (pmi != null) {
344 RuntimeTestWalker originalMethodResidueTest = getRuntimeTestWalker(originalShadowMatch);
345 if (!originalMethodResidueTest.testThisInstanceOfResidue(thisObject.getClass())) {
346 return false;
347 }
348 if (joinPointMatch.matches()) {
349 bindParameters(pmi, joinPointMatch);
350 }
351 }
352 return joinPointMatch.matches();
353 }
354
355 protected String getCurrentProxiedBeanName() {
356 return ProxyCreationContext.getCurrentProxiedBeanName();
357 }
358
359
360
361
362
363 private PointcutExpression getFallbackPointcutExpression(Class<?> targetClass) {
364 try {
365 ClassLoader classLoader = targetClass.getClassLoader();
366 if (classLoader != null && classLoader != this.pointcutClassLoader) {
367 return buildPointcutExpression(classLoader);
368 }
369 }
370 catch (Throwable ex) {
371 logger.debug("Failed to create fallback PointcutExpression", ex);
372 }
373 return null;
374 }
375
376 private RuntimeTestWalker getRuntimeTestWalker(ShadowMatch shadowMatch) {
377 if (shadowMatch instanceof DefensiveShadowMatch) {
378 return new RuntimeTestWalker(((DefensiveShadowMatch) shadowMatch).primary);
379 }
380 return new RuntimeTestWalker(shadowMatch);
381 }
382
383 private void bindParameters(ProxyMethodInvocation invocation, JoinPointMatch jpm) {
384
385
386
387
388
389
390 invocation.setUserAttribute(getExpression(), jpm);
391 }
392
393 private ShadowMatch getShadowMatch(Method targetMethod, Method originalMethod) {
394
395 ShadowMatch shadowMatch = this.shadowMatchCache.get(targetMethod);
396 if (shadowMatch == null) {
397 synchronized (this.shadowMatchCache) {
398
399 PointcutExpression fallbackExpression = null;
400 Method methodToMatch = targetMethod;
401 shadowMatch = this.shadowMatchCache.get(targetMethod);
402 if (shadowMatch == null) {
403 try {
404 shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch);
405 }
406 catch (ReflectionWorldException ex) {
407
408
409 try {
410 fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
411 if (fallbackExpression != null) {
412 shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
413 }
414 }
415 catch (ReflectionWorldException ex2) {
416 fallbackExpression = null;
417 }
418 }
419 if (shadowMatch == null && targetMethod != originalMethod) {
420 methodToMatch = originalMethod;
421 try {
422 shadowMatch = this.pointcutExpression.matchesMethodExecution(methodToMatch);
423 }
424 catch (ReflectionWorldException ex3) {
425
426
427 try {
428 fallbackExpression = getFallbackPointcutExpression(methodToMatch.getDeclaringClass());
429 if (fallbackExpression != null) {
430 shadowMatch = fallbackExpression.matchesMethodExecution(methodToMatch);
431 }
432 }
433 catch (ReflectionWorldException ex4) {
434 fallbackExpression = null;
435 }
436 }
437 }
438 if (shadowMatch == null) {
439 shadowMatch = new ShadowMatchImpl(org.aspectj.util.FuzzyBoolean.NO, null, null, null);
440 }
441 else if (shadowMatch.maybeMatches() && fallbackExpression != null) {
442 shadowMatch = new DefensiveShadowMatch(shadowMatch,
443 fallbackExpression.matchesMethodExecution(methodToMatch));
444 }
445 this.shadowMatchCache.put(targetMethod, shadowMatch);
446 }
447 }
448 }
449 return shadowMatch;
450 }
451
452
453 @Override
454 public boolean equals(Object other) {
455 if (this == other) {
456 return true;
457 }
458 if (!(other instanceof AspectJExpressionPointcut)) {
459 return false;
460 }
461 AspectJExpressionPointcut otherPc = (AspectJExpressionPointcut) other;
462 return ObjectUtils.nullSafeEquals(this.getExpression(), otherPc.getExpression()) &&
463 ObjectUtils.nullSafeEquals(this.pointcutDeclarationScope, otherPc.pointcutDeclarationScope) &&
464 ObjectUtils.nullSafeEquals(this.pointcutParameterNames, otherPc.pointcutParameterNames) &&
465 ObjectUtils.nullSafeEquals(this.pointcutParameterTypes, otherPc.pointcutParameterTypes);
466 }
467
468 @Override
469 public int hashCode() {
470 int hashCode = ObjectUtils.nullSafeHashCode(this.getExpression());
471 hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutDeclarationScope);
472 hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterNames);
473 hashCode = 31 * hashCode + ObjectUtils.nullSafeHashCode(this.pointcutParameterTypes);
474 return hashCode;
475 }
476
477 @Override
478 public String toString() {
479 StringBuilder sb = new StringBuilder();
480 sb.append("AspectJExpressionPointcut: ");
481 if (this.pointcutParameterNames != null && this.pointcutParameterTypes != null) {
482 sb.append("(");
483 for (int i = 0; i < this.pointcutParameterTypes.length; i++) {
484 sb.append(this.pointcutParameterTypes[i].getName());
485 sb.append(" ");
486 sb.append(this.pointcutParameterNames[i]);
487 if ((i+1) < this.pointcutParameterTypes.length) {
488 sb.append(", ");
489 }
490 }
491 sb.append(")");
492 }
493 sb.append(" ");
494 if (getExpression() != null) {
495 sb.append(getExpression());
496 }
497 else {
498 sb.append("<pointcut expression not set>");
499 }
500 return sb.toString();
501 }
502
503
504
505
506
507
508
509
510
511
512 private class BeanNamePointcutDesignatorHandler implements PointcutDesignatorHandler {
513
514 private static final String BEAN_DESIGNATOR_NAME = "bean";
515
516 @Override
517 public String getDesignatorName() {
518 return BEAN_DESIGNATOR_NAME;
519 }
520
521 @Override
522 public ContextBasedMatcher parse(String expression) {
523 return new BeanNameContextMatcher(expression);
524 }
525 }
526
527
528
529
530
531
532
533
534
535 private class BeanNameContextMatcher implements ContextBasedMatcher {
536
537 private final NamePattern expressionPattern;
538
539 public BeanNameContextMatcher(String expression) {
540 this.expressionPattern = new NamePattern(expression);
541 }
542
543 @Override
544 @SuppressWarnings("rawtypes")
545 @Deprecated
546 public boolean couldMatchJoinPointsInType(Class someClass) {
547 return (contextMatch(someClass) == FuzzyBoolean.YES);
548 }
549
550 @Override
551 @SuppressWarnings("rawtypes")
552 @Deprecated
553 public boolean couldMatchJoinPointsInType(Class someClass, MatchingContext context) {
554 return (contextMatch(someClass) == FuzzyBoolean.YES);
555 }
556
557 @Override
558 public boolean matchesDynamically(MatchingContext context) {
559 return true;
560 }
561
562 @Override
563 public FuzzyBoolean matchesStatically(MatchingContext context) {
564 return contextMatch(null);
565 }
566
567 @Override
568 public boolean mayNeedDynamicTest() {
569 return false;
570 }
571
572 private FuzzyBoolean contextMatch(Class<?> targetType) {
573 String advisedBeanName = getCurrentProxiedBeanName();
574 if (advisedBeanName == null) {
575
576 return FuzzyBoolean.MAYBE;
577 }
578 if (BeanFactoryUtils.isGeneratedBeanName(advisedBeanName)) {
579 return FuzzyBoolean.NO;
580 }
581 if (targetType != null) {
582 boolean isFactory = FactoryBean.class.isAssignableFrom(targetType);
583 return FuzzyBoolean.fromBoolean(
584 matchesBeanName(isFactory ? BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName : advisedBeanName));
585 }
586 else {
587 return FuzzyBoolean.fromBoolean(matchesBeanName(advisedBeanName) ||
588 matchesBeanName(BeanFactory.FACTORY_BEAN_PREFIX + advisedBeanName));
589 }
590 }
591
592 private boolean matchesBeanName(String advisedBeanName) {
593 if (this.expressionPattern.matches(advisedBeanName)) {
594 return true;
595 }
596 if (beanFactory != null) {
597 String[] aliases = beanFactory.getAliases(advisedBeanName);
598 for (String alias : aliases) {
599 if (this.expressionPattern.matches(alias)) {
600 return true;
601 }
602 }
603 }
604 return false;
605 }
606 }
607
608
609
610
611
612
613 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
614
615 ois.defaultReadObject();
616
617
618
619 this.shadowMatchCache = new ConcurrentHashMap<Method, ShadowMatch>(32);
620 }
621
622
623 private static class DefensiveShadowMatch implements ShadowMatch {
624
625 private final ShadowMatch primary;
626
627 private final ShadowMatch other;
628
629 public DefensiveShadowMatch(ShadowMatch primary, ShadowMatch other) {
630 this.primary = primary;
631 this.other = other;
632 }
633
634 @Override
635 public boolean alwaysMatches() {
636 return this.primary.alwaysMatches();
637 }
638
639 @Override
640 public boolean maybeMatches() {
641 return this.primary.maybeMatches();
642 }
643
644 @Override
645 public boolean neverMatches() {
646 return this.primary.neverMatches();
647 }
648
649 @Override
650 public JoinPointMatch matchesJoinPoint(Object thisObject, Object targetObject, Object[] args) {
651 try {
652 return this.primary.matchesJoinPoint(thisObject, targetObject, args);
653 }
654 catch (ReflectionWorldException ex) {
655 return this.other.matchesJoinPoint(thisObject, targetObject, args);
656 }
657 }
658
659 @Override
660 public void setMatchingContext(MatchingContext aMatchContext) {
661 this.primary.setMatchingContext(aMatchContext);
662 this.other.setMatchingContext(aMatchContext);
663 }
664 }
665
666 }