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.lang.annotation.Annotation;
20 import java.lang.reflect.Constructor;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Set;
26
27 import org.aspectj.lang.JoinPoint;
28 import org.aspectj.lang.ProceedingJoinPoint;
29 import org.aspectj.weaver.tools.PointcutParser;
30 import org.aspectj.weaver.tools.PointcutPrimitive;
31
32 import org.springframework.core.ParameterNameDiscoverer;
33 import org.springframework.util.StringUtils;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer {
119
120 private static final String THIS_JOIN_POINT = "thisJoinPoint";
121 private static final String THIS_JOIN_POINT_STATIC_PART = "thisJoinPointStaticPart";
122
123
124 private static final int STEP_JOIN_POINT_BINDING = 1;
125 private static final int STEP_THROWING_BINDING = 2;
126 private static final int STEP_ANNOTATION_BINDING = 3;
127 private static final int STEP_RETURNING_BINDING = 4;
128 private static final int STEP_PRIMITIVE_ARGS_BINDING = 5;
129 private static final int STEP_THIS_TARGET_ARGS_BINDING = 6;
130 private static final int STEP_REFERENCE_PCUT_BINDING = 7;
131 private static final int STEP_FINISHED = 8;
132
133 private static final Set<String> singleValuedAnnotationPcds = new HashSet<String>();
134 private static final Set<String> nonReferencePointcutTokens = new HashSet<String>();
135
136
137 static {
138 singleValuedAnnotationPcds.add("@this");
139 singleValuedAnnotationPcds.add("@target");
140 singleValuedAnnotationPcds.add("@within");
141 singleValuedAnnotationPcds.add("@withincode");
142 singleValuedAnnotationPcds.add("@annotation");
143
144 Set<PointcutPrimitive> pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives();
145 for (PointcutPrimitive primitive : pointcutPrimitives) {
146 nonReferencePointcutTokens.add(primitive.getName());
147 }
148 nonReferencePointcutTokens.add("&&");
149 nonReferencePointcutTokens.add("!");
150 nonReferencePointcutTokens.add("||");
151 nonReferencePointcutTokens.add("and");
152 nonReferencePointcutTokens.add("or");
153 nonReferencePointcutTokens.add("not");
154 }
155
156
157 private boolean raiseExceptions;
158
159
160
161
162 private String returningName;
163
164
165
166
167 private String throwingName;
168
169
170
171
172 private String pointcutExpression;
173
174 private Class<?>[] argumentTypes;
175
176 private String[] parameterNameBindings;
177
178 private int numberOfRemainingUnboundArguments;
179
180
181
182
183
184
185 public AspectJAdviceParameterNameDiscoverer(String pointcutExpression) {
186 this.pointcutExpression = pointcutExpression;
187 }
188
189
190
191
192
193
194 public void setRaiseExceptions(boolean raiseExceptions) {
195 this.raiseExceptions = raiseExceptions;
196 }
197
198
199
200
201
202
203 public void setReturningName(String returningName) {
204 this.returningName = returningName;
205 }
206
207
208
209
210
211
212 public void setThrowingName(String throwingName) {
213 this.throwingName = throwingName;
214 }
215
216
217
218
219
220
221
222
223 @Override
224 public String[] getParameterNames(Method method) {
225 this.argumentTypes = method.getParameterTypes();
226 this.numberOfRemainingUnboundArguments = this.argumentTypes.length;
227 this.parameterNameBindings = new String[this.numberOfRemainingUnboundArguments];
228
229 int minimumNumberUnboundArgs = 0;
230 if (this.returningName != null) {
231 minimumNumberUnboundArgs++;
232 }
233 if (this.throwingName != null) {
234 minimumNumberUnboundArgs++;
235 }
236 if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) {
237 throw new IllegalStateException(
238 "Not enough arguments in method to satisfy binding of returning and throwing variables");
239 }
240
241 try {
242 int algorithmicStep = STEP_JOIN_POINT_BINDING;
243 while ((this.numberOfRemainingUnboundArguments > 0) && algorithmicStep < STEP_FINISHED) {
244 switch (algorithmicStep++) {
245 case STEP_JOIN_POINT_BINDING:
246 if (!maybeBindThisJoinPoint()) {
247 maybeBindThisJoinPointStaticPart();
248 }
249 break;
250 case STEP_THROWING_BINDING:
251 maybeBindThrowingVariable();
252 break;
253 case STEP_ANNOTATION_BINDING:
254 maybeBindAnnotationsFromPointcutExpression();
255 break;
256 case STEP_RETURNING_BINDING:
257 maybeBindReturningVariable();
258 break;
259 case STEP_PRIMITIVE_ARGS_BINDING:
260 maybeBindPrimitiveArgsFromPointcutExpression();
261 break;
262 case STEP_THIS_TARGET_ARGS_BINDING:
263 maybeBindThisOrTargetOrArgsFromPointcutExpression();
264 break;
265 case STEP_REFERENCE_PCUT_BINDING:
266 maybeBindReferencePointcutParameter();
267 break;
268 default:
269 throw new IllegalStateException("Unknown algorithmic step: " + (algorithmicStep - 1));
270 }
271 }
272 }
273 catch (AmbiguousBindingException ambigEx) {
274 if (this.raiseExceptions) {
275 throw ambigEx;
276 }
277 else {
278 return null;
279 }
280 }
281 catch (IllegalArgumentException ex) {
282 if (this.raiseExceptions) {
283 throw ex;
284 }
285 else {
286 return null;
287 }
288 }
289
290 if (this.numberOfRemainingUnboundArguments == 0) {
291 return this.parameterNameBindings;
292 }
293 else {
294 if (this.raiseExceptions) {
295 throw new IllegalStateException("Failed to bind all argument names: " +
296 this.numberOfRemainingUnboundArguments + " argument(s) could not be bound");
297 }
298 else {
299
300 return null;
301 }
302 }
303 }
304
305
306
307
308
309
310
311 @Override
312 public String[] getParameterNames(Constructor<?> ctor) {
313 if (this.raiseExceptions) {
314 throw new UnsupportedOperationException("An advice method can never be a constructor");
315 }
316 else {
317
318
319 return null;
320 }
321 }
322
323
324 private void bindParameterName(int index, String name) {
325 this.parameterNameBindings[index] = name;
326 this.numberOfRemainingUnboundArguments--;
327 }
328
329
330
331
332
333 private boolean maybeBindThisJoinPoint() {
334 if ((this.argumentTypes[0] == JoinPoint.class) || (this.argumentTypes[0] == ProceedingJoinPoint.class)) {
335 bindParameterName(0, THIS_JOIN_POINT);
336 return true;
337 }
338 else {
339 return false;
340 }
341 }
342
343 private void maybeBindThisJoinPointStaticPart() {
344 if (this.argumentTypes[0] == JoinPoint.StaticPart.class) {
345 bindParameterName(0, THIS_JOIN_POINT_STATIC_PART);
346 }
347 }
348
349
350
351
352
353 private void maybeBindThrowingVariable() {
354 if (this.throwingName == null) {
355 return;
356 }
357
358
359 int throwableIndex = -1;
360 for (int i = 0; i < this.argumentTypes.length; i++) {
361 if (isUnbound(i) && isSubtypeOf(Throwable.class, i)) {
362 if (throwableIndex == -1) {
363 throwableIndex = i;
364 }
365 else {
366
367 throw new AmbiguousBindingException("Binding of throwing parameter '" +
368 this.throwingName + "' is ambiguous: could be bound to argument " +
369 throwableIndex + " or argument " + i);
370 }
371 }
372 }
373
374 if (throwableIndex == -1) {
375 throw new IllegalStateException("Binding of throwing parameter '" + this.throwingName
376 + "' could not be completed as no available arguments are a subtype of Throwable");
377 }
378 else {
379 bindParameterName(throwableIndex, this.throwingName);
380 }
381 }
382
383
384
385
386 private void maybeBindReturningVariable() {
387 if (this.numberOfRemainingUnboundArguments == 0) {
388 throw new IllegalStateException(
389 "Algorithm assumes that there must be at least one unbound parameter on entry to this method");
390 }
391
392 if (this.returningName != null) {
393 if (this.numberOfRemainingUnboundArguments > 1) {
394 throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName +
395 "' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates.");
396 }
397
398
399 for (int i = 0; i < this.parameterNameBindings.length; i++) {
400 if (this.parameterNameBindings[i] == null) {
401 bindParameterName(i, this.returningName);
402 break;
403 }
404 }
405 }
406 }
407
408
409
410
411
412
413
414
415
416 private void maybeBindAnnotationsFromPointcutExpression() {
417 List<String> varNames = new ArrayList<String>();
418 String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
419 for (int i = 0; i < tokens.length; i++) {
420 String toMatch = tokens[i];
421 int firstParenIndex = toMatch.indexOf("(");
422 if (firstParenIndex != -1) {
423 toMatch = toMatch.substring(0, firstParenIndex);
424 }
425 if (singleValuedAnnotationPcds.contains(toMatch)) {
426 PointcutBody body = getPointcutBody(tokens, i);
427 i += body.numTokensConsumed;
428 String varName = maybeExtractVariableName(body.text);
429 if (varName != null) {
430 varNames.add(varName);
431 }
432 }
433 else if (tokens[i].startsWith("@args(") || tokens[i].equals("@args")) {
434 PointcutBody body = getPointcutBody(tokens, i);
435 i += body.numTokensConsumed;
436 maybeExtractVariableNamesFromArgs(body.text, varNames);
437 }
438 }
439
440 bindAnnotationsFromVarNames(varNames);
441 }
442
443
444
445
446 private void bindAnnotationsFromVarNames(List<String> varNames) {
447 if (!varNames.isEmpty()) {
448
449 int numAnnotationSlots = countNumberOfUnboundAnnotationArguments();
450 if (numAnnotationSlots > 1) {
451 throw new AmbiguousBindingException("Found " + varNames.size() +
452 " potential annotation variable(s), and " +
453 numAnnotationSlots + " potential argument slots");
454 }
455 else if (numAnnotationSlots == 1) {
456 if (varNames.size() == 1) {
457
458 findAndBind(Annotation.class, varNames.get(0));
459 }
460 else {
461
462 throw new IllegalArgumentException("Found " + varNames.size() +
463 " candidate annotation binding variables" +
464 " but only one potential argument binding slot");
465 }
466 }
467 else {
468
469 }
470 }
471 }
472
473
474
475
476 private String maybeExtractVariableName(String candidateToken) {
477 if (candidateToken == null || candidateToken.equals("")) {
478 return null;
479 }
480 if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) &&
481 Character.isLowerCase(candidateToken.charAt(0))) {
482 char[] tokenChars = candidateToken.toCharArray();
483 for (char tokenChar : tokenChars) {
484 if (!Character.isJavaIdentifierPart(tokenChar)) {
485 return null;
486 }
487 }
488 return candidateToken;
489 }
490 else {
491 return null;
492 }
493 }
494
495
496
497
498
499 private void maybeExtractVariableNamesFromArgs(String argsSpec, List<String> varNames) {
500 if (argsSpec == null) {
501 return;
502 }
503 String[] tokens = StringUtils.tokenizeToStringArray(argsSpec, ",");
504 for (int i = 0; i < tokens.length; i++) {
505 tokens[i] = StringUtils.trimWhitespace(tokens[i]);
506 String varName = maybeExtractVariableName(tokens[i]);
507 if (varName != null) {
508 varNames.add(varName);
509 }
510 }
511 }
512
513
514
515
516
517 private void maybeBindThisOrTargetOrArgsFromPointcutExpression() {
518 if (this.numberOfRemainingUnboundArguments > 1) {
519 throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
520 + " unbound args at this(),target(),args() binding stage, with no way to determine between them");
521 }
522
523 List<String> varNames = new ArrayList<String>();
524 String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
525 for (int i = 0; i < tokens.length; i++) {
526 if (tokens[i].equals("this") ||
527 tokens[i].startsWith("this(") ||
528 tokens[i].equals("target") ||
529 tokens[i].startsWith("target(")) {
530 PointcutBody body = getPointcutBody(tokens, i);
531 i += body.numTokensConsumed;
532 String varName = maybeExtractVariableName(body.text);
533 if (varName != null) {
534 varNames.add(varName);
535 }
536 }
537 else if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {
538 PointcutBody body = getPointcutBody(tokens, i);
539 i += body.numTokensConsumed;
540 List<String> candidateVarNames = new ArrayList<String>();
541 maybeExtractVariableNamesFromArgs(body.text, candidateVarNames);
542
543
544 for (String varName : candidateVarNames) {
545 if (!alreadyBound(varName)) {
546 varNames.add(varName);
547 }
548 }
549 }
550 }
551
552
553 if (varNames.size() > 1) {
554 throw new AmbiguousBindingException("Found " + varNames.size() +
555 " candidate this(), target() or args() variables but only one unbound argument slot");
556 }
557 else if (varNames.size() == 1) {
558 for (int j = 0; j < this.parameterNameBindings.length; j++) {
559 if (isUnbound(j)) {
560 bindParameterName(j, varNames.get(0));
561 break;
562 }
563 }
564 }
565
566 }
567
568 private void maybeBindReferencePointcutParameter() {
569 if (this.numberOfRemainingUnboundArguments > 1) {
570 throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments
571 + " unbound args at reference pointcut binding stage, with no way to determine between them");
572 }
573
574 List<String> varNames = new ArrayList<String>();
575 String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
576 for (int i = 0; i < tokens.length; i++) {
577 String toMatch = tokens[i];
578 if (toMatch.startsWith("!")) {
579 toMatch = toMatch.substring(1);
580 }
581 int firstParenIndex = toMatch.indexOf("(");
582 if (firstParenIndex != -1) {
583 toMatch = toMatch.substring(0, firstParenIndex);
584 }
585 else {
586 if (tokens.length < i + 2) {
587
588 continue;
589 }
590 else {
591 String nextToken = tokens[i + 1];
592 if (nextToken.charAt(0) != '(') {
593
594 continue;
595 }
596 }
597
598 }
599
600
601 PointcutBody body = getPointcutBody(tokens, i);
602 i += body.numTokensConsumed;
603
604 if (!nonReferencePointcutTokens.contains(toMatch)) {
605
606 String varName = maybeExtractVariableName(body.text);
607 if (varName != null) {
608 varNames.add(varName);
609 }
610 }
611 }
612
613 if (varNames.size() > 1) {
614 throw new AmbiguousBindingException("Found " + varNames.size() +
615 " candidate reference pointcut variables but only one unbound argument slot");
616 }
617 else if (varNames.size() == 1) {
618 for (int j = 0; j < this.parameterNameBindings.length; j++) {
619 if (isUnbound(j)) {
620 bindParameterName(j, varNames.get(0));
621 break;
622 }
623 }
624 }
625
626 }
627
628
629
630
631
632 private PointcutBody getPointcutBody(String[] tokens, int startIndex) {
633 int numTokensConsumed = 0;
634 String currentToken = tokens[startIndex];
635 int bodyStart = currentToken.indexOf('(');
636 if (currentToken.charAt(currentToken.length() - 1) == ')') {
637
638 return new PointcutBody(0, currentToken.substring(bodyStart + 1, currentToken.length() - 1));
639 }
640 else {
641 StringBuilder sb = new StringBuilder();
642 if (bodyStart >= 0 && bodyStart != (currentToken.length() - 1)) {
643 sb.append(currentToken.substring(bodyStart + 1));
644 sb.append(" ");
645 }
646 numTokensConsumed++;
647 int currentIndex = startIndex + numTokensConsumed;
648 while (currentIndex < tokens.length) {
649 if (tokens[currentIndex].equals("(")) {
650 currentIndex++;
651 continue;
652 }
653
654 if (tokens[currentIndex].endsWith(")")) {
655 sb.append(tokens[currentIndex].substring(0, tokens[currentIndex].length() - 1));
656 return new PointcutBody(numTokensConsumed, sb.toString().trim());
657 }
658
659 String toAppend = tokens[currentIndex];
660 if (toAppend.startsWith("(")) {
661 toAppend = toAppend.substring(1);
662 }
663 sb.append(toAppend);
664 sb.append(" ");
665 currentIndex++;
666 numTokensConsumed++;
667 }
668
669 }
670
671
672 return new PointcutBody(numTokensConsumed, null);
673 }
674
675
676
677
678 private void maybeBindPrimitiveArgsFromPointcutExpression() {
679 int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments();
680 if (numUnboundPrimitives > 1) {
681 throw new AmbiguousBindingException("Found '" + numUnboundPrimitives +
682 "' unbound primitive arguments with no way to distinguish between them.");
683 }
684 if (numUnboundPrimitives == 1) {
685
686 List<String> varNames = new ArrayList<String>();
687 String[] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " ");
688 for (int i = 0; i < tokens.length; i++) {
689 if (tokens[i].equals("args") || tokens[i].startsWith("args(")) {
690 PointcutBody body = getPointcutBody(tokens, i);
691 i += body.numTokensConsumed;
692 maybeExtractVariableNamesFromArgs(body.text, varNames);
693 }
694 }
695 if (varNames.size() > 1) {
696 throw new AmbiguousBindingException("Found " + varNames.size() +
697 " candidate variable names but only one candidate binding slot when matching primitive args");
698 }
699 else if (varNames.size() == 1) {
700
701 for (int i = 0; i < this.argumentTypes.length; i++) {
702 if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) {
703 bindParameterName(i, varNames.get(0));
704 break;
705 }
706 }
707 }
708 }
709 }
710
711
712
713
714
715 private boolean isUnbound(int i) {
716 return this.parameterNameBindings[i] == null;
717 }
718
719 private boolean alreadyBound(String varName) {
720 for (int i = 0; i < this.parameterNameBindings.length; i++) {
721 if (!isUnbound(i) && varName.equals(this.parameterNameBindings[i])) {
722 return true;
723 }
724 }
725 return false;
726 }
727
728
729
730
731
732 private boolean isSubtypeOf(Class<?> supertype, int argumentNumber) {
733 return supertype.isAssignableFrom(this.argumentTypes[argumentNumber]);
734 }
735
736 private int countNumberOfUnboundAnnotationArguments() {
737 int count = 0;
738 for (int i = 0; i < this.argumentTypes.length; i++) {
739 if (isUnbound(i) && isSubtypeOf(Annotation.class, i)) {
740 count++;
741 }
742 }
743 return count;
744 }
745
746 private int countNumberOfUnboundPrimitiveArguments() {
747 int count = 0;
748 for (int i = 0; i < this.argumentTypes.length; i++) {
749 if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) {
750 count++;
751 }
752 }
753 return count;
754 }
755
756
757
758
759
760 private void findAndBind(Class<?> argumentType, String varName) {
761 for (int i = 0; i < this.argumentTypes.length; i++) {
762 if (isUnbound(i) && isSubtypeOf(argumentType, i)) {
763 bindParameterName(i, varName);
764 return;
765 }
766 }
767 throw new IllegalStateException("Expected to find an unbound argument of type '" +
768 argumentType.getName() + "'");
769 }
770
771
772
773
774
775
776 private static class PointcutBody {
777
778 private int numTokensConsumed;
779
780 private String text;
781
782 public PointcutBody(int tokens, String text) {
783 this.numTokensConsumed = tokens;
784 this.text = text;
785 }
786 }
787
788
789
790
791
792
793 @SuppressWarnings("serial")
794 public static class AmbiguousBindingException extends RuntimeException {
795
796
797
798
799
800 public AmbiguousBindingException(String msg) {
801 super(msg);
802 }
803 }
804
805 }