1 16 17 package org.springframework.aop.aspectj; 18 19 import java.lang.reflect.Constructor ; 20 import java.lang.reflect.Method ; 21 import java.util.ArrayList ; 22 import java.util.HashSet ; 23 import java.util.Iterator ; 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.ClassUtils; 34 import org.springframework.util.StringUtils; 35 36 119 public class AspectJAdviceParameterNameDiscoverer implements ParameterNameDiscoverer { 120 121 private static final String ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation"; 122 123 private static final String THIS_JOIN_POINT = "thisJoinPoint"; 124 private static final String THIS_JOIN_POINT_STATIC_PART = "thisJoinPointStaticPart"; 125 126 private static final int STEP_JOIN_POINT_BINDING = 1; 128 private static final int STEP_THROWING_BINDING = 2; 129 private static final int STEP_ANNOTATION_BINDING = 3; 130 private static final int STEP_RETURNING_BINDING = 4; 131 private static final int STEP_PRIMITIVE_ARGS_BINDING = 5; 132 private static final int STEP_THIS_TARGET_ARGS_BINDING = 6; 133 private static final int STEP_REFERENCE_PCUT_BINDING = 7; 134 private static final int STEP_FINISHED = 8; 135 136 private static final Set singleValuedAnnotationPcds = new HashSet (); 137 private static final Set nonReferencePointcutTokens = new HashSet (); 138 139 private static Class annotationClass; 140 141 142 static { 143 singleValuedAnnotationPcds.add("@this"); 144 singleValuedAnnotationPcds.add("@target"); 145 singleValuedAnnotationPcds.add("@within"); 146 singleValuedAnnotationPcds.add("@withincode"); 147 singleValuedAnnotationPcds.add("@annotation"); 148 149 Set pointcutPrimitives = PointcutParser.getAllSupportedPointcutPrimitives(); 150 for (Iterator iterator = pointcutPrimitives.iterator(); iterator.hasNext();) { 151 PointcutPrimitive primitive = (PointcutPrimitive) iterator.next(); 152 nonReferencePointcutTokens.add(primitive.getName()); 153 } 154 nonReferencePointcutTokens.add("&&"); 155 nonReferencePointcutTokens.add("!"); 156 nonReferencePointcutTokens.add("||"); 157 nonReferencePointcutTokens.add("and"); 158 nonReferencePointcutTokens.add("or"); 159 nonReferencePointcutTokens.add("not"); 160 161 try { 162 annotationClass = ClassUtils.forName(ANNOTATION_CLASS_NAME, 163 AspectJAdviceParameterNameDiscoverer.class.getClassLoader()); 164 } 165 catch (ClassNotFoundException ex) { 166 annotationClass = null; 168 } 169 } 170 171 172 private boolean raiseExceptions; 173 174 177 private String returningName; 178 179 182 private String throwingName; 183 184 187 private String pointcutExpression; 188 189 private Class [] argumentTypes; 190 191 private String [] parameterNameBindings; 192 193 private int numberOfRemainingUnboundArguments; 194 195 private int algorithmicStep = STEP_JOIN_POINT_BINDING; 196 197 198 202 public AspectJAdviceParameterNameDiscoverer(String pointcutExpression) { 203 this.pointcutExpression = pointcutExpression; 204 } 205 206 211 public void setRaiseExceptions(boolean raiseExceptions) { 212 this.raiseExceptions = raiseExceptions; 213 } 214 215 220 public void setReturningName(String returningName) { 221 this.returningName = returningName; 222 } 223 224 229 public void setThrowingName(String throwingName) { 230 this.throwingName = throwingName; 231 } 232 233 240 public String [] getParameterNames(Method method) { 241 this.argumentTypes = method.getParameterTypes(); 242 this.numberOfRemainingUnboundArguments = this.argumentTypes.length; 243 this.parameterNameBindings = new String [this.numberOfRemainingUnboundArguments]; 244 this.algorithmicStep = STEP_JOIN_POINT_BINDING; 245 246 int minimumNumberUnboundArgs = 0; 247 if (this.returningName != null) { 248 minimumNumberUnboundArgs++; 249 } 250 if (this.throwingName != null) { 251 minimumNumberUnboundArgs++; 252 } 253 if (this.numberOfRemainingUnboundArguments < minimumNumberUnboundArgs) { 254 throw new IllegalStateException ( 255 "Not enough arguments in method to satisfy binding of returning and throwing variables"); 256 } 257 258 try { 259 while ((this.numberOfRemainingUnboundArguments > 0) && (this.algorithmicStep < STEP_FINISHED)) { 260 switch (this.algorithmicStep++) { 261 case STEP_JOIN_POINT_BINDING: 262 if (!maybeBindThisJoinPoint()) { 263 maybeBindThisJoinPointStaticPart(); 264 } 265 break; 266 case STEP_THROWING_BINDING: 267 maybeBindThrowingVariable(); 268 break; 269 case STEP_ANNOTATION_BINDING: 270 maybeBindAnnotationsFromPointcutExpression(); 271 break; 272 case STEP_RETURNING_BINDING: 273 maybeBindReturningVariable(); 274 break; 275 case STEP_PRIMITIVE_ARGS_BINDING: 276 maybeBindPrimitiveArgsFromPointcutExpression(); 277 break; 278 case STEP_THIS_TARGET_ARGS_BINDING: 279 maybeBindThisOrTargetOrArgsFromPointcutExpression(); 280 break; 281 case STEP_REFERENCE_PCUT_BINDING: 282 maybeBindReferencePointcutParameter(); 283 break; 284 default: 285 throw new IllegalStateException ("Unknown algorithmic step: " + (this.algorithmicStep - 1)); 286 } 287 } 288 } 289 catch (AmbiguousBindingException ambigEx) { 290 if (this.raiseExceptions) { 291 throw ambigEx; 292 } 293 else { 294 return null; 295 } 296 } 297 catch (IllegalArgumentException ex) { 298 if (this.raiseExceptions) { 299 throw ex; 300 } 301 else { 302 return null; 303 } 304 } 305 306 if (this.numberOfRemainingUnboundArguments == 0) { 307 return this.parameterNameBindings; 308 } 309 else { 310 if (this.raiseExceptions) { 311 throw new IllegalStateException ("Failed to bind all argument names: " + 312 this.numberOfRemainingUnboundArguments + " argument(s) could not be bound"); 313 } 314 else { 315 return null; 317 } 318 } 319 } 320 321 327 public String [] getParameterNames(Constructor ctor) { 328 if (this.raiseExceptions) { 329 throw new UnsupportedOperationException ("An advice method can never be a constructor"); 330 } 331 else { 332 return null; 335 } 336 } 337 338 339 private void bindParameterName(int index, String name) { 340 this.parameterNameBindings[index] = name; 341 this.numberOfRemainingUnboundArguments--; 342 } 343 344 348 private boolean maybeBindThisJoinPoint() { 349 if ((this.argumentTypes[0] == JoinPoint.class) || (this.argumentTypes[0] == ProceedingJoinPoint.class)) { 350 bindParameterName(0, THIS_JOIN_POINT); 351 return true; 352 } 353 else { 354 return false; 355 } 356 } 357 358 private void maybeBindThisJoinPointStaticPart() { 359 if (this.argumentTypes[0] == JoinPoint.StaticPart.class) { 360 bindParameterName(0, THIS_JOIN_POINT_STATIC_PART); 361 } 362 } 363 364 368 private void maybeBindThrowingVariable() { 369 if (this.throwingName == null) { 370 return; 371 } 372 373 int throwableIndex = -1; 375 for (int i = 0; i < this.argumentTypes.length; i++) { 376 if (isUnbound(i) && isSubtypeOf(Throwable .class, i)) { 377 if (throwableIndex == -1) { 378 throwableIndex = i; 379 } 380 else { 381 throw new AmbiguousBindingException("Binding of throwing parameter '" + 383 this.throwingName + "' is ambiguous: could be bound to argument " + 384 throwableIndex + " or argument " + i); 385 } 386 } 387 } 388 389 if (throwableIndex == -1) { 390 throw new IllegalStateException ("Binding of throwing parameter '" + this.throwingName 391 + "' could not be completed as no available arguments are a subtype of Throwable"); 392 } 393 else { 394 bindParameterName(throwableIndex, this.throwingName); 395 } 396 } 397 398 401 private void maybeBindReturningVariable() { 402 if (this.numberOfRemainingUnboundArguments == 0) { 403 throw new IllegalStateException ( 404 "Algorithm assumes that there must be at least one unbound parameter on entry to this method"); 405 } 406 407 if (this.returningName != null) { 408 if (this.numberOfRemainingUnboundArguments > 1) { 409 throw new AmbiguousBindingException("Binding of returning parameter '" + this.returningName + 410 "' is ambiguous, there are " + this.numberOfRemainingUnboundArguments + " candidates."); 411 } 412 413 for (int i = 0; i < this.parameterNameBindings.length; i++) { 415 if (this.parameterNameBindings[i] == null) { 416 bindParameterName(i, this.returningName); 417 break; 418 } 419 } 420 } 421 } 422 423 424 431 private void maybeBindAnnotationsFromPointcutExpression() { 432 List varNames = new ArrayList (); 433 String [] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); 434 for (int i = 0; i < tokens.length; i++) { 435 String toMatch = tokens[i]; 436 int firstParenIndex = toMatch.indexOf("("); 437 if (firstParenIndex != -1) { 438 toMatch = toMatch.substring(0, firstParenIndex); 439 } 440 if (singleValuedAnnotationPcds.contains(toMatch)) { 441 PointcutBody body = getPointcutBody(tokens, i); 442 i += body.numTokensConsumed; 443 String varName = maybeExtractVariableName(body.text); 444 if (varName != null) { 445 varNames.add(varName); 446 } 447 } 448 else if (tokens[i].startsWith("@args(") || tokens[i].equals("@args")) { 449 PointcutBody body = getPointcutBody(tokens, i); 450 i += body.numTokensConsumed; 451 maybeExtractVariableNamesFromArgs(body.text, varNames); 452 } 453 } 454 455 bindAnnotationsFromVarNames(varNames); 456 } 457 458 461 private void bindAnnotationsFromVarNames(List varNames) { 462 if (!varNames.isEmpty()) { 463 int numAnnotationSlots = countNumberOfUnboundAnnotationArguments(); 465 if (numAnnotationSlots > 1) { 466 throw new AmbiguousBindingException("Found " + varNames.size() + 467 " potential annotation variable(s), and " + 468 numAnnotationSlots + " potential argument slots"); 469 } 470 else if (numAnnotationSlots == 1) { 471 if (varNames.size() == 1) { 472 findAndBind(annotationClass, (String ) varNames.get(0)); 474 } 475 else { 476 throw new IllegalArgumentException ("Found " + varNames.size() + 478 " candidate annotation binding variables" + 479 " but only one potential argument binding slot"); 480 } 481 } 482 else { 483 } 485 } 486 } 487 488 491 private String maybeExtractVariableName(String candidateToken) { 492 if (candidateToken == null || candidateToken.equals("")) { 493 return null; 494 } 495 if (Character.isJavaIdentifierStart(candidateToken.charAt(0)) && 496 Character.isLowerCase(candidateToken.charAt(0))) { 497 char[] tokenChars = candidateToken.toCharArray(); 498 for (int i = 0; i < tokenChars.length; i++) { 499 if (!Character.isJavaIdentifierPart(tokenChars[i])) { 500 return null; 501 } 502 } 503 return candidateToken; 504 } 505 else { 506 return null; 507 } 508 } 509 510 514 private void maybeExtractVariableNamesFromArgs(String argsSpec, List varNames) { 515 if (argsSpec == null) { 516 return; 517 } 518 519 String [] tokens = StringUtils.tokenizeToStringArray(argsSpec, ","); 520 for (int i = 0; i < tokens.length; i++) { 521 tokens[i] = StringUtils.trimWhitespace(tokens[i]); 522 String varName = maybeExtractVariableName(tokens[i]); 523 if (varName != null) { 524 varNames.add(varName); 525 } 526 } 527 } 528 529 533 private void maybeBindThisOrTargetOrArgsFromPointcutExpression() { 534 if (this.numberOfRemainingUnboundArguments > 1) { 535 throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments 536 + " unbound args at this(),target(),args() binding stage, with no way to determine between them"); 537 } 538 539 List varNames = new ArrayList (); 540 String [] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); 541 for (int i = 0; i < tokens.length; i++) { 542 if (tokens[i].equals("this") || 543 tokens[i].startsWith("this(") || 544 tokens[i].equals("target") || 545 tokens[i].startsWith("target(")) { 546 PointcutBody body = getPointcutBody(tokens, i); 547 i += body.numTokensConsumed; 548 String varName = maybeExtractVariableName(body.text); 549 if (varName != null) { 550 varNames.add(varName); 551 } 552 } 553 else if (tokens[i].equals("args") || tokens[i].startsWith("args(")) { 554 PointcutBody body = getPointcutBody(tokens, i); 555 i += body.numTokensConsumed; 556 List candidateVarNames = new ArrayList (); 557 maybeExtractVariableNamesFromArgs(body.text, candidateVarNames); 558 for (Iterator iter = candidateVarNames.iterator(); iter.hasNext();) { 561 String varName = (String ) iter.next(); 562 if (!alreadyBound(varName)) { 563 varNames.add(varName); 564 } 565 } 566 } 567 } 568 569 570 if (varNames.size() > 1) { 571 throw new AmbiguousBindingException("Found " + varNames.size() + 572 " candidate this(), target() or args() variables but only one unbound argument slot"); 573 } 574 else if (varNames.size() == 1) { 575 for (int j = 0; j < this.parameterNameBindings.length; j++) { 576 if (isUnbound(j)) { 577 bindParameterName(j, (String ) varNames.get(0)); 578 break; 579 } 580 } 581 } 582 } 584 585 private void maybeBindReferencePointcutParameter() { 586 if (this.numberOfRemainingUnboundArguments > 1) { 587 throw new AmbiguousBindingException("Still " + this.numberOfRemainingUnboundArguments 588 + " unbound args at reference pointcut binding stage, with no way to determine between them"); 589 } 590 591 List varNames = new ArrayList (); 592 String [] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); 593 for (int i = 0; i < tokens.length; i++) { 594 String toMatch = tokens[i]; 595 if (toMatch.startsWith("!")) { 596 toMatch = toMatch.substring(1); 597 } 598 int firstParenIndex = toMatch.indexOf("("); 599 if (firstParenIndex != -1) { 600 toMatch = toMatch.substring(0, firstParenIndex); 601 } 602 else { 603 if (tokens.length < i + 2) { 604 continue; 606 } 607 else { 608 String nextToken = tokens[i + 1]; 609 if (nextToken.charAt(0) != '(') { 610 continue; 612 } 613 } 614 615 } 616 617 PointcutBody body = getPointcutBody(tokens, i); 619 i += body.numTokensConsumed; 620 621 if (!nonReferencePointcutTokens.contains(toMatch)) { 622 String varName = maybeExtractVariableName(body.text); 624 if (varName != null) { 625 varNames.add(varName); 626 } 627 } 628 } 629 630 if (varNames.size() > 1) { 631 throw new AmbiguousBindingException("Found " + varNames.size() + 632 " candidate reference pointcut variables but only one unbound argument slot"); 633 } 634 else if (varNames.size() == 1) { 635 for (int j = 0; j < this.parameterNameBindings.length; j++) { 636 if (isUnbound(j)) { 637 bindParameterName(j, (String ) varNames.get(0)); 638 break; 639 } 640 } 641 } 642 } 644 645 649 private PointcutBody getPointcutBody(String [] tokens, int startIndex) { 650 int numTokensConsumed = 0; 651 String currentToken = tokens[startIndex]; 652 int bodyStart = currentToken.indexOf('('); 653 if (currentToken.charAt(currentToken.length() - 1) == ')') { 654 return new PointcutBody(0, currentToken.substring(bodyStart + 1, currentToken.length() - 1)); 656 } 657 else { 658 StringBuffer sb = new StringBuffer (); 659 if (bodyStart >= 0 && bodyStart != (currentToken.length() - 1)) { 660 sb.append(currentToken.substring(bodyStart + 1)); 661 sb.append(" "); 662 } 663 numTokensConsumed++; 664 int currentIndex = startIndex + numTokensConsumed; 665 while (currentIndex < tokens.length) { 666 if (tokens[currentIndex].equals("(")) { 667 currentIndex++; 668 continue; 669 } 670 671 if (tokens[currentIndex].endsWith(")")) { 672 sb.append(tokens[currentIndex].substring(0, tokens[currentIndex].length() - 1)); 673 return new PointcutBody(numTokensConsumed, sb.toString().trim()); 674 } 675 676 String toAppend = tokens[currentIndex]; 677 if (toAppend.startsWith("(")) { 678 toAppend = toAppend.substring(1); 679 } 680 sb.append(toAppend); 681 sb.append(" "); 682 currentIndex++; 683 numTokensConsumed++; 684 } 685 686 } 687 688 return new PointcutBody(numTokensConsumed, null); 690 } 691 692 695 private void maybeBindPrimitiveArgsFromPointcutExpression() { 696 int numUnboundPrimitives = countNumberOfUnboundPrimitiveArguments(); 697 if (numUnboundPrimitives > 1) { 698 throw new AmbiguousBindingException("Found '" + numUnboundPrimitives + 699 "' unbound primitive arguments with no way to distinguish between them."); 700 } 701 if (numUnboundPrimitives == 1) { 702 List varNames = new ArrayList (); 704 String [] tokens = StringUtils.tokenizeToStringArray(this.pointcutExpression, " "); 705 for (int i = 0; i < tokens.length; i++) { 706 if (tokens[i].equals("args") || tokens[i].startsWith("args(")) { 707 PointcutBody body = getPointcutBody(tokens, i); 708 i += body.numTokensConsumed; 709 maybeExtractVariableNamesFromArgs(body.text, varNames); 710 } 711 } 712 if (varNames.size() > 1) { 713 throw new AmbiguousBindingException("Found " + varNames.size() + 714 " candidate variable names but only one candidate binding slot when matching primitive args"); 715 } 716 else if (varNames.size() == 1) { 717 for (int i = 0; i < this.argumentTypes.length; i++) { 719 if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) { 720 bindParameterName(i, (String ) varNames.get(0)); 721 break; 722 } 723 } 724 } 725 } 726 } 727 728 732 private boolean isUnbound(int i) { 733 return this.parameterNameBindings[i] == null; 734 } 735 736 private boolean alreadyBound(String varName) { 737 for (int i = 0; i < this.parameterNameBindings.length; i++) { 738 if (!isUnbound(i) && varName.equals(this.parameterNameBindings[i])) { 739 return true; 740 } 741 } 742 return false; 743 } 744 745 749 private boolean isSubtypeOf(Class supertype, int argumentNumber) { 750 return supertype.isAssignableFrom(this.argumentTypes[argumentNumber]); 751 } 752 753 private int countNumberOfUnboundAnnotationArguments() { 754 if (annotationClass == null) { 755 return 0; 757 } 758 759 int count = 0; 760 for (int i = 0; i < this.argumentTypes.length; i++) { 761 if (isUnbound(i) && isSubtypeOf(annotationClass, i)) { 762 count++; 763 } 764 } 765 return count; 766 } 767 768 private int countNumberOfUnboundPrimitiveArguments() { 769 int count = 0; 770 for (int i = 0; i < this.argumentTypes.length; i++) { 771 if (isUnbound(i) && this.argumentTypes[i].isPrimitive()) { 772 count++; 773 } 774 } 775 return count; 776 } 777 778 782 private void findAndBind(Class argumentType, String varName) { 783 for (int i = 0; i < this.argumentTypes.length; i++) { 784 if (isUnbound(i) && isSubtypeOf(argumentType, i)) { 785 bindParameterName(i, varName); 786 return; 787 } 788 } 789 throw new IllegalStateException ("Expected to find an unbound argument of type '" + 790 argumentType.getName() + "'"); 791 } 792 793 794 798 private static class PointcutBody { 799 800 private int numTokensConsumed; 801 802 private String text; 803 804 public PointcutBody(int tokens, String text) { 805 this.numTokensConsumed = tokens; 806 this.text = text; 807 } 808 } 809 810 811 815 public static class AmbiguousBindingException extends RuntimeException { 816 817 821 public AmbiguousBindingException(String msg) { 822 super(msg); 823 } 824 } 825 826 } 827 | Popular Tags |