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;
014    
015    import static java.lang.Boolean.*;
016    
017    import java.lang.reflect.Constructor;
018    import java.lang.reflect.Field;
019    import java.lang.reflect.Method;
020    import java.util.Collection;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.Set;
024    import java.util.WeakHashMap;
025    
026    import net.sf.oval.collection.CollectionFactory;
027    import net.sf.oval.collection.CollectionFactoryJDKImpl;
028    import net.sf.oval.collection.CollectionFactoryJavalutionImpl;
029    import net.sf.oval.collection.CollectionFactoryTroveImpl;
030    import net.sf.oval.configuration.Configurer;
031    import net.sf.oval.configuration.annotation.AnnotationsConfigurer;
032    import net.sf.oval.configuration.annotation.JPAAnnotationsConfigurer;
033    import net.sf.oval.configuration.pojo.POJOConfigurer;
034    import net.sf.oval.configuration.pojo.elements.ClassConfiguration;
035    import net.sf.oval.configuration.pojo.elements.ConstraintSetConfiguration;
036    import net.sf.oval.configuration.pojo.elements.ConstructorConfiguration;
037    import net.sf.oval.configuration.pojo.elements.FieldConfiguration;
038    import net.sf.oval.configuration.pojo.elements.MethodConfiguration;
039    import net.sf.oval.configuration.pojo.elements.ObjectConfiguration;
040    import net.sf.oval.configuration.pojo.elements.ParameterConfiguration;
041    import net.sf.oval.configuration.xml.XMLConfigurer;
042    import net.sf.oval.constraint.AssertConstraintSetCheck;
043    import net.sf.oval.constraint.AssertFieldConstraintsCheck;
044    import net.sf.oval.constraint.AssertValidCheck;
045    import net.sf.oval.constraint.NotNullCheck;
046    import net.sf.oval.context.ConstructorParameterContext;
047    import net.sf.oval.context.FieldContext;
048    import net.sf.oval.context.MethodParameterContext;
049    import net.sf.oval.context.MethodReturnValueContext;
050    import net.sf.oval.context.OValContext;
051    import net.sf.oval.exception.ConstraintSetAlreadyDefinedException;
052    import net.sf.oval.exception.ConstraintsViolatedException;
053    import net.sf.oval.exception.ExceptionTranslator;
054    import net.sf.oval.exception.FieldNotFoundException;
055    import net.sf.oval.exception.InvalidConfigurationException;
056    import net.sf.oval.exception.MethodNotFoundException;
057    import net.sf.oval.exception.OValException;
058    import net.sf.oval.exception.ReflectionException;
059    import net.sf.oval.exception.UndefinedConstraintSetException;
060    import net.sf.oval.exception.ValidationFailedException;
061    import net.sf.oval.expression.ExpressionLanguageRegistry;
062    import net.sf.oval.guard.ParameterNameResolver;
063    import net.sf.oval.guard.ParameterNameResolverEnumerationImpl;
064    import net.sf.oval.internal.ClassChecks;
065    import net.sf.oval.internal.ContextCache;
066    import net.sf.oval.internal.Log;
067    import net.sf.oval.internal.MessageRenderer;
068    import net.sf.oval.internal.util.ArrayUtils;
069    import net.sf.oval.internal.util.Assert;
070    import net.sf.oval.internal.util.IdentitySet;
071    import net.sf.oval.internal.util.LinkedSet;
072    import net.sf.oval.internal.util.ReflectionUtils;
073    import net.sf.oval.internal.util.StringUtils;
074    import net.sf.oval.internal.util.ThreadLocalLinkedList;
075    import net.sf.oval.localization.context.OValContextRenderer;
076    import net.sf.oval.localization.context.ToStringValidationContextRenderer;
077    import net.sf.oval.localization.message.MessageResolver;
078    import net.sf.oval.localization.message.ResourceBundleMessageResolver;
079    import net.sf.oval.localization.value.MessageValueFormatter;
080    import net.sf.oval.localization.value.ToStringMessageValueFormatter;
081    import net.sf.oval.logging.LoggerFactory;
082    import net.sf.oval.ogn.ObjectGraphNavigationResult;
083    import net.sf.oval.ogn.ObjectGraphNavigatorRegistry;
084    
085    /**
086     * <p>Instances of this class can validate objects based on declared constraints.
087     * Constraints can either be declared using OVal's constraint annotations, XML configuration
088     * files or EJB3 JPA annotations.</p>
089     * 
090     * <p>This class is thread-safe.</p>
091     * 
092     * @author Sebastian Thomschke
093     * 
094     * @see AnnotationsConfigurer
095     * @see JPAAnnotationsConfigurer
096     * @see POJOConfigurer
097     * @see XMLConfigurer
098     */
099    public class Validator implements IValidator
100    {
101            protected static final class DelegatingParameterNameResolver implements ParameterNameResolver
102            {
103                    private ParameterNameResolver delegate;
104    
105                    public DelegatingParameterNameResolver(final ParameterNameResolver delegate)
106                    {
107                            this.delegate = delegate;
108                    }
109    
110                    public ParameterNameResolver getDelegate()
111                    {
112                            return delegate;
113                    }
114    
115                    /**
116                     * {@inheritDoc}
117                     */
118                    public String[] getParameterNames(final Constructor< ? > constructor) throws ReflectionException
119                    {
120                            return delegate.getParameterNames(constructor);
121                    }
122    
123                    /**
124                     * {@inheritDoc}
125                     */
126                    public String[] getParameterNames(final Method method) throws ReflectionException
127                    {
128                            return delegate.getParameterNames(method);
129                    }
130    
131                    public void setDelegate(final ParameterNameResolver delegate)
132                    {
133                            this.delegate = delegate;
134                    }
135            }
136    
137            private static final Log LOG = Log.getLog(Validator.class);
138    
139            private static CollectionFactory collectionFactory = _createDefaultCollectionFactory();
140            private static OValContextRenderer contextRenderer = ToStringValidationContextRenderer.INSTANCE;
141    
142            private static MessageResolver messageResolver;
143            private static MessageValueFormatter messageValueFormatter = ToStringMessageValueFormatter.INSTANCE;
144    
145            private static CollectionFactory _createDefaultCollectionFactory()
146            {
147                    // if Javolution collection classes are found use them by default
148                    if (ReflectionUtils.isClassPresent("javolution.util.FastMap")
149                                    && ReflectionUtils.isClassPresent("javolution.util.FastSet")
150                                    && ReflectionUtils.isClassPresent("javolution.util.FastTable"))
151                    {
152                            LOG.info("javolution.util collection classes are available.");
153    
154                            return new CollectionFactoryJavalutionImpl();
155                    }
156                    // else if Trove collection classes are found use them by default
157                    else if (ReflectionUtils.isClassPresent("gnu.trove.THashMap")
158                                    && ReflectionUtils.isClassPresent("gnu.trove.THashSet"))
159                    {
160                            LOG.info("gnu.trove collection classes are available.");
161    
162                            return new CollectionFactoryTroveImpl();
163                    }
164                    // else use JDK collection classes by default
165                    else
166                            return new CollectionFactoryJDKImpl();
167            }
168    
169            /**
170             * Returns a shared instance of the CollectionFactory
171             */
172            public static CollectionFactory getCollectionFactory()
173            {
174                    return collectionFactory;
175            }
176    
177            /**
178             * @return the contextRenderer
179             */
180            public static OValContextRenderer getContextRenderer()
181            {
182                    return contextRenderer;
183            }
184    
185            /**
186             * @return the loggerFactory
187             */
188            public static LoggerFactory getLoggerFactory()
189            {
190                    return Log.getLoggerFactory();
191            }
192    
193            /**
194             * @return the messageResolver
195             */
196            public static MessageResolver getMessageResolver()
197            {
198                    /*
199                     * since ResourceBundleMessageResolver references getCollectionFactory() of this class
200                     * we are lazy referencing the resolvers shared instance.
201                     */
202                    if (messageResolver == null) messageResolver = ResourceBundleMessageResolver.INSTANCE;
203                    return messageResolver;
204            }
205    
206            /**
207             * @return the messageValueFormatter
208             */
209            public static MessageValueFormatter getMessageValueFormatter()
210            {
211                    return messageValueFormatter;
212            }
213    
214            /**
215             * 
216             * @param factory the new collection factory to be used by all validator instances
217             */
218            public static void setCollectionFactory(final CollectionFactory factory) throws IllegalArgumentException
219            {
220                    Assert.argumentNotNull("factory", factory);
221                    Validator.collectionFactory = factory;
222            }
223    
224            /**
225             * @param contextRenderer the contextRenderer to set
226             */
227            public static void setContextRenderer(final OValContextRenderer contextRenderer)
228            {
229                    Assert.argumentNotNull("contextRenderer", contextRenderer);
230                    Validator.contextRenderer = contextRenderer;
231            }
232    
233            /**
234             * @param loggerFactory the loggerFactory to set
235             */
236            public static void setLoggerFactory(final LoggerFactory loggerFactory)
237            {
238                    Assert.argumentNotNull("loggerFactory", loggerFactory);
239                    Log.setLoggerFactory(loggerFactory);
240            }
241    
242            /**
243             * @param messageResolver the messageResolver to set
244             * @throws IllegalArgumentException if <code>messageResolver == null</code>
245             */
246            public static void setMessageResolver(final MessageResolver messageResolver) throws IllegalArgumentException
247            {
248                    Assert.argumentNotNull("messageResolver", messageResolver);
249                    Validator.messageResolver = messageResolver;
250            }
251    
252            /**
253             * @param formatter the messageValueFormatter to set
254             */
255            public static void setMessageValueFormatter(final MessageValueFormatter formatter)
256            {
257                    Assert.argumentNotNull("formatter", formatter);
258                    Validator.messageValueFormatter = formatter;
259            }
260    
261            private final Map<Class< ? >, ClassChecks> checksByClass = new WeakHashMap<Class< ? >, ClassChecks>();
262    
263            private final List<Configurer> configurers = new LinkedSet<Configurer>(4);
264    
265            private final Map<String, ConstraintSet> constraintSetsById = collectionFactory.createMap(4);
266    
267            protected final ThreadLocalLinkedList<Set<Object>> currentlyValidatedObjects = new ThreadLocalLinkedList<Set<Object>>();
268    
269            protected final ThreadLocalLinkedList<List<ConstraintViolation>> currentViolations = new ThreadLocalLinkedList<List<ConstraintViolation>>();
270    
271            private final Set<String> disabledProfiles = collectionFactory.createSet();
272    
273            private final Set<String> enabledProfiles = collectionFactory.createSet();
274    
275            protected final ExpressionLanguageRegistry expressionLanguageRegistry = new ExpressionLanguageRegistry();
276    
277            private ExceptionTranslator exceptionTranslator;
278    
279            private boolean isAllProfilesEnabledByDefault = true;
280    
281            /**
282             * Flag that indicates any configuration method related to profiles was called.
283             * Used for performance improvements.
284             */
285            private boolean isProfilesFeatureUsed = false;
286    
287            protected final ObjectGraphNavigatorRegistry ognRegistry = new ObjectGraphNavigatorRegistry();
288    
289            protected final DelegatingParameterNameResolver parameterNameResolver = new DelegatingParameterNameResolver(
290                            new ParameterNameResolverEnumerationImpl());
291    
292            /**
293             * Constructs a new validator instance and uses a new instance of AnnotationsConfigurer
294             */
295            public Validator()
296            {
297                    ReflectionUtils.assertPrivateAccessAllowed();
298                    configurers.add(new AnnotationsConfigurer());
299            }
300    
301            /**
302             * Constructs a new validator instance and configures it using the given configurers
303             * 
304             * @param configurers
305             */
306            public Validator(final Collection<Configurer> configurers)
307            {
308                    ReflectionUtils.assertPrivateAccessAllowed();
309                    if (configurers != null) this.configurers.addAll(configurers);
310            }
311    
312            /**
313             * Constructs a new validator instance and configures it using the given configurers
314             * 
315             * @param configurers
316             */
317            public Validator(final Configurer... configurers)
318            {
319                    ReflectionUtils.assertPrivateAccessAllowed();
320                    if (configurers != null) for (final Configurer configurer : configurers)
321                            this.configurers.add(configurer);
322            }
323    
324            private void _addChecks(final ClassChecks cc, final ClassConfiguration classCfg)
325                            throws InvalidConfigurationException, ReflectionException
326            {
327                    if (TRUE.equals(classCfg.overwrite)) cc.clear();
328    
329                    if (classCfg.checkInvariants != null) cc.isCheckInvariants = classCfg.checkInvariants;
330    
331                    // cache the result for better performance
332                    final boolean applyFieldConstraintsToConstructors = TRUE.equals(classCfg.applyFieldConstraintsToConstructors);
333                    final boolean applyFieldConstraintsToSetters = TRUE.equals(classCfg.applyFieldConstraintsToSetters);
334                    final boolean assertParametersNotNull = TRUE.equals(classCfg.assertParametersNotNull);
335                    final NotNullCheck sharedNotNullCheck = assertParametersNotNull ? new NotNullCheck() : null;
336    
337                    try
338                    {
339                            /* ******************************
340                             * apply object level checks
341                             * ******************************/
342                            if (classCfg.objectConfiguration != null)
343                            {
344                                    final ObjectConfiguration objectCfg = classCfg.objectConfiguration;
345    
346                                    if (TRUE.equals(objectCfg.overwrite)) cc.clearObjectChecks();
347                                    cc.addObjectChecks(objectCfg.checks);
348                            }
349    
350                            /* ******************************
351                             * apply field checks
352                             * ******************************/
353                            if (classCfg.fieldConfigurations != null)
354                                    for (final FieldConfiguration fieldCfg : classCfg.fieldConfigurations)
355                                    {
356                                            final Field field = classCfg.type.getDeclaredField(fieldCfg.name);
357    
358                                            if (TRUE.equals(fieldCfg.overwrite)) cc.clearFieldChecks(field);
359    
360                                            if (fieldCfg.checks != null && fieldCfg.checks.size() > 0)
361                                                    cc.addFieldChecks(field, fieldCfg.checks);
362                                    }
363    
364                            /* ******************************
365                             * apply constructor parameter checks
366                             * ******************************/
367                            if (classCfg.constructorConfigurations != null)
368                                    for (final ConstructorConfiguration ctorCfg : classCfg.constructorConfigurations)
369                                    {
370                                            // ignore constructors without parameters
371                                            if (ctorCfg.parameterConfigurations == null) continue;
372    
373                                            final Class< ? >[] paramTypes = new Class[ctorCfg.parameterConfigurations.size()];
374    
375                                            for (int i = 0, l = ctorCfg.parameterConfigurations.size(); i < l; i++)
376                                                    paramTypes[i] = ctorCfg.parameterConfigurations.get(i).type;
377    
378                                            final Constructor< ? > ctor = classCfg.type.getDeclaredConstructor(paramTypes);
379    
380                                            if (TRUE.equals(ctorCfg.overwrite)) cc.clearConstructorChecks(ctor);
381    
382                                            if (TRUE.equals(ctorCfg.postCheckInvariants)) cc.methodsWithCheckInvariantsPost.add(ctor);
383    
384                                            final String[] paramNames = parameterNameResolver.getParameterNames(ctor);
385    
386                                            for (int i = 0, l = ctorCfg.parameterConfigurations.size(); i < l; i++)
387                                            {
388                                                    final ParameterConfiguration paramCfg = ctorCfg.parameterConfigurations.get(i);
389    
390                                                    if (TRUE.equals(paramCfg.overwrite)) cc.clearConstructorParameterChecks(ctor, i);
391    
392                                                    if (paramCfg.hasChecks()) cc.addConstructorParameterChecks(ctor, i, paramCfg.checks);
393    
394                                                    if (paramCfg.hasCheckExclusions())
395                                                            cc.addConstructorParameterCheckExclusions(ctor, i, paramCfg.checkExclusions);
396    
397                                                    if (assertParametersNotNull) cc.addConstructorParameterChecks(ctor, i, sharedNotNullCheck);
398    
399                                                    /* *******************
400                                                     * applying field constraints to the single parameter of setter methods 
401                                                     * *******************/
402                                                    if (applyFieldConstraintsToConstructors)
403                                                    {
404                                                            final Field field = ReflectionUtils.getField(cc.clazz, paramNames[i]);
405    
406                                                            // check if a corresponding field has been found
407                                                            if (field != null && paramTypes[i].isAssignableFrom(field.getType()))
408                                                            {
409                                                                    final AssertFieldConstraintsCheck check = new AssertFieldConstraintsCheck();
410                                                                    check.setFieldName(field.getName());
411                                                                    cc.addConstructorParameterChecks(ctor, i, check);
412                                                            }
413                                                    }
414                                            }
415                                    }
416    
417                            /* ******************************
418                             * apply method parameter and return value checks and pre/post conditions
419                             * ******************************/
420                            if (classCfg.methodConfigurations != null)
421                                    for (final MethodConfiguration methodCfg : classCfg.methodConfigurations)
422                                    {
423                                            /* ******************************
424                                             * determine the method
425                                             * ******************************/
426                                            final Method method;
427    
428                                            if (methodCfg.parameterConfigurations == null || methodCfg.parameterConfigurations.size() == 0)
429                                                    method = classCfg.type.getDeclaredMethod(methodCfg.name);
430                                            else
431                                            {
432                                                    final Class< ? >[] paramTypes = new Class[methodCfg.parameterConfigurations.size()];
433    
434                                                    for (int i = 0, l = methodCfg.parameterConfigurations.size(); i < l; i++)
435                                                            paramTypes[i] = methodCfg.parameterConfigurations.get(i).type;
436    
437                                                    method = classCfg.type.getDeclaredMethod(methodCfg.name, paramTypes);
438                                            }
439    
440                                            if (TRUE.equals(methodCfg.overwrite)) cc.clearMethodChecks(method);
441    
442                                            /* ******************************
443                                             * applying field constraints to the single parameter of setter methods 
444                                             * ******************************/
445                                            if (applyFieldConstraintsToSetters)
446                                            {
447                                                    final Field field = ReflectionUtils.getFieldForSetter(method);
448    
449                                                    // check if a corresponding field has been found
450                                                    if (field != null)
451                                                    {
452                                                            final AssertFieldConstraintsCheck check = new AssertFieldConstraintsCheck();
453                                                            check.setFieldName(field.getName());
454                                                            cc.addMethodParameterChecks(method, 0, check);
455                                                    }
456                                            }
457    
458                                            /* ******************************
459                                             * configure parameter constraints
460                                             * ******************************/
461                                            if (methodCfg.parameterConfigurations != null && methodCfg.parameterConfigurations.size() > 0)
462                                                    for (int i = 0, l = methodCfg.parameterConfigurations.size(); i < l; i++)
463                                                    {
464                                                            final ParameterConfiguration paramCfg = methodCfg.parameterConfigurations.get(i);
465    
466                                                            if (TRUE.equals(paramCfg.overwrite)) cc.clearMethodParameterChecks(method, i);
467    
468                                                            if (paramCfg.hasChecks()) cc.addMethodParameterChecks(method, i, paramCfg.checks);
469    
470                                                            if (paramCfg.hasCheckExclusions())
471                                                                    cc.addMethodParameterCheckExclusions(method, i, paramCfg.checkExclusions);
472    
473                                                            if (assertParametersNotNull) cc.addMethodParameterChecks(method, i, sharedNotNullCheck);
474                                                    }
475    
476                                            /* ******************************
477                                             * configure return value constraints
478                                             * ******************************/
479                                            if (methodCfg.returnValueConfiguration != null)
480                                            {
481                                                    if (TRUE.equals(methodCfg.returnValueConfiguration.overwrite))
482                                                            cc.clearMethodReturnValueChecks(method);
483    
484                                                    if (methodCfg.returnValueConfiguration.checks != null
485                                                                    && methodCfg.returnValueConfiguration.checks.size() > 0)
486                                                            cc.addMethodReturnValueChecks(method, methodCfg.isInvariant,
487                                                                            methodCfg.returnValueConfiguration.checks);
488                                            }
489    
490                                            if (TRUE.equals(methodCfg.preCheckInvariants)) cc.methodsWithCheckInvariantsPre.add(method);
491    
492                                            /*
493                                             * configure pre conditions
494                                             */
495                                            if (methodCfg.preExecutionConfiguration != null)
496                                            {
497                                                    if (TRUE.equals(methodCfg.preExecutionConfiguration.overwrite))
498                                                            cc.clearMethodPreChecks(method);
499    
500                                                    if (methodCfg.preExecutionConfiguration.checks != null
501                                                                    && methodCfg.preExecutionConfiguration.checks.size() > 0)
502                                                            cc.addMethodPreChecks(method, methodCfg.preExecutionConfiguration.checks);
503                                            }
504    
505                                            if (TRUE.equals(methodCfg.postCheckInvariants)) cc.methodsWithCheckInvariantsPost.add(method);
506    
507                                            /*
508                                             * configure post conditions
509                                             */
510                                            if (methodCfg.postExecutionConfiguration != null)
511                                            {
512                                                    if (TRUE.equals(methodCfg.postExecutionConfiguration.overwrite))
513                                                            cc.clearMethodPostChecks(method);
514    
515                                                    if (methodCfg.postExecutionConfiguration.checks != null
516                                                                    && methodCfg.postExecutionConfiguration.checks.size() > 0)
517                                                            cc.addMethodPostChecks(method, methodCfg.postExecutionConfiguration.checks);
518                                            }
519                                    }
520                    }
521                    catch (final NoSuchMethodException ex)
522                    {
523                            throw new MethodNotFoundException(ex);
524                    }
525                    catch (final NoSuchFieldException ex)
526                    {
527                            throw new FieldNotFoundException(ex);
528                    }
529            }
530    
531            private void _checkConstraint(final List<ConstraintViolation> violations, final Check check,
532                            final Object validatedObject, final Object valueToValidate, final OValContext context,
533                            final String[] profiles)
534            {
535                    /*
536                     * special handling of the AssertValid constraint
537                     */
538                    if (check instanceof AssertValidCheck)
539                    {
540                            checkConstraintAssertValid(violations, (AssertValidCheck) check, validatedObject, valueToValidate, context,
541                                            profiles);
542                            return;
543                    }
544    
545                    /*
546                     * special handling of the FieldConstraints constraint
547                     */
548                    if (check instanceof AssertConstraintSetCheck)
549                    {
550                            checkConstraintAssertConstraintSet(violations, (AssertConstraintSetCheck) check, validatedObject,
551                                            valueToValidate, context, profiles);
552                            return;
553                    }
554    
555                    /*
556                     * special handling of the FieldConstraints constraint
557                     */
558                    if (check instanceof AssertFieldConstraintsCheck)
559                    {
560                            checkConstraintAssertFieldConstraints(violations, (AssertFieldConstraintsCheck) check, validatedObject,
561                                            valueToValidate, context, profiles);
562                            return;
563                    }
564    
565                    /*
566                     * standard constraints handling
567                     */
568                    if (!check.isSatisfied(validatedObject, valueToValidate, context, this))
569                    {
570                            final String errorMessage = renderMessage(context, valueToValidate, check.getMessage(),
571                                            check.getMessageVariables());
572                            violations.add(new ConstraintViolation(check, errorMessage, validatedObject, valueToValidate, context));
573                    }
574            }
575    
576            /**
577             * validate validatedObject based on the constraints of the given class 
578             */
579            private void _validateObjectInvariants(final Object validatedObject, final Class< ? > clazz,
580                            final List<ConstraintViolation> violations, final String[] profiles) throws ValidationFailedException
581            {
582                    assert validatedObject != null;
583                    assert clazz != null;
584                    assert violations != null;
585    
586                    // abort if the root class has been reached
587                    if (clazz == Object.class) return;
588    
589                    try
590                    {
591                            final ClassChecks cc = getClassChecks(clazz);
592    
593                            // validate field constraints
594                            for (final Field field : cc.constrainedFields)
595                            {
596                                    final Collection<Check> checks = cc.checksForFields.get(field);
597    
598                                    if (checks != null && checks.size() > 0)
599                                    {
600                                            final Object valueToValidate = ReflectionUtils.getFieldValue(field, validatedObject);
601                                            final OValContext ctx = ContextCache.getFieldContext(field);
602    
603                                            for (final Check check : checks)
604                                                    checkConstraint(violations, check, validatedObject, valueToValidate, ctx, profiles, false);
605                                    }
606                            }
607    
608                            // validate constraints on getter methods
609                            for (final Method getter : cc.constrainedMethods)
610                            {
611                                    final Collection<Check> checks = cc.checksForMethodReturnValues.get(getter);
612    
613                                    if (checks != null && checks.size() > 0)
614                                    {
615                                            final Object valueToValidate = ReflectionUtils.invokeMethod(getter, validatedObject);
616                                            final OValContext ctx = ContextCache.getMethodReturnValueContext(getter);
617    
618                                            for (final Check check : checks)
619                                                    checkConstraint(violations, check, validatedObject, valueToValidate, ctx, profiles, false);
620                                    }
621                            }
622    
623                            // validate object constraints
624                            if (cc.checksForObject.size() > 0)
625                            {
626                                    final OValContext ctx = ContextCache.getClassContext(clazz);
627                                    for (final Check check : cc.checksForObject)
628                                            checkConstraint(violations, check, validatedObject, validatedObject, ctx, profiles, false);
629                            }
630    
631                            // if the super class is annotated to be validatable also validate it against the object
632                            _validateObjectInvariants(validatedObject, clazz.getSuperclass(), violations, profiles);
633                    }
634                    catch (final OValException ex)
635                    {
636                            throw new ValidationFailedException("Object validation failed. Class: " + clazz + " Validated object: "
637                                            + validatedObject, ex);
638                    }
639            }
640    
641            /**
642             * Validates the static field and static getter constrains of the given class.
643             * Constraints specified for super classes are not taken in account.
644             */
645            private void _validateStaticInvariants(final Class< ? > validatedClass, final List<ConstraintViolation> violations,
646                            final String[] profiles) throws ValidationFailedException
647            {
648                    assert validatedClass != null;
649                    assert violations != null;
650    
651                    final ClassChecks cc = getClassChecks(validatedClass);
652    
653                    // validate static field constraints
654                    for (final Field field : cc.constrainedStaticFields)
655                    {
656                            final Collection<Check> checks = cc.checksForFields.get(field);
657    
658                            if (checks != null && checks.size() > 0)
659                            {
660                                    final Object valueToValidate = ReflectionUtils.getFieldValue(field, null);
661                                    final OValContext context = ContextCache.getFieldContext(field);
662    
663                                    for (final Check check : checks)
664                                            checkConstraint(violations, check, validatedClass, valueToValidate, context, profiles, false);
665                            }
666                    }
667    
668                    // validate constraints on getter methods
669                    for (final Method getter : cc.constrainedStaticMethods)
670                    {
671                            final Collection<Check> checks = cc.checksForMethodReturnValues.get(getter);
672    
673                            if (checks != null && checks.size() > 0)
674                            {
675                                    final Object valueToValidate = ReflectionUtils.invokeMethod(getter, null);
676                                    final OValContext context = ContextCache.getMethodReturnValueContext(getter);
677    
678                                    for (final Check check : checks)
679                                            checkConstraint(violations, check, validatedClass, valueToValidate, context, profiles, false);
680                            }
681                    }
682            }
683    
684            /**
685             * Registers object-level constraint checks
686             *  
687             * @param clazz the class to register the checks for
688             * @param checks the checks to add
689             * @throws IllegalArgumentException if <code>clazz == null</code> or <code>checks == null</code> or checks is empty 
690             */
691            public void addChecks(final Class< ? > clazz, final Check... checks) throws IllegalArgumentException
692            {
693                    Assert.argumentNotNull("clazz", clazz);
694                    Assert.argumentNotEmpty("checks", checks);
695    
696                    getClassChecks(clazz).addObjectChecks(checks);
697            }
698    
699            /**
700             * Registers constraint checks for the given field 
701             *  
702             * @param field the field to declare the checks for
703             * @param checks the checks to add
704             * @throws IllegalArgumentException if <code>field == null</code> or <code>checks == null</code> or checks is empty 
705             */
706            public void addChecks(final Field field, final Check... checks) throws IllegalArgumentException
707            {
708                    Assert.argumentNotNull("field", field);
709                    Assert.argumentNotEmpty("checks", checks);
710    
711                    getClassChecks(field.getDeclaringClass()).addFieldChecks(field, checks);
712            }
713    
714            /**
715             * Registers constraint checks for the given getter's return value
716             * 
717             * @param invariantMethod a non-void, non-parameterized method (usually a JavaBean Getter style method)
718             * @param checks the checks to add
719             * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code>
720             * @throws InvalidConfigurationException if getter is not a getter method
721             */
722            public void addChecks(final Method invariantMethod, final Check... checks) throws IllegalArgumentException,
723                            InvalidConfigurationException
724            {
725                    Assert.argumentNotNull("invariantMethod", invariantMethod);
726                    Assert.argumentNotEmpty("checks", checks);
727    
728                    getClassChecks(invariantMethod.getDeclaringClass()).addMethodReturnValueChecks(invariantMethod, TRUE, checks);
729            }
730    
731            /**
732             * Registers a new constraint set.
733             * 
734             * @param constraintSet cannot be null
735             * @param overwrite
736             * @throws ConstraintSetAlreadyDefinedException if <code>overwrite == false</code> and
737             *                                                                                              a constraint set with the given id exists already 
738             * @throws IllegalArgumentException if <code>constraintSet == null</code> 
739             *                                                                      or <code>constraintSet.id == null</code> 
740             *                                                                      or <code>constraintSet.id.length == 0</code>
741             * @throws IllegalArgumentException if <code>constraintSet.id == null</code>
742             */
743            public void addConstraintSet(final ConstraintSet constraintSet, final boolean overwrite)
744                            throws ConstraintSetAlreadyDefinedException, IllegalArgumentException
745            {
746                    Assert.argumentNotNull("constraintSet", constraintSet);
747                    Assert.argumentNotEmpty("constraintSet.id", constraintSet.getId());
748    
749                    synchronized (constraintSetsById)
750                    {
751                            if (!overwrite && constraintSetsById.containsKey(constraintSet.getId()))
752                                    throw new ConstraintSetAlreadyDefinedException(constraintSet.getId());
753    
754                            constraintSetsById.put(constraintSet.getId(), constraintSet);
755                    }
756            }
757    
758            /**
759             * {@inheritDoc}
760             */
761            public void assertValid(final Object validatedObject) throws IllegalArgumentException, ValidationFailedException,
762                            ConstraintsViolatedException
763            {
764                    final List<ConstraintViolation> violations = validate(validatedObject);
765    
766                    if (violations.size() > 0) throw translateException(new ConstraintsViolatedException(violations));
767            }
768    
769            /**
770             * {@inheritDoc}
771             */
772            public void assertValidFieldValue(final Object validatedObject, final Field validatedField,
773                            final Object fieldValueToValidate) throws IllegalArgumentException, ValidationFailedException,
774                            ConstraintsViolatedException
775            {
776                    final List<ConstraintViolation> violations = validateFieldValue(validatedObject, validatedField,
777                                    fieldValueToValidate);
778    
779                    if (violations.size() > 0) throw translateException(new ConstraintsViolatedException(violations));
780            }
781    
782            protected void checkConstraint(final List<ConstraintViolation> violations, final Check check,
783                            Object validatedObject, Object valueToValidate, OValContext context, final String[] profiles,
784                            final boolean isContainerValue) throws OValException
785            {
786                    if (!isAnyProfileEnabled(check.getProfiles(), profiles)) return;
787    
788                    if (!check.isActive(validatedObject, valueToValidate, this)) return;
789    
790                    final ConstraintTarget[] targets = check.getAppliesTo();
791    
792                    // only process the target expression if we are not already on a value inside the container object (collection, array, map)
793                    if (!isContainerValue)
794                    {
795                            String target = check.getTarget();
796                            if (target != null)
797                            {
798                                    target = target.trim();
799                                    if (target.length() > 0)
800                                    {
801                                            if (valueToValidate == null) return;
802                                            final String[] chunks = target.split(":", 2);
803                                            final String ognId, path;
804                                            if (chunks.length == 1)
805                                            {
806                                                    ognId = "";
807                                                    path = chunks[0];
808                                            }
809                                            else
810                                            {
811                                                    ognId = chunks[0];
812                                                    path = chunks[1];
813                                            }
814                                            final ObjectGraphNavigationResult result = ognRegistry.getObjectGraphNavigator(ognId) //
815                                                            .navigateTo(valueToValidate, path);
816                                            if (result == null) return;
817                                            validatedObject = result.targetParent;
818                                            valueToValidate = result.target;
819                                            if (result.targetAccessor instanceof Field)
820                                                    context = ContextCache.getFieldContext((Field) result.targetAccessor);
821                                            else
822                                                    context = ContextCache.getMethodReturnValueContext((Method) result.targetAccessor);
823                                    }
824                            }
825                    }
826    
827                    final Class< ? > compileTimeType = context.getCompileTimeType();
828    
829                    final boolean isCollection = valueToValidate != null ? //
830                                    valueToValidate instanceof Collection< ? > : //
831                                    Collection.class.isAssignableFrom(compileTimeType);
832                    final boolean isMap = !isCollection && //
833                                    (valueToValidate != null ? //
834                                                    valueToValidate instanceof Map< ? , ? > : //
835                                                    Map.class.isAssignableFrom(compileTimeType));
836                    final boolean isArray = !isCollection && !isMap && //
837                                    (valueToValidate != null ? // 
838                                                    valueToValidate.getClass().isArray() : //
839                                                    compileTimeType.isArray());
840                    final boolean isContainer = isCollection || isMap || isArray;
841    
842                    if (isContainer && valueToValidate != null)
843                            if (isCollection)
844                            {
845                                    if (ArrayUtils.containsSame(targets, ConstraintTarget.VALUES))
846                                            for (final Object item : (Collection< ? >) valueToValidate)
847                                                    checkConstraint(violations, check, validatedObject, item, context, profiles, true);
848                            }
849                            else if (isMap)
850                            {
851                                    if (ArrayUtils.containsSame(targets, ConstraintTarget.KEYS))
852                                            for (final Object item : ((Map< ? , ? >) valueToValidate).keySet())
853                                                    checkConstraint(violations, check, validatedObject, item, context, profiles, true);
854    
855                                    if (ArrayUtils.containsSame(targets, ConstraintTarget.VALUES))
856                                            for (final Object item : ((Map< ? , ? >) valueToValidate).values())
857                                                    checkConstraint(violations, check, validatedObject, item, context, profiles, true);
858                            }
859                            else if (ArrayUtils.containsSame(targets, ConstraintTarget.VALUES))
860                                    for (final Object item : ArrayUtils.asList(valueToValidate))
861                                            checkConstraint(violations, check, validatedObject, item, context, profiles, true);
862                    if (isContainerValue || !isContainer || isContainer
863                                    && ArrayUtils.containsSame(targets, ConstraintTarget.CONTAINER))
864                            _checkConstraint(violations, check, validatedObject, valueToValidate, context, profiles);
865            }
866    
867            protected void checkConstraintAssertConstraintSet(final List<ConstraintViolation> violations,
868                            final AssertConstraintSetCheck check, final Object validatedObject, final Object valueToValidate,
869                            final OValContext context, final String[] profiles) throws OValException
870            {
871                    final ConstraintSet cs = getConstraintSet(check.getId());
872    
873                    if (cs == null) throw new UndefinedConstraintSetException(check.getId());
874    
875                    final Collection<Check> referencedChecks = cs.getChecks();
876    
877                    if (referencedChecks != null && referencedChecks.size() > 0)
878                            for (final Check referencedCheck : referencedChecks)
879                                    checkConstraint(violations, referencedCheck, validatedObject, valueToValidate, context, profiles, false);
880            }
881    
882            protected void checkConstraintAssertFieldConstraints(final List<ConstraintViolation> violations,
883                            final AssertFieldConstraintsCheck check, final Object validatedObject, final Object valueToValidate,
884                            final OValContext context, final String[] profiles) throws OValException
885            {
886                    final Class< ? > targetClass;
887    
888                    /*
889                     * set the targetClass based on the validation context
890                     */
891                    if (check.getDeclaringClass() != null && check.getDeclaringClass() != Void.class)
892                            targetClass = check.getDeclaringClass();
893                    else if (context instanceof ConstructorParameterContext)
894                            // the class declaring the field must either be the class declaring the constructor or one of its super
895                            // classes
896                            targetClass = ((ConstructorParameterContext) context).getConstructor().getDeclaringClass();
897                    else if (context instanceof MethodParameterContext)
898                            // the class declaring the field must either be the class declaring the method or one of its super classes
899                            targetClass = ((MethodParameterContext) context).getMethod().getDeclaringClass();
900                    else if (context instanceof MethodReturnValueContext)
901                            // the class declaring the field must either be the class declaring the getter or one of its super classes
902                            targetClass = ((MethodReturnValueContext) context).getMethod().getDeclaringClass();
903                    else
904                            // the lowest class that is expected to declare the field (or one of its super classes)
905                            targetClass = validatedObject.getClass();
906    
907                    // the name of the field whose constraints shall be used
908                    String fieldName = check.getFieldName();
909    
910                    /*
911                     * calculate the field name based on the validation context if the @AssertFieldConstraints constraint didn't specify the field name
912                     */
913                    if (fieldName == null || fieldName.length() == 0)
914                            if (context instanceof ConstructorParameterContext)
915                                    fieldName = ((ConstructorParameterContext) context).getParameterName();
916                            else if (context instanceof MethodParameterContext)
917                                    fieldName = ((MethodParameterContext) context).getParameterName();
918                            else if (context instanceof MethodReturnValueContext)
919                                    fieldName = ReflectionUtils.guessFieldName(((MethodReturnValueContext) context).getMethod());
920    
921                    /*
922                     * find the field based on fieldName and targetClass
923                     */
924                    final Field field = ReflectionUtils.getFieldRecursive(targetClass, fieldName);
925    
926                    if (field == null)
927                            throw new FieldNotFoundException("Field <" + fieldName + "> not found in class <" + targetClass
928                                            + "> or its super classes.");
929    
930                    final ClassChecks cc = getClassChecks(field.getDeclaringClass());
931                    final Collection<Check> referencedChecks = cc.checksForFields.get(field);
932                    if (referencedChecks != null && referencedChecks.size() > 0)
933                            for (final Check referencedCheck : referencedChecks)
934                                    checkConstraint(violations, referencedCheck, validatedObject, valueToValidate, context, profiles, false);
935            }
936    
937            protected void checkConstraintAssertValid(final List<ConstraintViolation> violations, final AssertValidCheck check,
938                            final Object validatedObject, final Object valueToValidate, final OValContext context,
939                            final String[] profiles) throws OValException
940            {
941                    if (valueToValidate == null) return;
942    
943                    // ignore circular dependencies
944                    if (isCurrentlyValidated(valueToValidate)) return;
945    
946                    final List<ConstraintViolation> additionalViolations = collectionFactory.createList();
947                    validateInvariants(valueToValidate, additionalViolations, profiles);
948    
949                    if (additionalViolations.size() != 0)
950                    {
951                            final String errorMessage = renderMessage(context, valueToValidate, check.getMessage(),
952                                            check.getMessageVariables());
953    
954                            violations.add(new ConstraintViolation(check, errorMessage, validatedObject, valueToValidate, context,
955                                            additionalViolations));
956                    }
957            }
958    
959            /**
960             * Disables all constraints profiles globally, i.e. no configured constraint will be validated.
961             */
962            public synchronized void disableAllProfiles()
963            {
964                    isProfilesFeatureUsed = true;
965                    isAllProfilesEnabledByDefault = false;
966    
967                    enabledProfiles.clear();
968                    disabledProfiles.clear();
969            }
970    
971            /**
972             * Disables a constraints profile globally.
973             * @param profile the id of the profile
974             */
975            public void disableProfile(final String profile)
976            {
977                    isProfilesFeatureUsed = true;
978    
979                    if (isAllProfilesEnabledByDefault)
980                            disabledProfiles.add(profile);
981                    else
982                            enabledProfiles.remove(profile);
983            }
984    
985            /**
986             * Enables all constraints profiles globally, i.e. all configured constraint will be validated.
987             */
988            public synchronized void enableAllProfiles()
989            {
990                    isProfilesFeatureUsed = true;
991                    isAllProfilesEnabledByDefault = true;
992    
993                    enabledProfiles.clear();
994                    disabledProfiles.clear();
995            }
996    
997            /**
998             * Enables a constraints profile globally.
999             * @param profile the id of the profile
1000             */
1001            public void enableProfile(final String profile)
1002            {
1003                    isProfilesFeatureUsed = true;
1004    
1005                    if (isAllProfilesEnabledByDefault)
1006                            disabledProfiles.remove(profile);
1007                    else
1008                            enabledProfiles.add(profile);
1009            }
1010    
1011            /**
1012             * Gets the object-level constraint checks for the given class 
1013             *  
1014             * @param clazz the class to get the checks for
1015             * @throws IllegalArgumentException if <code>clazz == null</code> 
1016             */
1017            public Check[] getChecks(final Class< ? > clazz) throws IllegalArgumentException
1018            {
1019                    Assert.argumentNotNull("clazz", clazz);
1020    
1021                    final ClassChecks cc = getClassChecks(clazz);
1022    
1023                    final Set<Check> checks = cc.checksForObject;
1024                    return checks == null ? null : checks.toArray(new Check[checks.size()]);
1025            }
1026    
1027            /**
1028             * Gets the constraint checks for the given field 
1029             *  
1030             * @param field the field to get the checks for
1031             * @throws IllegalArgumentException if <code>field == null</code> 
1032             */
1033            public Check[] getChecks(final Field field) throws IllegalArgumentException
1034            {
1035                    Assert.argumentNotNull("field", field);
1036    
1037                    final ClassChecks cc = getClassChecks(field.getDeclaringClass());
1038    
1039                    final Set<Check> checks = cc.checksForFields.get(field);
1040                    return checks == null ? null : checks.toArray(new Check[checks.size()]);
1041            }
1042    
1043            /**
1044             * Gets the constraint checks for the given method's return value
1045             *  
1046             * @param method the method to get the checks for
1047             * @throws IllegalArgumentException if <code>getter == null</code>
1048             */
1049            public Check[] getChecks(final Method method) throws IllegalArgumentException
1050            {
1051                    Assert.argumentNotNull("method", method);
1052    
1053                    final ClassChecks cc = getClassChecks(method.getDeclaringClass());
1054    
1055                    final Set<Check> checks = cc.checksForMethodReturnValues.get(method);
1056                    return checks == null ? null : checks.toArray(new Check[checks.size()]);
1057            }
1058    
1059            /**
1060             * Returns the ClassChecks object for the particular class,
1061             * allowing you to modify the checks
1062             * 
1063             * @param clazz cannot be null
1064             * @return returns the ClassChecks for the given class
1065             * @throws IllegalArgumentException if <code>clazz == null</code>
1066             */
1067            protected ClassChecks getClassChecks(final Class< ? > clazz) throws IllegalArgumentException,
1068                            InvalidConfigurationException, ReflectionException
1069            {
1070                    Assert.argumentNotNull("clazz", clazz);
1071    
1072                    synchronized (checksByClass)
1073                    {
1074                            ClassChecks cc = checksByClass.get(clazz);
1075    
1076                            if (cc == null)
1077                            {
1078                                    cc = new ClassChecks(clazz, parameterNameResolver);
1079    
1080                                    for (final Configurer configurer : configurers)
1081                                    {
1082                                            final ClassConfiguration classConfig = configurer.getClassConfiguration(clazz);
1083                                            if (classConfig != null) _addChecks(cc, classConfig);
1084                                    }
1085    
1086                                    checksByClass.put(clazz, cc);
1087                            }
1088    
1089                            return cc;
1090                    }
1091            }
1092    
1093            /**
1094             * @return the internal linked set with the registered configurers
1095             */
1096            public List<Configurer> getConfigurers()
1097            {
1098                    return configurers;
1099            }
1100    
1101            /**
1102             * Returns the given constraint set.
1103             * 
1104             * @param constraintSetId the id of the constraint set to retrieve
1105             * @return the constraint set or null if not found
1106             * @throws InvalidConfigurationException
1107             * @throws IllegalArgumentException if <code>constraintSetId</code> is null
1108             */
1109            public ConstraintSet getConstraintSet(final String constraintSetId) throws InvalidConfigurationException,
1110                            IllegalArgumentException
1111            {
1112                    Assert.argumentNotNull("constraintSetsById", constraintSetsById);
1113                    synchronized (constraintSetsById)
1114                    {
1115                            ConstraintSet cs = constraintSetsById.get(constraintSetId);
1116    
1117                            if (cs == null) for (final Configurer configurer : configurers)
1118                            {
1119                                    final ConstraintSetConfiguration csc = configurer.getConstraintSetConfiguration(constraintSetId);
1120                                    if (csc != null)
1121                                    {
1122                                            cs = new ConstraintSet(csc.id);
1123                                            cs.setChecks(csc.checks);
1124    
1125                                            addConstraintSet(cs, csc.overwrite != null && csc.overwrite);
1126                                    }
1127                            }
1128                            return cs;
1129                    }
1130            }
1131    
1132            /**
1133             * @return the exceptionProcessor
1134             */
1135            public ExceptionTranslator getExceptionTranslator()
1136            {
1137                    return exceptionTranslator;
1138            }
1139    
1140            /**
1141             * @return the expressionLanguageRegistry
1142             */
1143            public ExpressionLanguageRegistry getExpressionLanguageRegistry()
1144            {
1145                    return expressionLanguageRegistry;
1146            }
1147    
1148            /**
1149             * @return the objectGraphNavigatorRegistry
1150             */
1151            public ObjectGraphNavigatorRegistry getObjectGraphNavigatorRegistry()
1152            {
1153                    return ognRegistry;
1154            }
1155    
1156            /**
1157             * Determines if at least one of the given profiles is enabled
1158             * 
1159             * @param profilesOfCheck
1160             * @param enabledProfiles optional array of profiles (can be null)
1161             * @return Returns true if at least one of the given profiles is enabled. 
1162             */
1163            protected boolean isAnyProfileEnabled(final String[] profilesOfCheck, final String[] enabledProfiles)
1164            {
1165                    if (enabledProfiles == null)
1166                    {
1167                            // use the global profile configuration
1168                            if (profilesOfCheck == null || profilesOfCheck.length == 0) return isProfileEnabled("default");
1169    
1170                            for (final String profile : profilesOfCheck)
1171                                    if (isProfileEnabled(profile)) return true;
1172                    }
1173                    else
1174                    {
1175                            // use the local profile configuration
1176                            if (profilesOfCheck == null || profilesOfCheck.length == 0)
1177                                    return ArrayUtils.containsEqual(enabledProfiles, "default");
1178    
1179                            for (final String profile : profilesOfCheck)
1180                                    if (ArrayUtils.containsEqual(enabledProfiles, profile)) return true;
1181                    }
1182                    return false;
1183            }
1184    
1185            /**
1186             * Determines if the given object is currently validated in the current thread
1187             * 
1188             * @param object
1189             * @return Returns true if the given object is currently validated in the current thread.
1190             */
1191            protected boolean isCurrentlyValidated(final Object object)
1192            {
1193                    Assert.argumentNotNull("object", object);
1194                    return currentlyValidatedObjects.get().getLast().contains(object);
1195            }
1196    
1197            /**
1198             * Determines if the given profile is enabled.
1199             * 
1200             * @param profileId
1201             * @return Returns true if the given profile is enabled.
1202             */
1203            public boolean isProfileEnabled(final String profileId)
1204            {
1205                    Assert.argumentNotNull("profileId", profileId);
1206                    if (isProfilesFeatureUsed)
1207                    {
1208                            if (isAllProfilesEnabledByDefault) return !disabledProfiles.contains(profileId);
1209    
1210                            return enabledProfiles.contains(profileId);
1211                    }
1212                    return true;
1213            }
1214    
1215            /**
1216             * clears the checks and constraint sets => a reconfiguration using the
1217             * currently registered configurers will automatically happen
1218             */
1219            public void reconfigureChecks()
1220            {
1221                    synchronized (checksByClass)
1222                    {
1223                            checksByClass.clear();
1224                    }
1225                    synchronized (constraintSetsById)
1226                    {
1227                            constraintSetsById.clear();
1228                    }
1229            }
1230    
1231            /**
1232             * Removes object-level constraint checks 
1233             *  
1234             * @param clazz
1235             * @param checks
1236             * @throws IllegalArgumentException if <code>clazz == null</code> or <code>checks == null</code> or checks is empty 
1237             */
1238            public void removeChecks(final Class< ? > clazz, final Check... checks) throws IllegalArgumentException
1239            {
1240                    Assert.argumentNotNull("clazz", clazz);
1241                    Assert.argumentNotEmpty("checks", checks);
1242    
1243                    getClassChecks(clazz).removeObjectChecks(checks);
1244            }
1245    
1246            /**
1247             * Removes constraint checks for the given field 
1248             *  
1249             * @param field
1250             * @param checks
1251             * @throws IllegalArgumentException if <code>field == null</code> or <code>checks == null</code> or checks is empty 
1252             */
1253            public void removeChecks(final Field field, final Check... checks) throws IllegalArgumentException
1254            {
1255                    Assert.argumentNotNull("field", field);
1256                    Assert.argumentNotEmpty("checks", checks);
1257    
1258                    getClassChecks(field.getDeclaringClass()).removeFieldChecks(field, checks);
1259            }
1260    
1261            /**
1262             * Removes constraint checks for the given getter's return value
1263             * 
1264             * @param getter a JavaBean Getter style method
1265             * @param checks
1266             * @throws IllegalArgumentException if <code>getter == null</code> or <code>checks == null</code>
1267             */
1268            public void removeChecks(final Method getter, final Check... checks) throws IllegalArgumentException
1269            {
1270                    Assert.argumentNotNull("getter", getter);
1271                    Assert.argumentNotEmpty("checks", checks);
1272    
1273                    getClassChecks(getter.getDeclaringClass()).removeMethodReturnValueChecks(getter, checks);
1274            }
1275    
1276            /**
1277             * Removes the constraint set with the given id
1278             * @param id the id of the constraint set to remove, cannot be null
1279             * @return the removed constraint set
1280             * @throws IllegalArgumentException if <code>id == null</code>
1281             */
1282            public ConstraintSet removeConstraintSet(final String id) throws IllegalArgumentException
1283            {
1284                    Assert.argumentNotNull("id", id);
1285    
1286                    synchronized (constraintSetsById)
1287                    {
1288                            return constraintSetsById.remove(id);
1289                    }
1290            }
1291    
1292            protected String renderMessage(final OValContext context, final Object value, final String messageKey,
1293                            final Map<String, ? > messageValues)
1294            {
1295                    String message = MessageRenderer.renderMessage(messageKey, messageValues);
1296    
1297                    // if there are no place holders in the message simply return it
1298                    if (message.indexOf('{') == -1) return message;
1299    
1300                    message = StringUtils.replaceAll(message, "{context}", contextRenderer.render(context));
1301                    message = StringUtils.replaceAll(message, "{invalidValue}", messageValueFormatter.format(value));
1302    
1303                    return message;
1304            }
1305    
1306            /**
1307             * Reports an additional constraint violation for the current validation cycle.
1308             * This method is intended to be executed by check implementations only.
1309             * @param constraintViolation the constraint violation
1310             */
1311            public void reportConstraintViolation(final ConstraintViolation constraintViolation)
1312            {
1313                    Assert.argumentNotNull("constraintViolation", constraintViolation);
1314                    if (currentViolations.get().size() == 0)
1315                            throw new IllegalStateException("No active validation cycle found for the current thread.");
1316                    currentViolations.get().getLast().add(constraintViolation);
1317            }
1318    
1319            /**
1320             * @param exceptionTranslator the exceptionTranslator to set
1321             */
1322            public void setExceptionTranslator(final ExceptionTranslator exceptionTranslator)
1323            {
1324                    this.exceptionTranslator = exceptionTranslator;
1325            }
1326    
1327            protected RuntimeException translateException(final OValException ex)
1328            {
1329                    if (exceptionTranslator != null)
1330                    {
1331                            final RuntimeException rex = exceptionTranslator.translateException(ex);
1332                            if (rex != null) return rex;
1333                    }
1334                    return ex;
1335            }
1336    
1337            /**
1338             * {@inheritDoc}
1339             */
1340            public List<ConstraintViolation> validate(final Object validatedObject) throws IllegalArgumentException,
1341                            ValidationFailedException
1342            {
1343                    Assert.argumentNotNull("validatedObject", validatedObject);
1344    
1345                    // create required objects for this validation cycle
1346                    final List<ConstraintViolation> violations = collectionFactory.createList();
1347                    currentViolations.get().add(violations);
1348                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1349    
1350                    try
1351                    {
1352                            validateInvariants(validatedObject, violations, (String[]) null);
1353                            return violations;
1354                    }
1355                    finally
1356                    {
1357                            // remove the validation cycle related objects
1358                            currentViolations.get().removeLast();
1359                            currentlyValidatedObjects.get().removeLast();
1360                    }
1361            }
1362    
1363            /**
1364             * {@inheritDoc}
1365             */
1366            public List<ConstraintViolation> validate(final Object validatedObject, final String... profiles)
1367                            throws IllegalArgumentException, ValidationFailedException
1368            {
1369                    Assert.argumentNotNull("validatedObject", validatedObject);
1370    
1371                    // create required objects for this validation cycle
1372                    final List<ConstraintViolation> violations = collectionFactory.createList();
1373                    currentViolations.get().add(violations);
1374                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1375    
1376                    try
1377                    {
1378                            validateInvariants(validatedObject, violations, profiles);
1379                            return violations;
1380                    }
1381                    finally
1382                    {
1383                            // remove the validation cycle related objects
1384                            currentViolations.get().removeLast();
1385                            currentlyValidatedObjects.get().removeLast();
1386                    }
1387            }
1388    
1389            /**
1390             * {@inheritDoc}
1391             */
1392            public List<ConstraintViolation> validateFieldValue(final Object validatedObject, final Field validatedField,
1393                            final Object fieldValueToValidate) throws IllegalArgumentException, ValidationFailedException
1394            {
1395                    Assert.argumentNotNull("validatedObject", validatedObject);
1396                    Assert.argumentNotNull("validatedField", validatedField);
1397    
1398                    // create required objects for this validation cycle
1399                    final List<ConstraintViolation> violations = collectionFactory.createList();
1400                    currentViolations.get().add(violations);
1401                    currentlyValidatedObjects.get().add(new IdentitySet<Object>(4));
1402    
1403                    try
1404                    {
1405                            final ClassChecks cc = getClassChecks(validatedField.getDeclaringClass());
1406                            final Collection<Check> checks = cc.checksForFields.get(validatedField);
1407    
1408                            if (checks == null || checks.size() == 0) return violations;
1409    
1410                            final FieldContext context = ContextCache.getFieldContext(validatedField);
1411    
1412                            for (final Check check : checks)
1413                                    checkConstraint(violations, check, validatedObject, fieldValueToValidate, context, null, false);
1414                            return violations;
1415                    }
1416                    catch (final OValException ex)
1417                    {
1418                            throw new ValidationFailedException("Field validation failed. Field: " + validatedField
1419                                            + " Validated object: " + validatedObject, ex);
1420                    }
1421                    finally
1422                    {
1423                            // remove the validation cycle related objects
1424                            currentViolations.get().removeLast();
1425                            currentlyValidatedObjects.get().removeLast();
1426                    }
1427            }
1428    
1429            /**
1430             * validates the field and getter constrains of the given object.
1431             * if the given object is a class the static fields and getters
1432             * are validated.
1433             * 
1434             * @param validatedObject the object to validate, cannot be null
1435             * @throws ValidationFailedException
1436             * @throws IllegalArgumentException if <code>validatedObject == null</code>
1437             */
1438            protected void validateInvariants(final Object validatedObject, final List<ConstraintViolation> violations,
1439                            final String[] profiles) throws IllegalArgumentException, ValidationFailedException
1440            {
1441                    Assert.argumentNotNull("validatedObject", validatedObject);
1442    
1443                    currentlyValidatedObjects.get().getLast().add(validatedObject);
1444                    if (validatedObject instanceof Class< ? >)
1445                            _validateStaticInvariants((Class< ? >) validatedObject, violations, profiles);
1446                    else
1447                            _validateObjectInvariants(validatedObject, validatedObject.getClass(), violations, profiles);
1448            }
1449    }