001 /******************************************************************************* 002 * Portions created by Sebastian Thomschke are copyright (c) 2005-2012 Sebastian 003 * Thomschke. 004 * 005 * All Rights Reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 * 010 * Contributors: 011 * Sebastian Thomschke - initial implementation. 012 *******************************************************************************/ 013 package net.sf.oval.guard; 014 015 import java.lang.reflect.Constructor; 016 import java.lang.reflect.Method; 017 import java.util.Collection; 018 import java.util.Iterator; 019 import java.util.LinkedList; 020 import java.util.List; 021 import java.util.Map; 022 import java.util.Set; 023 import java.util.WeakHashMap; 024 025 import net.sf.oval.Check; 026 import net.sf.oval.CheckExclusion; 027 import net.sf.oval.ConstraintViolation; 028 import net.sf.oval.Validator; 029 import net.sf.oval.configuration.Configurer; 030 import net.sf.oval.context.ConstructorParameterContext; 031 import net.sf.oval.context.MethodEntryContext; 032 import net.sf.oval.context.MethodExitContext; 033 import net.sf.oval.context.MethodParameterContext; 034 import net.sf.oval.context.MethodReturnValueContext; 035 import net.sf.oval.context.OValContext; 036 import net.sf.oval.exception.ConstraintsViolatedException; 037 import net.sf.oval.exception.InvalidConfigurationException; 038 import net.sf.oval.exception.OValException; 039 import net.sf.oval.exception.ValidationFailedException; 040 import net.sf.oval.expression.ExpressionLanguage; 041 import net.sf.oval.internal.ClassChecks; 042 import net.sf.oval.internal.ContextCache; 043 import net.sf.oval.internal.Log; 044 import net.sf.oval.internal.ParameterChecks; 045 import net.sf.oval.internal.util.ArrayUtils; 046 import net.sf.oval.internal.util.Assert; 047 import net.sf.oval.internal.util.IdentitySet; 048 import net.sf.oval.internal.util.Invocable; 049 import net.sf.oval.internal.util.LinkedSet; 050 import net.sf.oval.internal.util.ReflectionUtils; 051 import net.sf.oval.internal.util.ThreadLocalList; 052 import net.sf.oval.internal.util.ThreadLocalWeakHashMap; 053 054 /** 055 * Extended version of the validator to realize programming by contract. 056 * 057 * @author Sebastian Thomschke 058 */ 059 public class Guard extends Validator 060 { 061 062 /** 063 * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions 064 */ 065 protected static final class GuardMethodPreResult 066 { 067 protected final boolean checkInvariants; 068 protected final Method method; 069 protected final Object[] args; 070 protected final ClassChecks cc; 071 protected final List<ConstraintViolation> violations; 072 protected final Map<PostCheck, Object> postCheckOldValues; 073 protected final Object guardedObject; 074 075 public GuardMethodPreResult(final Object guardedObject, final Method method, final Object[] args, 076 final ClassChecks cc, final boolean checkInvariants, final Map<PostCheck, Object> postCheckOldValues, 077 final List<ConstraintViolation> violations) 078 { 079 this.guardedObject = guardedObject; 080 this.method = method; 081 this.args = args; 082 this.cc = cc; 083 this.checkInvariants = checkInvariants; 084 this.postCheckOldValues = postCheckOldValues; 085 this.violations = violations; 086 } 087 } 088 089 /** 090 * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions 091 */ 092 protected static final GuardMethodPreResult DO_NOT_PROCEED = new GuardMethodPreResult(null, null, null, null, 093 false, null, null); 094 095 private static final Log LOG = Log.getLog(Guard.class); 096 097 /** 098 * string based on validated object hashcode + method hashcode for currently validated method return values 099 */ 100 private static final ThreadLocalList<String> currentlyCheckingMethodReturnValues = new ThreadLocalList<String>(); 101 102 /** 103 * string based on validated object hashcode + method hashcode for currently validated method pre-conditions 104 */ 105 private static final ThreadLocalList<String> currentlyCheckingPreConditions = new ThreadLocalList<String>(); 106 107 /** 108 * string based on validated object hashcode + method hashcode for currently validated method post-conditions 109 */ 110 private static final ThreadLocalList<String> currentlyCheckingPostConditions = new ThreadLocalList<String>(); 111 112 private boolean isActivated = true; 113 private boolean isInvariantsEnabled = true; 114 private boolean isPreConditionsEnabled = true; 115 private boolean isPostConditionsEnabled = true; 116 117 /** 118 * Flag that indicates if any listeners were registered at any time. Used for improved performance. 119 */ 120 private boolean isListenersFeatureUsed = false; 121 /** 122 * Flag that indicates if exception suppressing was used at any time. Used for improved performance. 123 */ 124 private boolean isProbeModeFeatureUsed = false; 125 126 private final Set<ConstraintsViolatedListener> listeners = new IdentitySet<ConstraintsViolatedListener>(4); 127 128 private final Map<Class< ? >, Set<ConstraintsViolatedListener>> listenersByClass = new WeakHashMap<Class< ? >, Set<ConstraintsViolatedListener>>( 129 4); 130 131 private final Map<Object, Set<ConstraintsViolatedListener>> listenersByObject = new WeakHashMap<Object, Set<ConstraintsViolatedListener>>( 132 4); 133 134 /** 135 * Objects for OVal suppresses occurring ConstraintViolationExceptions for pre condition violations on setter methods 136 * for the current thread. 137 */ 138 private final ThreadLocalWeakHashMap<Object, ProbeModeListener> objectsInProbeMode = new ThreadLocalWeakHashMap<Object, ProbeModeListener>(); 139 140 /** 141 * Constructs a new guard object and uses a new instance of AnnotationsConfigurer 142 */ 143 public Guard() 144 { 145 super(); 146 } 147 148 public Guard(final Collection<Configurer> configurers) 149 { 150 super(configurers); 151 } 152 153 public Guard(final Configurer... configurers) 154 { 155 super(configurers); 156 } 157 158 private List<CheckExclusion> _getActiveExclusions(final Set<CheckExclusion> exclusions) 159 { 160 final List<CheckExclusion> activeExclusions = new LinkedList<CheckExclusion>(exclusions); 161 for (final Iterator<CheckExclusion> it = activeExclusions.iterator(); it.hasNext();) 162 { 163 final CheckExclusion exclusion = it.next(); 164 if (!isAnyProfileEnabled(exclusion.getProfiles(), null)) it.remove(); 165 } 166 return activeExclusions.size() == 0 ? null : activeExclusions; 167 } 168 169 private void _validateParameterChecks(final ParameterChecks checks, final Object validatedObject, 170 final Object valueToValidate, final OValContext context, final List<ConstraintViolation> violations) 171 { 172 // determine the active exclusions based on the active profiles 173 final List<CheckExclusion> activeExclusions = checks.hasExclusions() 174 ? _getActiveExclusions(checks.checkExclusions) : null; 175 176 // check the constraints 177 for (final Check check : checks.checks) 178 { 179 boolean skip = false; 180 181 if (activeExclusions != null) 182 for (final CheckExclusion exclusion : activeExclusions) 183 if (exclusion.isActive(validatedObject, valueToValidate, this) 184 && exclusion.isCheckExcluded(check, validatedObject, valueToValidate, context, this)) 185 { 186 // skip if this check should be excluded 187 skip = true; 188 continue; 189 } 190 if (!skip) checkConstraint(violations, check, validatedObject, valueToValidate, context, null, false); 191 } 192 } 193 194 /** 195 * Registers constraint checks for the given constructor parameter 196 * 197 * @param ctor 198 * @param paramIndex 199 * @param exclusions 200 * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty 201 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 202 */ 203 public void addCheckExclusions(final Constructor< ? > ctor, final int paramIndex, 204 final CheckExclusion... exclusions) throws IllegalArgumentException, InvalidConfigurationException 205 { 206 Assert.argumentNotNull("ctor", ctor); 207 Assert.argumentNotEmpty("exclusions", exclusions); 208 209 getClassChecks(ctor.getDeclaringClass()).addConstructorParameterCheckExclusions(ctor, paramIndex, exclusions); 210 } 211 212 /** 213 * Registers constraint checks for the given method parameter 214 * 215 * @param method 216 * @param paramIndex 217 * @param exclusions 218 * @throws IllegalArgumentException if <code>method == null</code> or <code>exclusions == null</code> or exclusions is empty 219 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 220 */ 221 public void addCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions) 222 throws IllegalArgumentException, InvalidConfigurationException 223 { 224 Assert.argumentNotNull("method", method); 225 Assert.argumentNotEmpty("exclusions", exclusions); 226 227 getClassChecks(method.getDeclaringClass()).addMethodParameterCheckExclusions(method, paramIndex, exclusions); 228 } 229 230 /** 231 * Registers constraint checks for the given constructor parameter 232 * 233 * @param ctor 234 * @param paramIndex 235 * @param checks 236 * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is 237 * empty 238 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 239 */ 240 public void addChecks(final Constructor< ? > ctor, final int paramIndex, final Check... checks) 241 throws IllegalArgumentException, InvalidConfigurationException 242 { 243 Assert.argumentNotNull("ctor", ctor); 244 Assert.argumentNotEmpty("checks", checks); 245 246 getClassChecks(ctor.getDeclaringClass()).addConstructorParameterChecks(ctor, paramIndex, checks); 247 } 248 249 /** 250 * Registers constraint checks for the given method's return value 251 * 252 * @param method 253 * @param checks 254 * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code> or checks is empty 255 * @throws InvalidConfigurationException if method does not declare a return type (void), or the declaring class is 256 * not guarded 257 */ 258 @Override 259 public void addChecks(final Method method, final Check... checks) throws IllegalArgumentException, 260 InvalidConfigurationException 261 { 262 Assert.argumentNotNull("method", method); 263 Assert.argumentNotEmpty("checks", checks); 264 265 getClassChecks(method.getDeclaringClass()).addMethodReturnValueChecks(method, null, checks); 266 } 267 268 /** 269 * Registers constraint checks for the given method parameter 270 * 271 * @param method 272 * @param paramIndex 273 * @param checks 274 * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty 275 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 276 */ 277 public void addChecks(final Method method, final int paramIndex, final Check... checks) 278 throws IllegalArgumentException, InvalidConfigurationException 279 { 280 Assert.argumentNotNull("method", method); 281 Assert.argumentNotEmpty("checks", checks); 282 283 getClassChecks(method.getDeclaringClass()).addMethodParameterChecks(method, paramIndex, checks); 284 } 285 286 /** 287 * Registers post condition checks to a method's return value 288 * 289 * @param method 290 * @param checks 291 * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty 292 * @throws InvalidConfigurationException if the declaring class is not guarded 293 */ 294 public void addChecks(final Method method, final PostCheck... checks) throws IllegalArgumentException, 295 InvalidConfigurationException 296 { 297 Assert.argumentNotNull("method", method); 298 Assert.argumentNotEmpty("checks", checks); 299 300 getClassChecks(method.getDeclaringClass()).addMethodPostChecks(method, checks); 301 } 302 303 /** 304 * Registers pre condition checks to a method's return value 305 * 306 * @param method 307 * @param checks 308 * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty 309 * @throws InvalidConfigurationException if the declaring class is not guarded 310 */ 311 public void addChecks(final Method method, final PreCheck... checks) throws IllegalArgumentException, 312 InvalidConfigurationException 313 { 314 Assert.argumentNotNull("method", method); 315 Assert.argumentNotEmpty("checks", checks); 316 317 getClassChecks(method.getDeclaringClass()).addMethodPreChecks(method, checks); 318 } 319 320 /** 321 * Registers the given listener for <b>all</b> thrown ConstraintViolationExceptions 322 * 323 * @param listener the listener to register 324 * @return <code>true</code> if the listener was not yet registered 325 * @throws IllegalArgumentException if <code>listener == null</code> 326 */ 327 public boolean addListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException 328 { 329 Assert.argumentNotNull("listener", listener); 330 331 isListenersFeatureUsed = true; 332 return listeners.add(listener); 333 } 334 335 /** 336 * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given class 337 * 338 * @param listener the listener to register 339 * @param guardedClass guarded class or interface 340 * @return <code>true</code> if the listener was not yet registered 341 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code> 342 */ 343 public boolean addListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass) 344 throws IllegalArgumentException 345 { 346 Assert.argumentNotNull("listener", listener); 347 Assert.argumentNotNull("guardedClass", guardedClass); 348 349 isListenersFeatureUsed = true; 350 351 synchronized (listenersByClass) 352 { 353 Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedClass); 354 355 if (classListeners == null) 356 { 357 classListeners = getCollectionFactory().createSet(); 358 listenersByClass.put(guardedClass, classListeners); 359 } 360 return classListeners.add(listener); 361 } 362 } 363 364 /** 365 * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given object 366 * 367 * @param listener the listener to register 368 * @param guardedObject 369 * @return <code>true</code> if the listener was not yet registered 370 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code> 371 */ 372 public boolean addListener(final ConstraintsViolatedListener listener, final Object guardedObject) 373 { 374 Assert.argumentNotNull("listener", listener); 375 Assert.argumentNotNull("guardedObject", guardedObject); 376 377 isListenersFeatureUsed = true; 378 379 synchronized (listenersByObject) 380 { 381 Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject); 382 383 if (objectListeners == null) 384 { 385 objectListeners = getCollectionFactory().createSet(2); 386 listenersByObject.put(guardedObject, objectListeners); 387 } 388 return objectListeners.add(listener); 389 } 390 } 391 392 /** 393 * Evaluates the old expression 394 * 395 * @param validatedObject 396 * @param method 397 * @param args 398 * @return null if no violation, otherwise a list 399 * @throws ValidationFailedException 400 */ 401 protected Map<PostCheck, Object> calculateMethodPostOldValues(final Object validatedObject, final Method method, 402 final Object[] args) throws ValidationFailedException 403 { 404 try 405 { 406 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 407 final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method); 408 409 // shortcut: check if any post checks for this method exist 410 if (postChecks == null) return null; 411 412 final String[] parameterNames = parameterNameResolver.getParameterNames(method); 413 final boolean hasParameters = parameterNames.length > 0; 414 415 final Map<PostCheck, Object> oldValues = getCollectionFactory().createMap(postChecks.size()); 416 417 for (final PostCheck check : postChecks) 418 if (isAnyProfileEnabled(check.getProfiles(), null) && check.getOld() != null 419 && check.getOld().length() > 0) 420 { 421 final ExpressionLanguage eng = expressionLanguageRegistry 422 .getExpressionLanguage(check.getLanguage()); 423 final Map<String, Object> values = getCollectionFactory().createMap(); 424 values.put("_this", validatedObject); 425 if (hasParameters) 426 { 427 values.put("_args", args); 428 for (int i = 0; i < args.length; i++) 429 values.put(parameterNames[i], args[i]); 430 } 431 else 432 values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY); 433 434 oldValues.put(check, eng.evaluate(check.getOld(), values)); 435 } 436 437 return oldValues; 438 } 439 catch (final OValException ex) 440 { 441 throw new ValidationFailedException("Method post conditions validation failed. Method: " + method 442 + " Validated object: " + validatedObject, ex); 443 } 444 } 445 446 /** 447 * Disables the probe mode for the given object in the current thread. 448 * 449 * @param guardedObject the object to disable the probe mode for 450 * @throws IllegalArgumentException if <code>guardedObject == null</code> 451 * @throws IllegalStateException in case probe mode was not enabled for the given object 452 */ 453 public ProbeModeListener disableProbeMode(final Object guardedObject) throws IllegalArgumentException, 454 IllegalStateException 455 { 456 Assert.argumentNotNull("guardedObject", guardedObject); 457 458 return objectsInProbeMode.get().remove(guardedObject); 459 } 460 461 /** 462 * Enables the probe mode for the given object in the current thread. In probe mode calls to methods of an 463 * object are not actually executed. OVal only validates method pre-conditions and notifies 464 * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will 465 * return null. 466 * 467 * @param guardedObject the object to enable the probe mode for 468 * @throws IllegalArgumentException if <code>guardedObject == null</code> 469 * @throws IllegalStateException if the probe mode is already enabled 470 */ 471 public void enableProbeMode(final Object guardedObject) throws IllegalArgumentException, IllegalStateException 472 { 473 Assert.argumentNotNull("guardedObject", guardedObject); 474 475 if (guardedObject instanceof Class< ? >) 476 LOG.warn("Enabling probe mode for a class looks like a programming error. Class: {1}", guardedObject); 477 isProbeModeFeatureUsed = true; 478 479 if (objectsInProbeMode.get().get(guardedObject) != null) 480 throw new IllegalStateException("The object is already in probe mode."); 481 482 objectsInProbeMode.get().put(guardedObject, new ProbeModeListener(guardedObject)); 483 } 484 485 /** 486 * Returns the registers constraint pre condition checks for the given method parameter 487 * 488 * @param method 489 * @param paramIndex 490 * @throws IllegalArgumentException if <code>method == null</code> 491 */ 492 public Check[] getChecks(final Method method, final int paramIndex) throws InvalidConfigurationException 493 { 494 Assert.argumentNotNull("method", method); 495 496 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 497 498 final Map<Integer, ParameterChecks> checks = cc.checksForMethodParameters.get(method); 499 if (checks == null) return null; 500 501 final ParameterChecks paramChecks = checks.get(paramIndex); 502 return paramChecks == null ? null : paramChecks.checks.toArray(new Check[checks.size()]); 503 } 504 505 /** 506 * Returns the registered post condition checks for the given method 507 * 508 * @param method 509 * @throws IllegalArgumentException if <code>method == null</code> 510 */ 511 public PostCheck[] getChecksPost(final Method method) throws IllegalArgumentException 512 { 513 Assert.argumentNotNull("method", method); 514 515 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 516 517 final Set<PostCheck> checks = cc.checksForMethodsPostExcecution.get(method); 518 return checks == null ? null : checks.toArray(new PostCheck[checks.size()]); 519 } 520 521 /** 522 * Returns the registered pre condition checks for the given method. 523 * 524 * @param method 525 * @throws IllegalArgumentException if <code>method == null</code> 526 */ 527 public PreCheck[] getChecksPre(final Method method) throws IllegalArgumentException 528 { 529 Assert.argumentNotNull("method", method); 530 531 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 532 533 final Set<PreCheck> checks = cc.checksForMethodsPreExecution.get(method); 534 return checks == null ? null : checks.toArray(new PreCheck[checks.size()]); 535 } 536 537 /** 538 * @return the parameterNameResolver 539 */ 540 public ParameterNameResolver getParameterNameResolver() 541 { 542 return parameterNameResolver; 543 } 544 545 /** 546 * This method is provided for use by guard aspects. 547 * 548 * @throws ConstraintsViolatedException 549 * @throws ValidationFailedException 550 */ 551 protected void guardConstructorPost(final Object guardedObject, final Constructor< ? > ctor, final Object[] args) 552 throws ConstraintsViolatedException, ValidationFailedException 553 { 554 if (!isActivated) return; 555 556 final ClassChecks cc = getClassChecks(ctor.getDeclaringClass()); 557 558 // check invariants 559 if (isInvariantsEnabled && cc.isCheckInvariants || cc.methodsWithCheckInvariantsPost.contains(ctor)) 560 { 561 final List<ConstraintViolation> violations = getCollectionFactory().createList(); 562 currentViolations.get().add(violations); 563 try 564 { 565 validateInvariants(guardedObject, violations, null); 566 } 567 catch (final ValidationFailedException ex) 568 { 569 throw translateException(ex); 570 } 571 finally 572 { 573 currentViolations.get().removeLast(); 574 } 575 576 if (violations.size() > 0) 577 { 578 final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations); 579 if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException); 580 581 throw translateException(violationException); 582 } 583 } 584 } 585 586 /** 587 * This method is provided for use by guard aspects. 588 * 589 * @throws ConstraintsViolatedException if anything precondition is not satisfied 590 * @throws ValidationFailedException 591 */ 592 protected void guardConstructorPre(final Object guardedObject, final Constructor< ? > ctor, final Object[] args) 593 throws ConstraintsViolatedException, ValidationFailedException 594 { 595 if (!isActivated) return; 596 597 // constructor parameter validation 598 if (isPreConditionsEnabled && args.length > 0) 599 { 600 final List<ConstraintViolation> violations; 601 try 602 { 603 violations = validateConstructorParameters(guardedObject, ctor, args); 604 } 605 catch (final ValidationFailedException ex) 606 { 607 throw translateException(ex); 608 } 609 610 if (violations != null) 611 { 612 final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations); 613 if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException); 614 615 throw translateException(violationException); 616 } 617 } 618 } 619 620 /** 621 * This method is provided for use by guard aspects. 622 * 623 * @param guardedObject 624 * @param method 625 * @param args 626 * @param invocable 627 * @return The method return value or null if the guarded object is in probe mode. 628 * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe mode. 629 * @throws ValidationFailedException 630 */ 631 protected Object guardMethod(Object guardedObject, final Method method, final Object[] args, 632 final Invocable invocable) throws Throwable 633 { 634 if (!isActivated) return invocable.invoke(); 635 636 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 637 638 final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants 639 && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method); 640 641 // if static method use the declaring class as guardedObject 642 if (guardedObject == null && ReflectionUtils.isStatic(method)) guardedObject = method.getDeclaringClass(); 643 644 final List<ConstraintViolation> violations = getCollectionFactory().createList(); 645 currentViolations.get().add(violations); 646 647 try 648 { 649 // check invariants 650 if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method)) 651 validateInvariants(guardedObject, violations, null); 652 653 if (isPreConditionsEnabled) 654 { 655 // method parameter validation 656 if (violations.size() == 0 && args.length > 0) 657 validateMethodParameters(guardedObject, method, args, violations); 658 659 // @Pre validation 660 if (violations.size() == 0) validateMethodPre(guardedObject, method, args, violations); 661 } 662 } 663 catch (final ValidationFailedException ex) 664 { 665 throw translateException(ex); 666 } 667 finally 668 { 669 currentViolations.get().removeLast(); 670 } 671 672 final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null; 673 if (pml != null) pml.onMethodCall(method, args); 674 675 if (violations.size() > 0) 676 { 677 final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations); 678 if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException); 679 680 // don't throw an exception if the method is a setter and suppressing for precondition is enabled 681 if (pml != null) 682 { 683 pml.onConstraintsViolatedException(violationException); 684 return null; 685 } 686 687 throw translateException(violationException); 688 } 689 690 // abort method execution if in probe mode 691 if (pml != null) return null; 692 693 final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args); 694 695 final Object returnValue = invocable.invoke(); 696 697 currentViolations.get().add(violations); 698 699 try 700 { 701 // check invariants if executed method is not private 702 if (checkInvariants || cc.methodsWithCheckInvariantsPost.contains(method)) 703 validateInvariants(guardedObject, violations, null); 704 705 if (isPostConditionsEnabled) 706 { 707 708 // method return value 709 if (violations.size() == 0) validateMethodReturnValue(guardedObject, method, returnValue, violations); 710 711 // @Post 712 if (violations.size() == 0) 713 validateMethodPost(guardedObject, method, args, returnValue, postCheckOldValues, violations); 714 } 715 } 716 catch (final ValidationFailedException ex) 717 { 718 throw translateException(ex); 719 } 720 finally 721 { 722 currentViolations.get().removeLast(); 723 } 724 725 if (violations.size() > 0) 726 { 727 final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations); 728 if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException); 729 730 throw translateException(violationException); 731 } 732 733 return returnValue; 734 } 735 736 /** 737 * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions, 738 * then {@link #guardMethod(Object, Method, Object[], Invocable)} can be used instead 739 * 740 * This method is provided for use by guard aspects. 741 * 742 * @param returnValue 743 * @param preResult 744 * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe 745 * mode. 746 */ 747 protected void guardMethodPost(final Object returnValue, final GuardMethodPreResult preResult) 748 throws ConstraintsViolatedException, ValidationFailedException 749 { 750 if (!isActivated) return; 751 752 try 753 { 754 // check invariants if executed method is not private 755 if (preResult.checkInvariants || preResult.cc.methodsWithCheckInvariantsPost.contains(preResult.method)) 756 validateInvariants(preResult.guardedObject, preResult.violations, null); 757 758 if (isPostConditionsEnabled) 759 { 760 761 // method return value 762 if (preResult.violations.size() == 0) 763 validateMethodReturnValue(preResult.guardedObject, preResult.method, returnValue, 764 preResult.violations); 765 766 // @Post 767 if (preResult.violations.size() == 0) 768 validateMethodPost(preResult.guardedObject, preResult.method, preResult.args, returnValue, 769 preResult.postCheckOldValues, preResult.violations); 770 } 771 } 772 catch (final ValidationFailedException ex) 773 { 774 throw translateException(ex); 775 } 776 777 if (preResult.violations.size() > 0) 778 { 779 final ConstraintsViolatedException violationException = new ConstraintsViolatedException( 780 preResult.violations); 781 if (isListenersFeatureUsed) notifyListeners(preResult.guardedObject, violationException); 782 783 throw translateException(violationException); 784 } 785 } 786 787 /** 788 * <b>Note:</b> Only required until AspectJ allows throwing of checked exceptions, then {@link #guardMethod(Object, Method, Object[], Invocable)} can be used instead 789 * 790 * This method is provided for use by guard aspects. 791 * 792 * @param guardedObject 793 * @param method 794 * @param args 795 * @return Null if method guarding is deactivated or a result object that needs to be passed to {@link #guardMethodPost(Object, GuardMethodPreResult)} 796 * @throws ConstraintsViolatedException if an constraint violation occurs and the validated object is not in probe 797 * mode. 798 */ 799 protected GuardMethodPreResult guardMethodPre(Object guardedObject, final Method method, final Object[] args) 800 throws ConstraintsViolatedException, ValidationFailedException 801 { 802 if (!isActivated) return null; 803 804 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 805 806 final boolean checkInvariants = isInvariantsEnabled && cc.isCheckInvariants 807 && !ReflectionUtils.isPrivate(method) && !ReflectionUtils.isProtected(method); 808 809 // if static method use the declaring class as guardedObject 810 if (guardedObject == null && ReflectionUtils.isStatic(method)) guardedObject = method.getDeclaringClass(); 811 812 final List<ConstraintViolation> violations = getCollectionFactory().createList(); 813 currentViolations.get().add(violations); 814 815 try 816 { 817 // check invariants 818 if (checkInvariants || cc.methodsWithCheckInvariantsPre.contains(method)) 819 validateInvariants(guardedObject, violations, null); 820 821 if (isPreConditionsEnabled) 822 { 823 // method parameter validation 824 if (violations.size() == 0 && args.length > 0) 825 validateMethodParameters(guardedObject, method, args, violations); 826 827 // @Pre validation 828 if (violations.size() == 0) validateMethodPre(guardedObject, method, args, violations); 829 } 830 } 831 catch (final ValidationFailedException ex) 832 { 833 throw translateException(ex); 834 } 835 finally 836 { 837 currentViolations.get().removeLast(); 838 } 839 840 final ProbeModeListener pml = isProbeModeFeatureUsed ? objectsInProbeMode.get().get(guardedObject) : null; 841 if (pml != null) pml.onMethodCall(method, args); 842 843 if (violations.size() > 0) 844 { 845 final ConstraintsViolatedException violationException = new ConstraintsViolatedException(violations); 846 if (isListenersFeatureUsed) notifyListeners(guardedObject, violationException); 847 848 // don't throw an exception if the method is a setter and suppressing for precondition is enabled 849 if (pml != null) 850 { 851 pml.onConstraintsViolatedException(violationException); 852 return DO_NOT_PROCEED; 853 } 854 855 throw translateException(violationException); 856 } 857 858 // abort method execution if in probe mode 859 if (pml != null) return DO_NOT_PROCEED; 860 861 final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(guardedObject, method, args); 862 863 return new GuardMethodPreResult(guardedObject, method, args, cc, checkInvariants, postCheckOldValues, 864 violations); 865 } 866 867 /** 868 * @param listener 869 * @return <code>true</code> if the listener is registered 870 * @throws IllegalArgumentException if <code>listener == null</code> 871 */ 872 public boolean hasListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException 873 { 874 Assert.argumentNotNull("listener", listener); 875 876 return listeners.contains(listener); 877 } 878 879 /** 880 * @param listener 881 * @param guardedClass guarded class or interface 882 * @return <code>true</code> if the listener is registered 883 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code> 884 */ 885 public boolean hasListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass) 886 throws IllegalArgumentException 887 { 888 Assert.argumentNotNull("listener", listener); 889 Assert.argumentNotNull("guardedClass", guardedClass); 890 891 final Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedClass); 892 893 if (classListeners == null) return false; 894 895 return classListeners.contains(listener); 896 } 897 898 /** 899 * @param listener 900 * @param guardedObject 901 * @return <code>true</code> if the listener is registered 902 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code> 903 */ 904 public boolean hasListener(final ConstraintsViolatedListener listener, final Object guardedObject) 905 throws IllegalArgumentException 906 { 907 Assert.argumentNotNull("listener", listener); 908 Assert.argumentNotNull("guardedObject", guardedObject); 909 910 final Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject); 911 912 if (objectListeners == null) return false; 913 914 return objectListeners.contains(listener); 915 } 916 917 /** 918 * @return the isEnabled 919 */ 920 public boolean isActivated() 921 { 922 return isActivated; 923 } 924 925 /** 926 * Determines if the probe mode is enabled for the given object in the current thread. In probe mode calls to 927 * methods of an object are not actually executed. OVal only validates method pre-conditions and notifies 928 * ConstraintViolationListeners but does not throw ConstraintViolationExceptions. Methods with return values will 929 * return null. 930 * 931 * @param guardedObject 932 * @return true if exceptions are suppressed 933 */ 934 public boolean isInProbeMode(final Object guardedObject) 935 { 936 // guardedObject may be null if isInProbeMode is called when validating pre conditions of a static method 937 if (guardedObject == null) return false; 938 939 return objectsInProbeMode.get().containsKey(guardedObject); 940 } 941 942 /** 943 * Determines if invariants are checked prior and after every call to a non-private method or constructor. 944 * 945 * @return the isInvariantChecksActivated 946 */ 947 public boolean isInvariantsEnabled() 948 { 949 return isInvariantsEnabled; 950 } 951 952 /** 953 * Determines if invariants are checked prior and after every call to a non-private method or constructor. 954 * 955 * @param guardedClass the guarded class 956 * @return the isInvariantChecksActivated 957 */ 958 public boolean isInvariantsEnabled(final Class< ? > guardedClass) 959 { 960 return getClassChecks(guardedClass).isCheckInvariants; 961 } 962 963 /** 964 * @return the isPostChecksActivated 965 */ 966 public boolean isPostConditionsEnabled() 967 { 968 return isPostConditionsEnabled; 969 } 970 971 /** 972 * @return the isPreChecksActivated 973 */ 974 public boolean isPreConditionsEnabled() 975 { 976 return isPreConditionsEnabled; 977 } 978 979 /** 980 * notifies all registered validation listener about the occurred constraint violation exception 981 */ 982 protected void notifyListeners(final Object guardedObject, final ConstraintsViolatedException ex) 983 { 984 // happens for static methods 985 if (guardedObject == null) return; 986 987 final LinkedSet<ConstraintsViolatedListener> listenersToNotify = new LinkedSet<ConstraintsViolatedListener>(); 988 989 // get the object listeners 990 { 991 final Set<ConstraintsViolatedListener> objectListeners = listenersByObject.get(guardedObject); 992 if (objectListeners != null) listenersToNotify.addAll(objectListeners); 993 } 994 995 // get the class listeners 996 { 997 final Set<ConstraintsViolatedListener> classListeners = listenersByClass.get(guardedObject.getClass()); 998 if (classListeners != null) listenersToNotify.addAll(classListeners); 999 } 1000 1001 // get the interface listeners 1002 { 1003 for (final Class< ? > interfaze : guardedObject.getClass().getInterfaces()) 1004 { 1005 final Set<ConstraintsViolatedListener> interfaceListeners = listenersByClass.get(interfaze); 1006 if (interfaceListeners != null) listenersToNotify.addAll(interfaceListeners); 1007 } 1008 } 1009 1010 // get the global listeners 1011 listenersToNotify.addAll(listeners); 1012 1013 // notify the listeners 1014 for (final ConstraintsViolatedListener listener : listenersToNotify) 1015 try 1016 { 1017 listener.onConstraintsViolatedException(ex); 1018 } 1019 catch (final RuntimeException rex) 1020 { 1021 LOG.warn("Notifying listener '{1}' failed.", listener, rex); 1022 } 1023 1024 } 1025 1026 /** 1027 * Removes constraint check exclusions from the given constructor parameter 1028 * 1029 * @param ctor 1030 * @param paramIndex 1031 * @param exclusions 1032 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 1033 */ 1034 public void removeCheckExclusions(final Constructor< ? > ctor, final int paramIndex, 1035 final CheckExclusion... exclusions) throws InvalidConfigurationException 1036 { 1037 Assert.argumentNotNull("ctor", ctor); 1038 Assert.argumentNotEmpty("exclusions", exclusions); 1039 1040 getClassChecks(ctor.getDeclaringClass()) 1041 .removeConstructorParameterCheckExclusions(ctor, paramIndex, exclusions); 1042 } 1043 1044 /** 1045 * Removes constraint check exclusions from the given method parameter 1046 * 1047 * @param method 1048 * @param paramIndex 1049 * @param exclusions 1050 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 1051 */ 1052 public void removeCheckExclusions(final Method method, final int paramIndex, final CheckExclusion... exclusions) 1053 throws InvalidConfigurationException 1054 { 1055 Assert.argumentNotNull("method", method); 1056 Assert.argumentNotEmpty("exclusions", exclusions); 1057 1058 getClassChecks(method.getDeclaringClass()).removeMethodParameterCheckExclusions(method, paramIndex, exclusions); 1059 } 1060 1061 /** 1062 * Removes constraint checks from the given constructor parameter 1063 * 1064 * @param ctor 1065 * @param paramIndex 1066 * @param checks 1067 * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range 1068 */ 1069 public void removeChecks(final Constructor< ? > ctor, final int paramIndex, final Check... checks) 1070 throws InvalidConfigurationException 1071 { 1072 Assert.argumentNotNull("ctor", ctor); 1073 Assert.argumentNotEmpty("checks", checks); 1074 1075 getClassChecks(ctor.getDeclaringClass()).removeConstructorParameterChecks(ctor, paramIndex, checks); 1076 } 1077 1078 /** 1079 * Removes constraint checks for the given method parameter 1080 * 1081 * @param method 1082 * @param paramIndex 1083 * @param checks 1084 * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is 1085 * empty 1086 * @throws InvalidConfigurationException if the parameterIndex is out of range 1087 */ 1088 public void removeChecks(final Method method, final int paramIndex, final Check... checks) 1089 throws InvalidConfigurationException 1090 { 1091 Assert.argumentNotNull("method", method); 1092 Assert.argumentNotEmpty("checks", checks); 1093 1094 getClassChecks(method.getDeclaringClass()).removeMethodParameterChecks(method, paramIndex, checks); 1095 } 1096 1097 /** 1098 * Registers post condition checks to a method's return value 1099 * 1100 * @param method 1101 * @param checks 1102 * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty 1103 * @throws InvalidConfigurationException if the declaring class is not guarded 1104 */ 1105 public void removeChecks(final Method method, final PostCheck... checks) throws InvalidConfigurationException 1106 { 1107 Assert.argumentNotNull("method", method); 1108 Assert.argumentNotEmpty("checks", checks); 1109 1110 getClassChecks(method.getDeclaringClass()).removeMethodPostChecks(method, checks); 1111 } 1112 1113 /** 1114 * Registers pre condition checks to a method's return value 1115 * 1116 * @param method 1117 * @param checks 1118 * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty 1119 * @throws InvalidConfigurationException if the declaring class is not guarded 1120 */ 1121 public void removeChecks(final Method method, final PreCheck... checks) throws InvalidConfigurationException 1122 { 1123 Assert.argumentNotNull("method", method); 1124 Assert.argumentNotEmpty("checks", checks); 1125 1126 getClassChecks(method.getDeclaringClass()).removeMethodPreChecks(method, checks); 1127 } 1128 1129 /** 1130 * Removes the given listener 1131 * 1132 * @param listener 1133 * @return <code>true</code> if the listener was registered 1134 * @throws IllegalArgumentException if <code>listener == null</code> 1135 */ 1136 public boolean removeListener(final ConstraintsViolatedListener listener) throws IllegalArgumentException 1137 { 1138 Assert.argumentNotNull("listener", listener); 1139 1140 return listeners.remove(listener); 1141 } 1142 1143 /** 1144 * Removes the given listener 1145 * 1146 * @param listener 1147 * @param guardedClass guarded class or interface 1148 * @return <code>true</code> if the listener was registered 1149 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code> 1150 */ 1151 public boolean removeListener(final ConstraintsViolatedListener listener, final Class< ? > guardedClass) 1152 throws IllegalArgumentException 1153 { 1154 Assert.argumentNotNull("listener", listener); 1155 Assert.argumentNotNull("guardedClass", guardedClass); 1156 1157 final Set<ConstraintsViolatedListener> currentListeners = listenersByClass.get(guardedClass); 1158 1159 return currentListeners == null ? false : currentListeners.remove(listener); 1160 } 1161 1162 /** 1163 * Removes the given listener 1164 * 1165 * @param listener 1166 * @param guardedObject 1167 * @return <code>true</code> if the listener was registered 1168 * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code> 1169 */ 1170 public boolean removeListener(final ConstraintsViolatedListener listener, final Object guardedObject) 1171 throws IllegalArgumentException 1172 { 1173 Assert.argumentNotNull("listener", listener); 1174 Assert.argumentNotNull("guardedObject", guardedObject); 1175 1176 final Set<ConstraintsViolatedListener> currentListeners = listenersByObject.get(guardedObject); 1177 1178 return currentListeners == null ? false : currentListeners.remove(listener); 1179 } 1180 1181 /** 1182 * If set to false OVal's programming by contract features are disabled and constraints are not checked 1183 * automatically during runtime. 1184 * 1185 * @param isActivated the isActivated to set 1186 */ 1187 public void setActivated(final boolean isActivated) 1188 { 1189 this.isActivated = isActivated; 1190 } 1191 1192 /** 1193 * Specifies if invariants are checked prior and after calls to non-private methods and constructors. 1194 * 1195 * @param isEnabled the isInvariantsEnabled to set 1196 */ 1197 public void setInvariantsEnabled(final boolean isEnabled) 1198 { 1199 isInvariantsEnabled = isEnabled; 1200 } 1201 1202 /** 1203 * Specifies if invariants are checked prior and after calls to non-private methods and constructors. 1204 * 1205 * @param guardedClass the guarded class to turn on/off the invariant checking 1206 * @param isEnabled the isEnabled to set 1207 */ 1208 public void setInvariantsEnabled(final Class< ? > guardedClass, final boolean isEnabled) 1209 { 1210 getClassChecks(guardedClass).isCheckInvariants = isEnabled; 1211 } 1212 1213 /** 1214 * @param parameterNameResolver the parameterNameResolver to set, cannot be null 1215 * @throws IllegalArgumentException if <code>parameterNameResolver == null</code> 1216 */ 1217 public void setParameterNameResolver(final ParameterNameResolver parameterNameResolver) 1218 throws IllegalArgumentException 1219 { 1220 Assert.argumentNotNull("parameterNameResolver", parameterNameResolver); 1221 1222 this.parameterNameResolver.setDelegate(parameterNameResolver); 1223 } 1224 1225 /** 1226 * @param isEnabled the isEnabled to set 1227 */ 1228 public void setPostConditionsEnabled(final boolean isEnabled) 1229 { 1230 isPostConditionsEnabled = isEnabled; 1231 } 1232 1233 /** 1234 * @param isEnabled the isEnabled to set 1235 */ 1236 public void setPreConditionsEnabled(final boolean isEnabled) 1237 { 1238 isPreConditionsEnabled = isEnabled; 1239 } 1240 1241 /** 1242 * Validates the give arguments against the defined constructor parameter constraints.<br> 1243 * 1244 * @return null if no violation, otherwise a list 1245 * @throws ValidationFailedException 1246 */ 1247 protected List<ConstraintViolation> validateConstructorParameters(final Object validatedObject, 1248 final Constructor< ? > constructor, final Object[] argsToValidate) throws ValidationFailedException 1249 { 1250 // create required objects for this validation cycle 1251 final List<ConstraintViolation> violations = getCollectionFactory().createList(); 1252 currentViolations.get().add(violations); 1253 currentlyValidatedObjects.get().add(new IdentitySet<Object>(4)); 1254 1255 try 1256 { 1257 final ClassChecks cc = getClassChecks(constructor.getDeclaringClass()); 1258 final Map<Integer, ParameterChecks> parameterChecks = cc.checksForConstructorParameters.get(constructor); 1259 1260 // if no parameter checks exist just return null 1261 if (parameterChecks == null) return null; 1262 1263 final String[] parameterNames = parameterNameResolver.getParameterNames(constructor); 1264 1265 for (int i = 0; i < argsToValidate.length; i++) 1266 { 1267 final ParameterChecks checks = parameterChecks.get(i); 1268 1269 if (checks != null && checks.hasChecks()) 1270 { 1271 final Object valueToValidate = argsToValidate[i]; 1272 final ConstructorParameterContext context = new ConstructorParameterContext(constructor, i, 1273 parameterNames[i]); 1274 1275 _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations); 1276 } 1277 } 1278 return violations.size() == 0 ? null : violations; 1279 } 1280 catch (final OValException ex) 1281 { 1282 throw new ValidationFailedException("Validation of constructor parameters failed. Constructor: " 1283 + constructor + " Validated object: " + validatedObject.getClass().getName() + "@" 1284 + Integer.toHexString(validatedObject.hashCode()), ex); 1285 } 1286 finally 1287 { 1288 // remove the validation cycle related objects 1289 currentViolations.get().removeLast(); 1290 currentlyValidatedObjects.get().removeLast(); 1291 } 1292 } 1293 1294 /** 1295 * {@inheritDoc} 1296 */ 1297 @Override 1298 protected void validateInvariants(final Object guardedObject, final List<ConstraintViolation> violations, 1299 final String[] profiles) throws IllegalArgumentException, ValidationFailedException 1300 { 1301 // create a new set for this validation cycle 1302 currentlyValidatedObjects.get().add(new IdentitySet<Object>(4)); 1303 try 1304 { 1305 super.validateInvariants(guardedObject, violations, profiles); 1306 } 1307 finally 1308 { 1309 // remove the set 1310 currentlyValidatedObjects.get().removeLast(); 1311 } 1312 } 1313 1314 /** 1315 * Validates the pre conditions for a method call.<br> 1316 * 1317 * @throws ValidationFailedException 1318 */ 1319 protected void validateMethodParameters(final Object validatedObject, final Method method, final Object[] args, 1320 final List<ConstraintViolation> violations) throws ValidationFailedException 1321 { 1322 // create a new set for this validation cycle 1323 currentlyValidatedObjects.get().add(new IdentitySet<Object>(4)); 1324 try 1325 { 1326 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 1327 final Map<Integer, ParameterChecks> parameterChecks = cc.checksForMethodParameters.get(method); 1328 1329 if (parameterChecks == null) return; 1330 1331 final String[] parameterNames = parameterNameResolver.getParameterNames(method); 1332 1333 /* 1334 * parameter constraints validation 1335 */ 1336 if (parameterNames.length > 0) for (int i = 0; i < args.length; i++) 1337 { 1338 final ParameterChecks checks = parameterChecks.get(i); 1339 1340 if (checks != null && checks.checks.size() > 0) 1341 { 1342 final Object valueToValidate = args[i]; 1343 final MethodParameterContext context = new MethodParameterContext(method, i, parameterNames[i]); 1344 1345 _validateParameterChecks(checks, validatedObject, valueToValidate, context, violations); 1346 } 1347 } 1348 } 1349 catch (final OValException ex) 1350 { 1351 throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method 1352 + " Validated object: " + validatedObject, ex); 1353 } 1354 finally 1355 { 1356 // remove the set 1357 currentlyValidatedObjects.get().removeLast(); 1358 } 1359 } 1360 1361 /** 1362 * Validates the post conditions for a method call.<br> 1363 * 1364 * @throws ValidationFailedException 1365 */ 1366 protected void validateMethodPost(final Object validatedObject, final Method method, final Object[] args, 1367 final Object returnValue, final Map<PostCheck, Object> oldValues, final List<ConstraintViolation> violations) 1368 throws ValidationFailedException 1369 { 1370 final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method); 1371 1372 /* 1373 * avoid circular references 1374 */ 1375 if (currentlyCheckingPostConditions.get().contains(key)) return; 1376 1377 currentlyCheckingPostConditions.get().add(key); 1378 try 1379 { 1380 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 1381 final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution.get(method); 1382 1383 if (postChecks == null) return; 1384 1385 final String[] parameterNames = parameterNameResolver.getParameterNames(method); 1386 final boolean hasParameters = parameterNames.length > 0; 1387 1388 final MethodExitContext context = ContextCache.getMethodExitContext(method); 1389 1390 for (final PostCheck check : postChecks) 1391 { 1392 if (!isAnyProfileEnabled(check.getProfiles(), null)) continue; 1393 1394 final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLanguage()); 1395 final Map<String, Object> values = getCollectionFactory().createMap(); 1396 values.put("_this", validatedObject); 1397 values.put("_returns", returnValue); 1398 values.put("_old", oldValues.get(check)); 1399 if (hasParameters) 1400 { 1401 values.put("_args", args); 1402 for (int i = 0; i < args.length; i++) 1403 values.put(parameterNames[i], args[i]); 1404 } 1405 else 1406 values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY); 1407 1408 if (!eng.evaluateAsBoolean(check.getExpression(), values)) 1409 { 1410 final Map<String, String> messageVariables = getCollectionFactory().createMap(2); 1411 messageVariables.put("expression", check.getExpression()); 1412 final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables); 1413 1414 violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context)); 1415 } 1416 } 1417 } 1418 catch (final OValException ex) 1419 { 1420 throw new ValidationFailedException("Method post conditions validation failed. Method: " + method 1421 + " Validated object: " + validatedObject, ex); 1422 } 1423 finally 1424 { 1425 currentlyCheckingPostConditions.get().remove(key); 1426 } 1427 } 1428 1429 /** 1430 * Validates the @Pre conditions for a method call.<br> 1431 * 1432 * @throws ValidationFailedException 1433 */ 1434 protected void validateMethodPre(final Object validatedObject, final Method method, final Object[] args, 1435 final List<ConstraintViolation> violations) throws ValidationFailedException 1436 { 1437 final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method); 1438 1439 /* 1440 * avoid circular references 1441 */ 1442 if (currentlyCheckingPreConditions.get().contains(key)) return; 1443 1444 currentlyCheckingPreConditions.get().add(key); 1445 try 1446 { 1447 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 1448 final Set<PreCheck> preChecks = cc.checksForMethodsPreExecution.get(method); 1449 1450 if (preChecks == null) return; 1451 1452 final String[] parameterNames = parameterNameResolver.getParameterNames(method); 1453 final boolean hasParameters = parameterNames.length > 0; 1454 1455 final MethodEntryContext context = ContextCache.getMethodEntryContext(method); 1456 1457 for (final PreCheck check : preChecks) 1458 { 1459 if (!isAnyProfileEnabled(check.getProfiles(), null)) continue; 1460 1461 final ExpressionLanguage eng = expressionLanguageRegistry.getExpressionLanguage(check.getLanguage()); 1462 final Map<String, Object> values = getCollectionFactory().createMap(); 1463 values.put("_this", validatedObject); 1464 if (hasParameters) 1465 { 1466 values.put("_args", args); 1467 for (int i = 0; i < args.length; i++) 1468 values.put(parameterNames[i], args[i]); 1469 } 1470 else 1471 values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY); 1472 1473 if (!eng.evaluateAsBoolean(check.getExpression(), values)) 1474 { 1475 final Map<String, String> messageVariables = getCollectionFactory().createMap(2); 1476 messageVariables.put("expression", check.getExpression()); 1477 final String errorMessage = renderMessage(context, null, check.getMessage(), messageVariables); 1478 1479 violations.add(new ConstraintViolation(check, errorMessage, validatedObject, null, context)); 1480 } 1481 } 1482 } 1483 catch (final OValException ex) 1484 { 1485 throw new ValidationFailedException("Method pre conditions validation failed. Method: " + method 1486 + " Validated object: " + validatedObject, ex); 1487 } 1488 finally 1489 { 1490 currentlyCheckingPreConditions.get().remove(key); 1491 } 1492 } 1493 1494 /** 1495 * Validates the return value checks for a method call.<br> 1496 * 1497 * @throws ValidationFailedException 1498 */ 1499 protected void validateMethodReturnValue(final Object validatedObject, final Method method, 1500 final Object returnValue, final List<ConstraintViolation> violations) throws ValidationFailedException 1501 { 1502 final String key = System.identityHashCode(validatedObject) + " " + System.identityHashCode(method); 1503 1504 /* 1505 * avoid circular references, e.g. 1506 * 1507 * private String name; 1508 * 1509 * @Assert("_this.name != null", lang="groovy") 1510 * public String getName { return name; } 1511 * 1512 * => Groovy will invoke the getter to return the value, invocations of the getter will trigger the validation of the method return values again, including the @Assert constraint 1513 */ 1514 if (currentlyCheckingMethodReturnValues.get().contains(key)) return; 1515 1516 currentlyCheckingMethodReturnValues.get().add(key); 1517 // create a new set for this validation cycle 1518 currentlyValidatedObjects.get().add(new IdentitySet<Object>(4)); 1519 try 1520 { 1521 final ClassChecks cc = getClassChecks(method.getDeclaringClass()); 1522 final Collection<Check> returnValueChecks = cc.checksForMethodReturnValues.get(method); 1523 1524 if (returnValueChecks == null || returnValueChecks.size() == 0) return; 1525 1526 final MethodReturnValueContext context = ContextCache.getMethodReturnValueContext(method); 1527 1528 for (final Check check : returnValueChecks) 1529 checkConstraint(violations, check, validatedObject, returnValue, context, null, false); 1530 } 1531 catch (final OValException ex) 1532 { 1533 throw new ValidationFailedException("Method post conditions validation failed. Method: " + method 1534 + " Validated object: " + validatedObject, ex); 1535 } 1536 finally 1537 { 1538 currentlyCheckingMethodReturnValues.get().remove(key); 1539 1540 // remove the set 1541 currentlyValidatedObjects.get().removeLast(); 1542 } 1543 } 1544 }