0001: /*******************************************************************************
0002: * Portions created by Sebastian Thomschke are copyright (c) 2005-2007 Sebastian
0003: * Thomschke.
0004: *
0005: * All Rights Reserved. This program and the accompanying materials
0006: * are made available under the terms of the Eclipse Public License v1.0
0007: * which accompanies this distribution, and is available at
0008: * http://www.eclipse.org/legal/epl-v10.html
0009: *
0010: * Contributors:
0011: * Sebastian Thomschke - initial implementation.
0012: *******************************************************************************/package net.sf.oval.guard;
0013:
0014: import java.lang.reflect.Constructor;
0015: import java.lang.reflect.Method;
0016: import java.util.Collection;
0017: import java.util.List;
0018: import java.util.Map;
0019: import java.util.Set;
0020: import java.util.WeakHashMap;
0021:
0022: import net.sf.oval.Check;
0023: import net.sf.oval.ConstraintViolation;
0024: import net.sf.oval.Validator;
0025: import net.sf.oval.configuration.Configurer;
0026: import net.sf.oval.context.ConstructorParameterContext;
0027: import net.sf.oval.context.MethodEntryContext;
0028: import net.sf.oval.context.MethodExitContext;
0029: import net.sf.oval.context.MethodParameterContext;
0030: import net.sf.oval.context.MethodReturnValueContext;
0031: import net.sf.oval.exception.ConstraintsViolatedException;
0032: import net.sf.oval.exception.InvalidConfigurationException;
0033: import net.sf.oval.exception.OValException;
0034: import net.sf.oval.exception.ValidationFailedException;
0035: import net.sf.oval.expression.ExpressionLanguage;
0036: import net.sf.oval.internal.ClassChecks;
0037: import net.sf.oval.internal.CollectionFactoryHolder;
0038: import net.sf.oval.internal.Log;
0039: import net.sf.oval.internal.util.ArrayUtils;
0040: import net.sf.oval.internal.util.IdentitySet;
0041: import net.sf.oval.internal.util.Invocable;
0042: import net.sf.oval.internal.util.ListOrderedSet;
0043: import net.sf.oval.internal.util.ReflectionUtils;
0044: import net.sf.oval.internal.util.ThreadLocalIdentitySet;
0045: import net.sf.oval.internal.util.ThreadLocalWeakHashSet;
0046:
0047: /**
0048: * Extended version of the validator to realize programming by contract.
0049: *
0050: * @author Sebastian Thomschke
0051: */
0052: public class Guard extends Validator {
0053: private final static Log LOG = Log.getLog(Guard.class);
0054:
0055: private final ThreadLocalIdentitySet<Object> currentlyInvariantCheckingFor = new ThreadLocalIdentitySet<Object>();
0056:
0057: private boolean isActivated = true;
0058: private boolean isInvariantsEnabled = true;
0059: private boolean isPreConditionsEnabled = true;
0060: private boolean isPostConditionsEnabled = true;
0061:
0062: /**
0063: * Flag that indicates if any listeners were registered at any time.
0064: * Used for performance improvements.
0065: */
0066: private boolean isListenersFeatureUsed = false;
0067:
0068: /**
0069: * Flag that indicates if exception suppressing was used at any time.
0070: * Used for performance improvements.
0071: */
0072: private boolean isProbeModeFeatureUsed = false;
0073:
0074: private final Set<ConstraintsViolatedListener> listeners = new IdentitySet<ConstraintsViolatedListener>(
0075: 4);
0076: private final Map<Class, Set<ConstraintsViolatedListener>> listenersByClass = new WeakHashMap<Class, Set<ConstraintsViolatedListener>>(
0077: 4);
0078: private final Map<Object, Set<ConstraintsViolatedListener>> listenersByObject = new WeakHashMap<Object, Set<ConstraintsViolatedListener>>(
0079: 4);
0080:
0081: /**
0082: * Objects for OVal suppresses occuring ConstraintViolationExceptions
0083: * for pre condition violations on setter methods for the current thread.
0084: */
0085: private final ThreadLocalWeakHashSet<Object> objectsInProbeMode = new ThreadLocalWeakHashSet<Object>();
0086:
0087: /**
0088: * Constructs a new guard object and uses a new isntance of
0089: * AnnotationsConfigurer
0090: */
0091: public Guard() {
0092: super ();
0093: }
0094:
0095: public Guard(final Collection<Configurer> configurers) {
0096: super (configurers);
0097: }
0098:
0099: public Guard(final Configurer... configurers) {
0100: super (configurers);
0101: }
0102:
0103: /**
0104: * Registers constraint checks for the given constructor parameter
0105: *
0106: * @param constructor
0107: * @param parameterIndex
0108: * @param checks
0109: * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is empty
0110: * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
0111: */
0112: public void addChecks(final Constructor constructor,
0113: final int parameterIndex, final Check... checks)
0114: throws IllegalArgumentException,
0115: InvalidConfigurationException {
0116: if (constructor == null)
0117: throw new IllegalArgumentException(
0118: "constructor cannot be null");
0119: if (checks == null)
0120: throw new IllegalArgumentException("checks cannot be null");
0121: if (checks.length == 0)
0122: throw new IllegalArgumentException("checks cannot empty");
0123:
0124: final ClassChecks cc = getClassChecks(constructor
0125: .getDeclaringClass());
0126: cc.addConstructorParameterChecks(constructor, parameterIndex,
0127: checks);
0128: }
0129:
0130: /**
0131: * Registers constraint checks for the given method's return value
0132: *
0133: * @param method
0134: * @param checks
0135: * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code> or checks is empty
0136: * @throws InvalidConfigurationException if method does not declare a return type (void), or the declaring class is not guarded
0137: */
0138: @Override
0139: public void addChecks(final Method method, final Check... checks)
0140: throws IllegalArgumentException,
0141: InvalidConfigurationException {
0142: if (method == null)
0143: throw new IllegalArgumentException("method cannot be null");
0144: if (checks == null)
0145: throw new IllegalArgumentException("checks cannot be null");
0146: if (checks.length == 0)
0147: throw new IllegalArgumentException("checks cannot empty");
0148:
0149: final ClassChecks cc = getClassChecks(method
0150: .getDeclaringClass());
0151: cc.addMethodReturnValueChecks(method, null, checks);
0152: }
0153:
0154: /**
0155: * Registers constraint checks for the given method parameter
0156: *
0157: * @param method
0158: * @param parameterIndex
0159: * @param checks
0160: * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
0161: * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
0162: */
0163: public void addChecks(final Method method,
0164: final int parameterIndex, final Check... checks)
0165: throws IllegalArgumentException,
0166: InvalidConfigurationException {
0167: if (method == null)
0168: throw new IllegalArgumentException("method cannot be null");
0169: if (checks == null)
0170: throw new IllegalArgumentException("checks cannot be null");
0171: if (checks.length == 0)
0172: throw new IllegalArgumentException("checks cannot empty");
0173:
0174: final ClassChecks cc = getClassChecks(method
0175: .getDeclaringClass());
0176: cc.addMethodParameterChecks(method, parameterIndex, checks);
0177: }
0178:
0179: /**
0180: * Registers post condition checks to a method's return value
0181: * @param method
0182: * @param checks
0183: * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
0184: * @throws InvalidConfigurationException if the declaring class is not guarded
0185: */
0186: public void addChecks(final Method method,
0187: final PostCheck... checks) throws IllegalArgumentException,
0188: InvalidConfigurationException {
0189: if (method == null)
0190: throw new IllegalArgumentException("method cannot be null");
0191: if (checks == null)
0192: throw new IllegalArgumentException("checks cannot be null");
0193: if (checks.length == 0)
0194: throw new IllegalArgumentException("checks cannot empty");
0195:
0196: final ClassChecks cc = getClassChecks(method
0197: .getDeclaringClass());
0198: cc.addMethodPostChecks(method, checks);
0199: }
0200:
0201: /**
0202: * Registers pre condition checks to a method's return value
0203: * @param method
0204: * @param checks
0205: * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
0206: * @throws InvalidConfigurationException if the declaring class is not guarded
0207: */
0208: public void addChecks(final Method method, final PreCheck... checks)
0209: throws IllegalArgumentException,
0210: InvalidConfigurationException {
0211: if (method == null)
0212: throw new IllegalArgumentException("method cannot be null");
0213: if (checks == null)
0214: throw new IllegalArgumentException("checks cannot be null");
0215: if (checks.length == 0)
0216: throw new IllegalArgumentException("checks cannot empty");
0217:
0218: final ClassChecks cc = getClassChecks(method
0219: .getDeclaringClass());
0220: cc.addMethodPreChecks(method, checks);
0221: }
0222:
0223: /**
0224: * Registers the given listener for <b>all</b> thrown ConstraintViolationExceptions
0225: *
0226: * @param listener the listener to register
0227: * @return <code>true</code> if the listener was not yet registered
0228: * @throws IllegalArgumentException if <code>listener == null</code>
0229: */
0230: public boolean addListener(
0231: final ConstraintsViolatedListener listener)
0232: throws IllegalArgumentException {
0233: if (listener == null)
0234: throw new IllegalArgumentException(
0235: "listener cannot be null");
0236:
0237: isListenersFeatureUsed = true;
0238: return listeners.add(listener);
0239: }
0240:
0241: /**
0242: * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given class
0243: * @param listener the listener to register
0244: * @param guardedClass guarded class or interface
0245: * @return <code>true</code> if the listener was not yet registered
0246: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
0247: */
0248: public boolean addListener(
0249: final ConstraintsViolatedListener listener,
0250: final Class guardedClass) throws IllegalArgumentException {
0251: if (listener == null)
0252: throw new IllegalArgumentException(
0253: "listener cannot be null");
0254: if (guardedClass == null)
0255: throw new IllegalArgumentException(
0256: "guardedClass cannot be null");
0257:
0258: isListenersFeatureUsed = true;
0259:
0260: synchronized (listenersByClass) {
0261: Set<ConstraintsViolatedListener> classListeners = listenersByClass
0262: .get(guardedClass);
0263:
0264: if (classListeners == null) {
0265: classListeners = CollectionFactoryHolder.getFactory()
0266: .createSet();
0267: listenersByClass.put(guardedClass, classListeners);
0268: }
0269: return classListeners.add(listener);
0270: }
0271: }
0272:
0273: /**
0274: * Registers the given listener for all thrown ConstraintViolationExceptions on objects of the given object
0275: * @param listener the listener to register
0276: * @param guardedObject
0277: * @return <code>true</code> if the listener was not yet registered
0278: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
0279: */
0280: public boolean addListener(
0281: final ConstraintsViolatedListener listener,
0282: final Object guardedObject) {
0283: if (listener == null)
0284: throw new IllegalArgumentException(
0285: "listener cannot be null");
0286: if (guardedObject == null)
0287: throw new IllegalArgumentException(
0288: "guardedObject cannot be null");
0289:
0290: isListenersFeatureUsed = true;
0291:
0292: synchronized (listenersByObject) {
0293: Set<ConstraintsViolatedListener> objectListeners = listenersByObject
0294: .get(guardedObject);
0295:
0296: if (objectListeners == null) {
0297: objectListeners = CollectionFactoryHolder.getFactory()
0298: .createSet(2);
0299: listenersByObject.put(guardedObject, objectListeners);
0300: }
0301: return objectListeners.add(listener);
0302: }
0303: }
0304:
0305: /**
0306: * Evaluates the old expression
0307: *
0308: * @return null if no violation, otherwise a list
0309: * @throws ValidationFailedException
0310: */
0311: protected Map<PostCheck, Object> calculateMethodPostOldValues(
0312: final Object validatedObject, final Method method,
0313: final Object[] args) throws ValidationFailedException {
0314: try {
0315: final ClassChecks cc = getClassChecks(method
0316: .getDeclaringClass());
0317: final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution
0318: .get(method);
0319:
0320: // shortcut: check if any post checks for this method exist
0321: if (postChecks == null)
0322: return null;
0323:
0324: final String[] parameterNames = parameterNameResolver
0325: .getParameterNames(method);
0326: final boolean hasParameters = parameterNames.length > 0;
0327:
0328: final Map<PostCheck, Object> oldValues = CollectionFactoryHolder
0329: .getFactory().createMap(postChecks.size());
0330:
0331: for (final PostCheck check : postChecks) {
0332: if (isAnyProfileEnabled(check.getProfiles())
0333: && check.getOld() != null
0334: && check.getOld().length() > 0) {
0335: final ExpressionLanguage eng = expressionLanguages
0336: .get(check.getLanguage());
0337: final Map<String, Object> values = CollectionFactoryHolder
0338: .getFactory().createMap();
0339: values.put("_this", validatedObject);
0340: if (hasParameters) {
0341: values.put("_args", args);
0342: for (int i = 0; i < args.length; i++) {
0343: values.put(parameterNames[i], args[i]);
0344: }
0345: } else
0346: values.put("_args",
0347: ArrayUtils.EMPTY_OBJECT_ARRAY);
0348:
0349: oldValues.put(check, eng.evaluate(check.getOld(),
0350: values));
0351: }
0352: }
0353:
0354: return oldValues;
0355: } catch (final OValException ex) {
0356: throw new ValidationFailedException(
0357: "Method post conditions validation failed. Method: "
0358: + method + " Validated object: "
0359: + validatedObject, ex);
0360: }
0361: }
0362:
0363: /**
0364: * Returns the registers constraint pre condition checks for the given method parameter
0365: *
0366: * @param method
0367: * @param parameterIndex
0368: * @throws IllegalArgumentException if <code>method == null</code>
0369: */
0370: public Check[] getChecks(final Method method,
0371: final int parameterIndex)
0372: throws InvalidConfigurationException {
0373: if (method == null)
0374: throw new IllegalArgumentException("method cannot be null");
0375:
0376: final ClassChecks cc = getClassChecks(method
0377: .getDeclaringClass());
0378:
0379: final Map<Integer, Set<Check>> checks = cc.checksForMethodParameters
0380: .get(method);
0381: if (checks == null)
0382: return null;
0383:
0384: final Collection<Check> paramChecks = checks
0385: .get(parameterIndex);
0386: return paramChecks == null ? null : paramChecks
0387: .toArray(new Check[checks.size()]);
0388: }
0389:
0390: /**
0391: * Returns the registered post condition checks for the given method
0392: *
0393: * @param method
0394: * @throws IllegalArgumentException if <code>method == null</code>
0395: */
0396: public PostCheck[] getChecksPost(final Method method)
0397: throws IllegalArgumentException {
0398: if (method == null)
0399: throw new IllegalArgumentException("method cannot be null");
0400:
0401: final ClassChecks cc = getClassChecks(method
0402: .getDeclaringClass());
0403:
0404: final Set<PostCheck> checks = cc.checksForMethodsPostExcecution
0405: .get(method);
0406: return checks == null ? null : checks
0407: .toArray(new PostCheck[checks.size()]);
0408: }
0409:
0410: /**
0411: * Returns the registered pre condition checks for the given method.
0412: *
0413: * @param method
0414: * @throws IllegalArgumentException if <code>method == null</code>
0415: */
0416: public PreCheck[] getChecksPre(final Method method)
0417: throws IllegalArgumentException {
0418: if (method == null)
0419: throw new IllegalArgumentException("method cannot be null");
0420:
0421: final ClassChecks cc = getClassChecks(method
0422: .getDeclaringClass());
0423:
0424: final Set<PreCheck> checks = cc.checksForMethodsPreExecution
0425: .get(method);
0426: return checks == null ? null : checks
0427: .toArray(new PreCheck[checks.size()]);
0428: }
0429:
0430: /**
0431: * @return the parameterNameResolver
0432: */
0433: public ParameterNameResolver getParameterNameResolver() {
0434: return parameterNameResolver;
0435: }
0436:
0437: /**
0438: * This method is provided for use by guard aspects.
0439: *
0440: * @throws ConstraintsViolatedException
0441: * @throws ValidationFailedException
0442: */
0443: protected void guardConstructorPost(final Object guardedObject,
0444: final Constructor constructor, final Object[] args)
0445: throws ConstraintsViolatedException,
0446: ValidationFailedException {
0447: if (!isActivated)
0448: return;
0449:
0450: final ClassChecks cc = getClassChecks(constructor
0451: .getDeclaringClass());
0452:
0453: // check invariants
0454: if (isInvariantsEnabled
0455: && cc.isCheckInvariants
0456: || cc.methodsWithCheckInvariantsPost
0457: .contains(constructor)) {
0458: try {
0459: final List<ConstraintViolation> violations = CollectionFactoryHolder
0460: .getFactory().createList();
0461: validateInvariants(guardedObject, violations);
0462:
0463: if (violations.size() > 0) {
0464: final ConstraintsViolatedException violationException = new ConstraintsViolatedException(
0465: violations);
0466: if (isListenersFeatureUsed)
0467: notifyListeners(guardedObject,
0468: violationException);
0469:
0470: throw translateException(violationException);
0471: }
0472: } catch (final ValidationFailedException ex) {
0473: throw translateException(ex);
0474: }
0475: }
0476: }
0477:
0478: /**
0479: * This method is provided for use by guard aspects.
0480: *
0481: * @throws ConstraintsViolatedException if anything precondition is not satisfied
0482: * @throws ValidationFailedException
0483: */
0484: protected void guardConstructorPre(final Object guardedObject,
0485: final Constructor constructor, final Object[] args)
0486: throws ConstraintsViolatedException,
0487: ValidationFailedException {
0488: if (!isActivated)
0489: return;
0490:
0491: // constructor parameter validation
0492: if (isPreConditionsEnabled && args.length > 0) {
0493: try {
0494: final List<ConstraintViolation> violations = validateConstructorParameters(
0495: guardedObject, constructor, args);
0496:
0497: if (violations != null) {
0498: final ConstraintsViolatedException violationException = new ConstraintsViolatedException(
0499: violations);
0500: if (isListenersFeatureUsed)
0501: notifyListeners(guardedObject,
0502: violationException);
0503:
0504: throw translateException(violationException);
0505: }
0506: } catch (final ValidationFailedException ex) {
0507: throw translateException(ex);
0508: }
0509: }
0510: }
0511:
0512: /**
0513: * This method is provided for use by guard aspects.
0514: *
0515: * @param guardedObject
0516: * @param method
0517: * @param args
0518: * @param invocable
0519: * @return The method return value or null if the guarded object is in probe mode.
0520: * @throws ConstraintsViolatedException if an constriant violation occures and the validated object is not in probe mode.
0521: */
0522: protected Object guardMethod(Object guardedObject,
0523: final Method method, final Object[] args,
0524: final Invocable invocable)
0525: throws ConstraintsViolatedException,
0526: ValidationFailedException {
0527: if (!isActivated)
0528: return invocable.invoke();
0529:
0530: final ClassChecks cc = getClassChecks(method
0531: .getDeclaringClass());
0532:
0533: final boolean checkInvariants = isInvariantsEnabled
0534: && cc.isCheckInvariants
0535: && !ReflectionUtils.isPrivate(method)
0536: && !ReflectionUtils.isProtected(method);
0537:
0538: final List<ConstraintViolation> violations = CollectionFactoryHolder
0539: .getFactory().createList();
0540:
0541: // if static method use the declaring class as guardedObject
0542: if (guardedObject == null && ReflectionUtils.isStatic(method))
0543: guardedObject = method.getDeclaringClass();
0544:
0545: try {
0546: // check invariants
0547: if (checkInvariants
0548: || cc.methodsWithCheckInvariantsPre
0549: .contains(method))
0550: validateInvariants(guardedObject, violations);
0551:
0552: if (isPreConditionsEnabled) {
0553: // method parameter validation
0554: if (violations.size() == 0 && args.length > 0)
0555: validateMethodParameters(guardedObject, method,
0556: args, violations);
0557:
0558: // @Pre validation
0559: if (violations.size() == 0)
0560: validateMethodPre(guardedObject, method, args,
0561: violations);
0562: }
0563:
0564: if (violations.size() > 0) {
0565: final ConstraintsViolatedException violationException = new ConstraintsViolatedException(
0566: violations);
0567: if (isListenersFeatureUsed)
0568: notifyListeners(guardedObject, violationException);
0569:
0570: // don't throw an exception if the method is a setter and suppressing for precondition is enabled
0571: if (isProbeModeFeatureUsed
0572: && isInProbeMode(guardedObject))
0573: return null;
0574:
0575: throw translateException(violationException);
0576: }
0577:
0578: // abort method execution if in probe mode
0579: if (isProbeModeFeatureUsed && isInProbeMode(guardedObject))
0580: return null;
0581: } catch (final ValidationFailedException ex) {
0582: throw translateException(ex);
0583: }
0584:
0585: final Map<PostCheck, Object> postCheckOldValues = calculateMethodPostOldValues(
0586: guardedObject, method, args);
0587:
0588: final Object returnValue = invocable.invoke();
0589:
0590: try {
0591: // chek invariants if executed method is not private
0592: if (checkInvariants
0593: || cc.methodsWithCheckInvariantsPost
0594: .contains(method)) {
0595: validateInvariants(guardedObject, violations);
0596: }
0597:
0598: if (isPostConditionsEnabled) {
0599:
0600: // method return value
0601: if (violations.size() == 0)
0602: validateMethodReturnValue(guardedObject, method,
0603: returnValue, violations);
0604:
0605: // @Post
0606: if (violations.size() == 0)
0607: validateMethodPost(guardedObject, method, args,
0608: returnValue, postCheckOldValues, violations);
0609: }
0610:
0611: if (violations.size() > 0) {
0612: final ConstraintsViolatedException violationException = new ConstraintsViolatedException(
0613: violations);
0614: if (isListenersFeatureUsed)
0615: notifyListeners(guardedObject, violationException);
0616:
0617: throw translateException(violationException);
0618: }
0619: } catch (final ValidationFailedException ex) {
0620: throw translateException(ex);
0621: }
0622: return returnValue;
0623: }
0624:
0625: /**
0626: * @param listener
0627: * @return <code>true</code> if the listener is registered
0628: * @throws IllegalArgumentException if <code>listener == null</code>
0629: */
0630: public boolean hasListener(
0631: final ConstraintsViolatedListener listener)
0632: throws IllegalArgumentException {
0633: if (listener == null)
0634: throw new IllegalArgumentException(
0635: "listener cannot be null");
0636:
0637: return listeners.contains(listener);
0638: }
0639:
0640: /**
0641: * @param listener
0642: * @param guardedClass guarded class or interface
0643: * @return <code>true</code> if the listener is registered
0644: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
0645: */
0646: public boolean hasListener(
0647: final ConstraintsViolatedListener listener,
0648: final Class guardedClass) throws IllegalArgumentException {
0649: if (listener == null)
0650: throw new IllegalArgumentException(
0651: "listener cannot be null");
0652: if (guardedClass == null)
0653: throw new IllegalArgumentException(
0654: "guardedClass cannot be null");
0655:
0656: final Set<ConstraintsViolatedListener> classListeners = listenersByClass
0657: .get(guardedClass);
0658:
0659: if (classListeners == null)
0660: return false;
0661:
0662: return classListeners.contains(listener);
0663: }
0664:
0665: /**
0666: * @param listener
0667: * @param guardedObject
0668: * @return <code>true</code> if the listener is registered
0669: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
0670: */
0671: public boolean hasListener(
0672: final ConstraintsViolatedListener listener,
0673: final Object guardedObject) throws IllegalArgumentException {
0674: if (listener == null)
0675: throw new IllegalArgumentException(
0676: "listener cannot be null");
0677: if (guardedObject == null)
0678: throw new IllegalArgumentException(
0679: "guardedObject cannot be null");
0680:
0681: final Set<ConstraintsViolatedListener> objectListeners = listenersByObject
0682: .get(guardedObject);
0683:
0684: if (objectListeners == null)
0685: return false;
0686:
0687: return objectListeners.contains(listener);
0688: }
0689:
0690: /**
0691: * @return the isEnabled
0692: */
0693: public boolean isActivated() {
0694: return isActivated;
0695: }
0696:
0697: /**
0698: * Determines if the probe mode is enabled for the given object in the current thread.
0699: * In probe mode calls to methods of an object are not actually executed. OVal only
0700: * validates method pre-conditions and notifies ConstraintViolationListeners but
0701: * does not throw ConstraintViolationExceptions. Methods with return values will return null.
0702: *
0703: * @param guardedObject
0704: * @return true if exceptions are suppressed
0705: */
0706: public boolean isInProbeMode(final Object guardedObject) {
0707: // guardedObject may be null if isInProbeMode is called when validating pre conditions of a static method
0708: if (guardedObject == null)
0709: return false;
0710:
0711: return objectsInProbeMode.get().contains(guardedObject);
0712: }
0713:
0714: /**
0715: * Determins if invariants are checked prior and after every
0716: * call to a non-private method or constructor.
0717: *
0718: * @return the isInvariantChecksActivated
0719: */
0720: public boolean isInvariantsEnabled() {
0721: return isInvariantsEnabled;
0722: }
0723:
0724: /**
0725: * Determins if invariants are checked prior and after every
0726: * call to a non-private method or constructor.
0727: *
0728: * @param guardedClass the guarded class
0729: * @return the isInvariantChecksActivated
0730: */
0731: public boolean isInvariantsEnabled(final Class guardedClass) {
0732: final ClassChecks cc = getClassChecks(guardedClass);
0733: return cc.isCheckInvariants;
0734: }
0735:
0736: /**
0737: * @return the isPostChecksActivated
0738: */
0739: public boolean isPostConditionsEnabled() {
0740: return isPostConditionsEnabled;
0741: }
0742:
0743: /**
0744: * @return the isPreChecksActivated
0745: */
0746: public boolean isPreConditionsEnabled() {
0747: return isPreConditionsEnabled;
0748: }
0749:
0750: /**
0751: * notifies all registered validation listener about the occured constraint violation exception
0752: */
0753: protected void notifyListeners(final Object guardedObject,
0754: final ConstraintsViolatedException ex) {
0755: // happens for static methods
0756: if (guardedObject == null)
0757: return;
0758:
0759: final ListOrderedSet<ConstraintsViolatedListener> listenersToNotify = new ListOrderedSet<ConstraintsViolatedListener>();
0760:
0761: // get the object listeners
0762: {
0763: final Set<ConstraintsViolatedListener> objectListeners = listenersByObject
0764: .get(guardedObject);
0765: if (objectListeners != null) {
0766: listenersToNotify.addAll(objectListeners);
0767: }
0768: }
0769:
0770: // get the class listeners
0771: {
0772: final Set<ConstraintsViolatedListener> classListeners = listenersByClass
0773: .get(guardedObject.getClass());
0774: if (classListeners != null) {
0775: listenersToNotify.addAll(classListeners);
0776: }
0777: }
0778:
0779: // get the interface listeners
0780: {
0781: for (final Class interfaze : guardedObject.getClass()
0782: .getInterfaces()) {
0783: final Set<ConstraintsViolatedListener> interfaceListeners = listenersByClass
0784: .get(interfaze);
0785: if (interfaceListeners != null) {
0786: listenersToNotify.addAll(interfaceListeners);
0787: }
0788: }
0789: }
0790:
0791: // get the global listeners
0792: listenersToNotify.addAll(listeners);
0793:
0794: // notify the listeners
0795: for (final ConstraintsViolatedListener listener : listenersToNotify) {
0796: try {
0797: listener.onConstraintsViolatedException(ex);
0798: } catch (final RuntimeException rex) {
0799: LOG.warn("Notifying listener '{}' failed.", listener,
0800: rex);
0801: }
0802: }
0803:
0804: }
0805:
0806: /**
0807: * Removes constraint checks for the given constructor parameter
0808: *
0809: * @param constructor
0810: * @param parameterIndex
0811: * @param checks
0812: * @throws InvalidConfigurationException if the declaring class is not guarded or the parameterIndex is out of range
0813: */
0814: public void removeChecks(final Constructor constructor,
0815: final int parameterIndex, final Check... checks)
0816: throws InvalidConfigurationException {
0817: if (constructor == null)
0818: throw new IllegalArgumentException(
0819: "constructor cannot be null");
0820: if (checks == null)
0821: throw new IllegalArgumentException("checks cannot be null");
0822: if (checks.length == 0)
0823: throw new IllegalArgumentException("checks cannot empty");
0824:
0825: final ClassChecks cc = getClassChecks(constructor
0826: .getDeclaringClass());
0827: cc.removeConstructorParameterChecks(constructor,
0828: parameterIndex, checks);
0829: }
0830:
0831: /**
0832: * Removes constraint checks for the given method parameter
0833: *
0834: * @param method
0835: * @param parameterIndex
0836: * @param checks
0837: * @throws IllegalArgumentException if <code>constructor == null</code> or <code>checks == null</code> or checks is empty
0838: * @throws InvalidConfigurationException if the parameterIndex is out of range
0839: */
0840: public void removeChecks(final Method method,
0841: final int parameterIndex, final Check... checks)
0842: throws InvalidConfigurationException {
0843: if (method == null)
0844: throw new IllegalArgumentException("method cannot be null");
0845: if (checks == null)
0846: throw new IllegalArgumentException("checks cannot be null");
0847: if (checks.length == 0)
0848: throw new IllegalArgumentException("checks cannot empty");
0849:
0850: final ClassChecks cc = getClassChecks(method
0851: .getDeclaringClass());
0852: cc.removeMethodParameterChecks(method, parameterIndex, checks);
0853: }
0854:
0855: /**
0856: * Registers post condition checks to a method's return value
0857: * @param method
0858: * @param checks
0859: * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
0860: * @throws InvalidConfigurationException if the declaring class is not guarded
0861: */
0862: public void removeChecks(final Method method,
0863: final PostCheck... checks)
0864: throws InvalidConfigurationException {
0865: if (method == null)
0866: throw new IllegalArgumentException("method cannot be null");
0867: if (checks == null)
0868: throw new IllegalArgumentException("checks cannot be null");
0869: if (checks.length == 0)
0870: throw new IllegalArgumentException("checks cannot empty");
0871:
0872: final ClassChecks cc = getClassChecks(method
0873: .getDeclaringClass());
0874: cc.removeMethodPostChecks(method, checks);
0875: }
0876:
0877: /**
0878: * Registers pre condition checks to a method's return value
0879: * @param method
0880: * @param checks
0881: * @throws IllegalArgumentException if <code>method == null</code> or <code>checks == null</code> or checks is empty
0882: * @throws InvalidConfigurationException if the declaring class is not guarded
0883: */
0884: public void removeChecks(final Method method,
0885: final PreCheck... checks)
0886: throws InvalidConfigurationException {
0887: if (method == null)
0888: throw new IllegalArgumentException("method cannot be null");
0889: if (checks == null)
0890: throw new IllegalArgumentException("checks cannot be null");
0891: if (checks.length == 0)
0892: throw new IllegalArgumentException("checks cannot empty");
0893:
0894: final ClassChecks cc = getClassChecks(method
0895: .getDeclaringClass());
0896: cc.removeMethodPreChecks(method, checks);
0897: }
0898:
0899: /**
0900: * Removes the given listener
0901: * @param listener
0902: * @return <code>true</code> if the listener was registered
0903: * @throws IllegalArgumentException if <code>listener == null</code>
0904: */
0905: public boolean removeListener(
0906: final ConstraintsViolatedListener listener)
0907: throws IllegalArgumentException {
0908: if (listener == null)
0909: throw new IllegalArgumentException(
0910: "listener cannot be null");
0911:
0912: return listeners.remove(listener);
0913: }
0914:
0915: /**
0916: * Removes the given listener
0917: * @param listener
0918: * @param guardedClass guarded class or interface
0919: * @return <code>true</code> if the listener was registered
0920: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedClass == null</code>
0921: */
0922: public boolean removeListener(
0923: final ConstraintsViolatedListener listener,
0924: final Class guardedClass) throws IllegalArgumentException {
0925: if (listener == null)
0926: throw new IllegalArgumentException(
0927: "listener cannot be null");
0928: if (guardedClass == null)
0929: throw new IllegalArgumentException(
0930: "guardedClass cannot be null");
0931:
0932: final Set<ConstraintsViolatedListener> currentListeners = listenersByClass
0933: .get(guardedClass);
0934:
0935: return currentListeners == null ? false : currentListeners
0936: .remove(listener);
0937: }
0938:
0939: /**
0940: * Removes the given listener
0941: * @param listener
0942: * @param guardedObject
0943: * @return <code>true</code> if the listener was registered
0944: * @throws IllegalArgumentException if <code>listener == null</code> or <code>guardedObject == null</code>
0945: */
0946: public boolean removeListener(
0947: final ConstraintsViolatedListener listener,
0948: final Object guardedObject) throws IllegalArgumentException {
0949: if (listener == null)
0950: throw new IllegalArgumentException(
0951: "listener cannot be null");
0952: if (guardedObject == null)
0953: throw new IllegalArgumentException(
0954: "guardedObject cannot be null");
0955:
0956: final Set<ConstraintsViolatedListener> currentListeners = listenersByObject
0957: .get(guardedObject);
0958:
0959: return currentListeners == null ? false : currentListeners
0960: .remove(listener);
0961: }
0962:
0963: /**
0964: * If set to false OVal's programming by contract features are disabled
0965: * and constraints are not checked automatically during runtime.
0966: * @param isActivated the isActivated to set
0967: */
0968: public void setActivated(final boolean isActivated) {
0969: this .isActivated = isActivated;
0970: }
0971:
0972: /**
0973: * Enable or disable the probe mode for the given object in the current thread.
0974: * In probe mode calls to methods of an object are not actually executed. OVal only
0975: * validates method pre-conditions and notifies ConstraintViolationListeners but
0976: * does not throw ConstraintViolationExceptions. Methods with return values will return null.
0977: *
0978: * @param guardedObject
0979: * @param isInProbeMode
0980: * @throws IllegalArgumentException if <code>guardedObject == null</code>
0981: */
0982: public void setInProbeMode(final Object guardedObject,
0983: final boolean isInProbeMode)
0984: throws IllegalArgumentException {
0985: if (guardedObject == null)
0986: throw new IllegalArgumentException(
0987: "guardedObject cannot be null");
0988:
0989: if (guardedObject instanceof Class) {
0990: LOG
0991: .warn(
0992: "Enabling probe mode for a class looks like a programming error. Class: {}",
0993: guardedObject);
0994: }
0995: isProbeModeFeatureUsed = true;
0996:
0997: if (isInProbeMode)
0998: objectsInProbeMode.get().add(guardedObject);
0999: else
1000: objectsInProbeMode.get().remove(guardedObject);
1001: }
1002:
1003: /**
1004: * Specifies if invariants are checked prior and after
1005: * calls to non-private methods and constructors.
1006: *
1007: * @param isEnabled the isInvariantsEnabled to set
1008: */
1009: public void setInvariantsEnabled(final boolean isEnabled) {
1010: isInvariantsEnabled = isEnabled;
1011: }
1012:
1013: /**
1014: * Specifies if invariants are checked prior and after
1015: * calls to non-private methods and constructors.
1016: *
1017: * @param guardedClass the guarded class to turn on/off the invariant checking
1018: * @param isEnabled the isEnabled to set
1019: */
1020: public void setInvariantsEnabled(final Class<?> guardedClass,
1021: final boolean isEnabled) {
1022: final ClassChecks cc = getClassChecks(guardedClass);
1023: cc.isCheckInvariants = isEnabled;
1024: }
1025:
1026: /**
1027: * @param parameterNameResolver the parameterNameResolver to set, cannot be null
1028: * @throws IllegalArgumentException if <code>parameterNameResolver == null</code>
1029: */
1030: public void setParameterNameResolver(
1031: final ParameterNameResolver parameterNameResolver)
1032: throws IllegalArgumentException {
1033: if (parameterNameResolver == null)
1034: throw new IllegalArgumentException(
1035: "parameterNameResolver cannot be null");
1036:
1037: this .parameterNameResolver = parameterNameResolver;
1038: }
1039:
1040: /**
1041: * @param isEnabled the isEnabled to set
1042: */
1043: public void setPostConditionsEnabled(final boolean isEnabled) {
1044: isPostConditionsEnabled = isEnabled;
1045: }
1046:
1047: /**
1048: * @param isEnabled the isEnabled to set
1049: */
1050: public void setPreConditionsEnabled(final boolean isEnabled) {
1051: isPreConditionsEnabled = isEnabled;
1052: }
1053:
1054: /**
1055: * Validates the give arguments against the defined constructor parameter constraints.<br>
1056: *
1057: * @return null if no violation, otherwise a list
1058: * @throws ValidationFailedException
1059: */
1060: protected List<ConstraintViolation> validateConstructorParameters(
1061: final Object validatedObject,
1062: final Constructor constructor, final Object[] argsToValidate)
1063: throws ValidationFailedException {
1064: try {
1065: final ClassChecks cc = getClassChecks(constructor
1066: .getDeclaringClass());
1067: final Map<Integer, Set<Check>> parameterChecks = cc.checksForConstructorParameters
1068: .get(constructor);
1069:
1070: // if no parameter checks exist just return null
1071: if (parameterChecks == null)
1072: return null;
1073:
1074: final List<ConstraintViolation> violations = CollectionFactoryHolder
1075: .getFactory().createList();
1076:
1077: final String[] parameterNames = parameterNameResolver
1078: .getParameterNames(constructor);
1079:
1080: for (int i = 0; i < argsToValidate.length; i++) {
1081: final Collection<Check> checks = parameterChecks.get(i);
1082:
1083: if (checks != null && checks.size() > 0) {
1084: final Object valueToValidate = argsToValidate[i];
1085: final ConstructorParameterContext context = new ConstructorParameterContext(
1086: constructor, i, parameterNames[i]);
1087:
1088: for (final Check check : checks) {
1089: checkConstraint(violations, check,
1090: validatedObject, valueToValidate,
1091: context);
1092: }
1093: }
1094: }
1095: return violations.size() == 0 ? null : violations;
1096: } catch (final OValException ex) {
1097: throw new ValidationFailedException(
1098: "Validation of constructor parameters failed. Constructor: "
1099: + constructor + " Validated object:"
1100: + validatedObject, ex);
1101: }
1102: }
1103:
1104: @Override
1105: protected void validateInvariants(final Object guardedObject,
1106: final List<ConstraintViolation> violations)
1107: throws IllegalArgumentException, ValidationFailedException {
1108: if (!currentlyInvariantCheckingFor.get()
1109: .contains(guardedObject)) {
1110: currentlyInvariantCheckingFor.get().add(guardedObject);
1111: try {
1112: super .validateInvariants(guardedObject, violations);
1113: } finally {
1114: currentlyInvariantCheckingFor.get().remove(
1115: guardedObject);
1116: }
1117: }
1118: }
1119:
1120: /**
1121: * Validates the pre conditions for a method call.<br>
1122: *
1123: * @throws ValidationFailedException
1124: */
1125: protected void validateMethodParameters(
1126: final Object validatedObject, final Method method,
1127: final Object[] args,
1128: final List<ConstraintViolation> violations)
1129: throws ValidationFailedException {
1130: try {
1131: final ClassChecks cc = getClassChecks(method
1132: .getDeclaringClass());
1133: final Map<Integer, Set<Check>> parameterChecks = cc.checksForMethodParameters
1134: .get(method);
1135:
1136: if (parameterChecks == null)
1137: return;
1138:
1139: final String[] parameterNames = parameterNameResolver
1140: .getParameterNames(method);
1141: final boolean hasParameters = parameterNames.length > 0;
1142:
1143: /*
1144: * parameter constraints validation
1145: */
1146: if (parameterChecks != null && hasParameters) {
1147: for (int i = 0; i < args.length; i++) {
1148: final Collection<Check> checks = parameterChecks
1149: .get(i);
1150:
1151: if (checks != null && checks.size() > 0) {
1152: final Object valueToValidate = args[i];
1153: final MethodParameterContext context = new MethodParameterContext(
1154: method, i, parameterNames[i]);
1155:
1156: for (final Check check : checks) {
1157: checkConstraint(violations, check,
1158: validatedObject, valueToValidate,
1159: context);
1160: }
1161: }
1162: }
1163: }
1164: } catch (final OValException ex) {
1165: throw new ValidationFailedException(
1166: "Method pre conditions validation failed. Method: "
1167: + method + " Validated object: "
1168: + validatedObject, ex);
1169: }
1170: }
1171:
1172: /**
1173: * Validates the post conditions for a method call.<br>
1174: *
1175: * @throws ValidationFailedException
1176: */
1177: protected void validateMethodPost(final Object validatedObject,
1178: final Method method, final Object[] args,
1179: final Object returnValue,
1180: final Map<PostCheck, Object> oldValues,
1181: final List<ConstraintViolation> violations)
1182: throws ValidationFailedException {
1183: try {
1184: final ClassChecks cc = getClassChecks(method
1185: .getDeclaringClass());
1186: final Set<PostCheck> postChecks = cc.checksForMethodsPostExcecution
1187: .get(method);
1188:
1189: if (postChecks == null)
1190: return;
1191:
1192: final String[] parameterNames = parameterNameResolver
1193: .getParameterNames(method);
1194: final boolean hasParameters = parameterNames.length > 0;
1195:
1196: final MethodExitContext context = new MethodExitContext(
1197: method);
1198:
1199: for (final PostCheck check : postChecks) {
1200: if (!isAnyProfileEnabled(check.getProfiles()))
1201: continue;
1202:
1203: final ExpressionLanguage eng = expressionLanguages
1204: .get(check.getLanguage());
1205: final Map<String, Object> values = CollectionFactoryHolder
1206: .getFactory().createMap();
1207: values.put("_this", validatedObject);
1208: values.put("_returns", returnValue);
1209: values.put("_old", oldValues.get(check));
1210: if (hasParameters) {
1211: values.put("_args", args);
1212: for (int i = 0; i < args.length; i++) {
1213: values.put(parameterNames[i], args[i]);
1214: }
1215: } else
1216: values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
1217:
1218: if (!eng.evaluateAsBoolean(check.getExpression(),
1219: values)) {
1220: final Map<String, String> messageVariables = CollectionFactoryHolder
1221: .getFactory().createMap(2);
1222: messageVariables.put("expression", check
1223: .getExpression());
1224: final String errorMessage = renderMessage(context,
1225: null, check.getMessage(), messageVariables);
1226:
1227: violations.add(new ConstraintViolation(check
1228: .getErrorCode(), errorMessage, check
1229: .getSeverity(), validatedObject, null,
1230: context));
1231: }
1232: }
1233: } catch (final OValException ex) {
1234: throw new ValidationFailedException(
1235: "Method post conditions validation failed. Method: "
1236: + method + " Validated object: "
1237: + validatedObject, ex);
1238: }
1239: }
1240:
1241: /**
1242: * Validates the @Pre conditions for a method call.<br>
1243: *
1244: * @throws ValidationFailedException
1245: */
1246: protected void validateMethodPre(final Object validatedObject,
1247: final Method method, final Object[] args,
1248: final List<ConstraintViolation> violations)
1249: throws ValidationFailedException {
1250: try {
1251: final ClassChecks cc = getClassChecks(method
1252: .getDeclaringClass());
1253: final Set<PreCheck> preChecks = cc.checksForMethodsPreExecution
1254: .get(method);
1255:
1256: if (preChecks == null)
1257: return;
1258:
1259: final String[] parameterNames = parameterNameResolver
1260: .getParameterNames(method);
1261: final boolean hasParameters = parameterNames.length > 0;
1262:
1263: final MethodEntryContext context = new MethodEntryContext(
1264: method);
1265:
1266: for (final PreCheck check : preChecks) {
1267: if (!isAnyProfileEnabled(check.getProfiles()))
1268: continue;
1269:
1270: final ExpressionLanguage eng = expressionLanguages
1271: .get(check.getLanguage());
1272: final Map<String, Object> values = CollectionFactoryHolder
1273: .getFactory().createMap();
1274: values.put("_this", validatedObject);
1275: if (hasParameters) {
1276: values.put("_args", args);
1277: for (int i = 0; i < args.length; i++) {
1278: values.put(parameterNames[i], args[i]);
1279: }
1280: } else
1281: values.put("_args", ArrayUtils.EMPTY_OBJECT_ARRAY);
1282:
1283: if (!eng.evaluateAsBoolean(check.getExpression(),
1284: values)) {
1285: final Map<String, String> messageVariables = CollectionFactoryHolder
1286: .getFactory().createMap(2);
1287: messageVariables.put("expression", check
1288: .getExpression());
1289: final String errorMessage = renderMessage(context,
1290: null, check.getMessage(), messageVariables);
1291:
1292: violations.add(new ConstraintViolation(check
1293: .getErrorCode(), errorMessage, check
1294: .getSeverity(), validatedObject, null,
1295: context));
1296: }
1297: }
1298: } catch (final OValException ex) {
1299: throw new ValidationFailedException(
1300: "Method post conditions validation failed. Method: "
1301: + method + " Validated object: "
1302: + validatedObject, ex);
1303: }
1304: }
1305:
1306: /**
1307: * Validates the return value checks for a method call.<br>
1308: *
1309: * @throws ValidationFailedException
1310: */
1311: protected void validateMethodReturnValue(
1312: final Object validatedObject, final Method method,
1313: final Object returnValue,
1314: final List<ConstraintViolation> violations)
1315: throws ValidationFailedException {
1316: try {
1317: final ClassChecks cc = getClassChecks(method
1318: .getDeclaringClass());
1319: final Collection<Check> returnValueChecks = cc.checksForMethodReturnValues
1320: .get(method);
1321:
1322: if (returnValueChecks == null
1323: || returnValueChecks.size() == 0)
1324: return;
1325:
1326: final MethodReturnValueContext context = new MethodReturnValueContext(
1327: method);
1328:
1329: for (final Check check : returnValueChecks) {
1330: checkConstraint(violations, check, validatedObject,
1331: returnValue, context);
1332: }
1333: } catch (final OValException ex) {
1334: throw new ValidationFailedException(
1335: "Method post conditions validation failed. Method: "
1336: + method + " Validated object: "
1337: + validatedObject, ex);
1338: }
1339: }
1340: }
|