[hibernate-commits] Hibernate SVN: r16945 - in validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation: metadata and 1 other directory.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Wed Jun 24 10:45:17 EDT 2009


Author: hardy.ferentschik
Date: 2009-06-24 10:45:17 -0400 (Wed, 24 Jun 2009)
New Revision: 16945

Added:
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/GlobalExecutionContext.java
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/LocalExecutionContext.java
Removed:
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ExecutionContext.java
Modified:
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ConstraintTree.java
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/PathImpl.java
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/metadata/MetaConstraint.java
Log:
HV-177 While integrating the Path API also refactored the ExecutionContext approach and got rid of the stack type algorithm.

Modified: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ConstraintTree.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ConstraintTree.java	2009-06-24 14:43:50 UTC (rev 16944)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ConstraintTree.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -95,38 +95,37 @@
 		return descriptor;
 	}
 
-	/**
-	 * Validates the specified value.
-	 *
-	 * @param value The value to validate
-	 * @param type The type of the value determined from the type the annotation was placed on.
-	 * @param executionContext The current execution context.
-	 * @param constraintViolations List of constraint violation into which to accumulate all constraint violation as we traverse
-	 * this <code>ConstraintTree </code>.
-	 * @param <T> Type of the root bean for the current validation.
-	 * @param <V> Type of the value to be validated.
-	 */
-	public <T, V> void validateConstraints(V value, Type type, ExecutionContext<T> executionContext, List<ConstraintViolation<T>> constraintViolations) {
+	public <T, U, V> void validateConstraints(Type type, GlobalExecutionContext<T> executionContext, LocalExecutionContext<U, V> localExecutionContext, List<ConstraintViolation<T>> constraintViolations) {
 		// first validate composing constraints
 		for ( ConstraintTree<?> tree : getChildren() ) {
-			tree.validateConstraints( value, type, executionContext, constraintViolations );
+			tree.validateConstraints( type, executionContext, localExecutionContext, constraintViolations );
 		}
 
 		ConstraintValidatorContextImpl constraintValidatorContext = new ConstraintValidatorContextImpl(
-				executionContext.peekPropertyPath(), descriptor
+				localExecutionContext.getPropertyPath(), descriptor
 		);
 
 		// we could have a composing constraint which does not need its own validator.
 		if ( !descriptor.getConstraintValidatorClasses().isEmpty() ) {
 			if ( log.isTraceEnabled() ) {
-				log.trace( "Validating value {} against constraint defined by {}", value, descriptor );
+				log.trace(
+						"Validating value {} against constraint defined by {}",
+						localExecutionContext.getCurrentValidatedValue(),
+						descriptor
+				);
 			}
 			ConstraintValidator<A, V> validator = getInitalizedValidator(
-					value, type, executionContext.getConstraintValidatorFactory()
+					localExecutionContext.getCurrentValidatedValue(),
+					type,
+					executionContext.getConstraintValidatorFactory()
 			);
 
 			validateSingleConstraint(
-					value, executionContext, constraintViolations, constraintValidatorContext, validator
+					executionContext,
+					localExecutionContext,
+					constraintViolations,
+					constraintValidatorContext,
+					validator
 			);
 		}
 
@@ -134,16 +133,20 @@
 			constraintViolations.clear();
 			final String message = ( String ) getParent().getDescriptor().getAttributes().get( "message" );
 			ConstraintValidatorContextImpl.ErrorMessage error = constraintValidatorContext.new ErrorMessage(
-					message, executionContext.peekPropertyPath()
+					message, localExecutionContext.getPropertyPath()
 			);
-			constraintViolations.add( executionContext.createConstraintViolation( value, error, descriptor ) );
+			constraintViolations.add(
+					executionContext.createConstraintViolation(
+							localExecutionContext, error, descriptor
+					)
+			);
 		}
 	}
 
-	private <T, V> void validateSingleConstraint(V value, ExecutionContext<T> executionContext, List<ConstraintViolation<T>> constraintViolations, ConstraintValidatorContextImpl constraintValidatorContext, ConstraintValidator<A, V> validator) {
+	private <T, U, V> void validateSingleConstraint(GlobalExecutionContext<T> executionContext, LocalExecutionContext<U, V> localExecutionContext, List<ConstraintViolation<T>> constraintViolations, ConstraintValidatorContextImpl constraintValidatorContext, ConstraintValidator<A, V> validator) {
 		boolean isValid;
 		try {
-			isValid = validator.isValid( value, constraintValidatorContext );
+			isValid = validator.isValid( localExecutionContext.getCurrentValidatedValue(), constraintValidatorContext );
 		}
 		catch ( RuntimeException e ) {
 			throw new ValidationException( "Unexpected exception during isValid call", e );
@@ -151,7 +154,7 @@
 		if ( !isValid ) {
 			constraintViolations.addAll(
 					executionContext.createConstraintViolations(
-							value, constraintValidatorContext
+							localExecutionContext, constraintValidatorContext
 					)
 			);
 		}

Deleted: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ExecutionContext.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ExecutionContext.java	2009-06-24 14:43:50 UTC (rev 16944)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ExecutionContext.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -1,374 +0,0 @@
-// $Id$
-/*
-* JBoss, Home of Professional Open Source
-* Copyright 2008, Red Hat Middleware LLC, and individual contributors
-* by the @authors tag. See the copyright.txt in the distribution for a
-* full listing of individual contributors.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-* http://www.apache.org/licenses/LICENSE-2.0
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,  
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-package org.hibernate.validation.engine;
-
-import java.lang.annotation.ElementType;
-import java.lang.reflect.Field;
-import java.lang.reflect.Member;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Stack;
-import javax.validation.ConstraintValidatorFactory;
-import javax.validation.ConstraintViolation;
-import javax.validation.MessageInterpolator;
-import javax.validation.TraversableResolver;
-import javax.validation.Path;
-import javax.validation.metadata.ConstraintDescriptor;
-
-import org.hibernate.validation.metadata.MetaConstraint;
-import org.hibernate.validation.util.IdentitySet;
-
-/**
- * Context object keeping track of all processed objects and failing constraints.
- * It also keeps track of the currently validated object, group and property path.
- *
- * @author Hardy Ferentschik
- * @author Emmanuel Bernard
- * @todo Look for ways to improve this data structure. It is quite fragile and depends on the right oder of calls
- * in order to work.
- */
-public class ExecutionContext<T> {
-
-	/**
-	 * The root bean of the validation.
-	 */
-	private final T rootBean;
-
-	/**
-	 * The root bean class of the validation.
-	 */
-	private Class<T> rootBeanClass;
-
-	/**
-	 * Maps a group to an identity set to keep track of already validated objects. We have to make sure
-	 * that each object gets only validated once per group and property path.
-	 */
-	private final Map<Class<?>, IdentitySet> processedObjects;
-
-	/**
-	 * Maps an object to a list of paths in which it has been invalidated.
-	 */
-	private final Map<Object, Set<PathImpl>> processedPaths;
-
-	/**
-	 * A list of all failing constraints so far.
-	 */
-	private final List<ConstraintViolation<T>> failingConstraintViolations;
-
-	/**
-	 * The current property path we are validating.
-	 */
-	private PathImpl propertyPath;
-
-	/**
-	 * The current group we are validating.
-	 */
-	private Class<?> currentGroup;
-
-	/**
-	 * Stack for keeping track of the currently validated bean.
-	 */
-	private Stack<Object> beanStack = new Stack<Object>();
-
-	/**
-	 * Flag indicating whether an object can only be validated once per group or once per group AND validation path.
-	 *
-	 * @todo Make this boolean a configurable item.
-	 */
-	private boolean allowOneValidationPerPath = true;
-
-	/**
-	 * The message resolver which should be used in this context.
-	 */
-	private final MessageInterpolator messageInterpolator;
-
-	/**
-	 * The constraint factory which should be used in this context.
-	 */
-	private final ConstraintValidatorFactory constraintValidatorFactory;
-
-	/**
-	 * Allows a JPA provider to decide whether a property should be validated.
-	 */
-	private final TraversableResolver traversableResolver;
-
-	public static <T> ExecutionContext<T> getContextForValidate(T object, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
-		@SuppressWarnings("unchecked")
-		Class<T> rootBeanClass = ( Class<T> ) object.getClass();
-		return new ExecutionContext<T>(
-				rootBeanClass, object, object, messageInterpolator, constraintValidatorFactory, traversableResolver
-		);
-	}
-
-	public static <T> ExecutionContext<T> getContextForValidateValue(Class<T> rootBeanClass, Object object, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
-		return new ExecutionContext<T>(
-				rootBeanClass,
-				null,
-				object,
-				messageInterpolator,
-				constraintValidatorFactory,
-				traversableResolver
-		);
-	}
-
-	public static <T> ExecutionContext<T> getContextForValidateProperty(T rootBean, Object object, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
-		@SuppressWarnings("unchecked")
-		Class<T> rootBeanClass = ( Class<T> ) rootBean.getClass();
-		return new ExecutionContext<T>(
-				rootBeanClass, rootBean, object, messageInterpolator, constraintValidatorFactory, traversableResolver
-		);
-	}
-
-	private ExecutionContext(Class<T> rootBeanClass, T rootBean, Object object, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
-		this.rootBean = rootBean;
-		this.rootBeanClass = rootBeanClass;
-		this.messageInterpolator = messageInterpolator;
-		this.constraintValidatorFactory = constraintValidatorFactory;
-		this.traversableResolver = traversableResolver;
-
-		beanStack.push( object );
-		processedObjects = new HashMap<Class<?>, IdentitySet>();
-		processedPaths = new IdentityHashMap<Object, Set<PathImpl>>();
-		propertyPath = PathImpl.createNewRootPath();
-		failingConstraintViolations = new ArrayList<ConstraintViolation<T>>();
-	}
-
-	public ConstraintValidatorFactory getConstraintValidatorFactory() {
-		return constraintValidatorFactory;
-	}
-
-	public Object peekCurrentBean() {
-		return beanStack.peek();
-	}
-
-	public Class<?> peekCurrentBeanType() {
-		return beanStack.peek().getClass();
-	}
-
-	public void pushCurrentBean(Object validatedBean) {
-		beanStack.push( validatedBean );
-	}
-
-	public void popCurrentBean() {
-		beanStack.pop();
-	}
-
-	public T getRootBean() {
-		return rootBean;
-	}
-
-	public Class<T> getRootBeanClass() {
-		return rootBeanClass;
-	}
-
-	public Class<?> getCurrentGroup() {
-		return currentGroup;
-	}
-
-	public void setCurrentGroup(Class<?> currentGroup) {
-		this.currentGroup = currentGroup;
-		markProcessed();
-	}
-
-	/**
-	 * Returns <code>true</code> if the specified value has already been validated, <code>false</code> otherwise.
-	 * Each object can only be validated once per group and validation path. The flag {@link #allowOneValidationPerPath}
-	 * determines whether an object can only be validated once per group or once per group and validation path.Ê
-	 *
-	 * @param value The value to be validated.
-	 *
-	 * @return Returns <code>true</code> if the specified value has already been validated, <code>false</code> otherwise.
-	 */
-	public boolean isAlreadyValidated(Object value) {
-		boolean alreadyValidated;
-		alreadyValidated = isAlreadyValidatedForCurrentGroup( value );
-
-		if ( alreadyValidated && allowOneValidationPerPath ) {
-			alreadyValidated = isAlreadyValidatedForPath( value );
-		}
-		return alreadyValidated;
-	}
-
-	public void addConstraintFailures(List<ConstraintViolation<T>> failingConstraintViolations) {
-		for ( ConstraintViolation<T> violation : failingConstraintViolations ) {
-			addConstraintFailure( violation );
-		}
-	}
-
-	public List<ConstraintViolation<T>> getFailingConstraints() {
-		return failingConstraintViolations;
-	}
-
-	/**
-	 * Adds a new level to the current property path of this context.
-	 *
-	 * @param property the new property to add to the current path.
-	 */
-	public void pushProperty(String property) {
-		propertyPath.addNode( new NodeImpl( property ) );
-	}
-
-	public void setProperty(PathImpl path) {
-		propertyPath = path;
-	}
-
-	/**
-	 * Drops the last level of the current property path of this context.
-	 */
-	public void popProperty() {
-		propertyPath.removeLeafNode();
-	}
-
-	public void markCurrentPropertyAsIterable() {
-		( ( NodeImpl ) propertyPath.getLeafNode() ).setInIterable( true );
-	}
-
-	public void setPropertyIndex(String index) {
-		( ( NodeImpl ) propertyPath.getLeafNode() ).setIndex( new Integer( index ) );
-	}
-
-	public PathImpl peekPropertyPath() {
-		return new PathImpl( propertyPath );
-	}
-
-	@SuppressWarnings("SimplifiableIfStatement")
-	public boolean isValidationRequired(MetaConstraint metaConstraint) {
-		if ( !metaConstraint.getGroupList().contains( currentGroup ) ) {
-			return false;
-		}
-
-		return traversableResolver.isReachable(
-				peekCurrentBean(),
-				propertyPath.getLeafNode(),
-				getRootBeanClass(),
-				propertyPath.getPathWithoutLeafNode(),
-				metaConstraint.getElementType()
-		);
-	}
-
-	public boolean isCascadeRequired(Member member) {
-		final ElementType type = member instanceof Field ? ElementType.FIELD : ElementType.METHOD;
-		final Class<T> rootBeanType = getRootBeanClass();
-		final Object traversableobject = peekCurrentBean();
-		return traversableResolver.isReachable(
-				traversableobject,
-				propertyPath.getLeafNode(),
-				rootBeanType,
-				propertyPath.getPathWithoutLeafNode(),
-				type
-		)
-				&& traversableResolver.isCascadable(
-				traversableobject,
-				propertyPath.getLeafNode(),
-				rootBeanType,
-				propertyPath.getPathWithoutLeafNode(),
-				type
-		);
-	}
-
-	public List<ConstraintViolationImpl<T>> createConstraintViolations(Object value, ConstraintValidatorContextImpl constraintValidatorContext) {
-		List<ConstraintViolationImpl<T>> constraintViolations = new ArrayList<ConstraintViolationImpl<T>>();
-		for ( ConstraintValidatorContextImpl.ErrorMessage error : constraintValidatorContext.getErrorMessages() ) {
-			ConstraintViolationImpl<T> violation = createConstraintViolation(
-					value, error, constraintValidatorContext.getConstraintDescriptor()
-			);
-			constraintViolations.add( violation );
-		}
-		return constraintViolations;
-	}
-
-	public ConstraintViolationImpl<T> createConstraintViolation(Object value, ConstraintValidatorContextImpl.ErrorMessage error, ConstraintDescriptor<?> descriptor) {
-		String messageTemplate = error.getMessage();
-		String interpolatedMessage = messageInterpolator.interpolate(
-				messageTemplate,
-				new MessageInterpolatorContext( descriptor, peekCurrentBean() )
-		);
-		return new ConstraintViolationImpl<T>(
-				messageTemplate,
-				interpolatedMessage,
-				getRootBeanClass(),
-				getRootBean(),
-				peekCurrentBean(),
-				value,
-				error.getPath(),
-				descriptor
-		);
-	}
-
-	private boolean isAlreadyValidatedForPath(Object value) {
-		Set<PathImpl> pathSet = processedPaths.get( value );
-		if ( pathSet == null ) {
-			return false;
-		}
-
-		for ( PathImpl p : pathSet ) {
-			if ( p.isSubPathOf( peekPropertyPath() ) || peekPropertyPath().isSubPathOf( p ) ) {
-				return true;
-			}
-		}
-
-		return false;
-	}
-
-	private boolean isAlreadyValidatedForCurrentGroup(Object value) {
-		final IdentitySet objectsProcessedInCurrentGroups = processedObjects.get( currentGroup );
-		return objectsProcessedInCurrentGroups != null && objectsProcessedInCurrentGroups.contains( value );
-	}
-
-	private void markProcessed() {
-		markProcessForCurrentGroup();
-		if ( allowOneValidationPerPath ) {
-			markProcessedForCurrentPath();
-		}
-	}
-
-	private void markProcessedForCurrentPath() {
-		if ( processedPaths.containsKey( peekCurrentBean() ) ) {
-			processedPaths.get( peekCurrentBean() ).add( peekPropertyPath() );
-		}
-		else {
-			Set<PathImpl> set = new HashSet<PathImpl>();
-			set.add( peekPropertyPath() );
-			processedPaths.put( peekCurrentBean(), set );
-		}
-	}
-
-
-	private void markProcessForCurrentGroup() {
-		if ( processedObjects.containsKey( currentGroup ) ) {
-			processedObjects.get( currentGroup ).add( peekCurrentBean() );
-		}
-		else {
-			IdentitySet set = new IdentitySet();
-			set.add( peekCurrentBean() );
-			processedObjects.put( currentGroup, set );
-		}
-	}
-
-	private void addConstraintFailure(ConstraintViolation<T> failingConstraintViolation) {
-		int i = failingConstraintViolations.indexOf( failingConstraintViolation );
-		if ( i == -1 ) {
-			failingConstraintViolations.add( failingConstraintViolation );
-		}
-	}
-}
\ No newline at end of file

Copied: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/GlobalExecutionContext.java (from rev 16905, validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ExecutionContext.java)
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/GlobalExecutionContext.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/GlobalExecutionContext.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -0,0 +1,257 @@
+// $Id$
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2008, Red Hat Middleware LLC, and individual contributors
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+* http://www.apache.org/licenses/LICENSE-2.0
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,  
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.hibernate.validation.engine;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.validation.ConstraintValidatorFactory;
+import javax.validation.ConstraintViolation;
+import javax.validation.MessageInterpolator;
+import javax.validation.TraversableResolver;
+import javax.validation.metadata.ConstraintDescriptor;
+
+import org.hibernate.validation.util.IdentitySet;
+
+/**
+ * Context object keeping track of all important data for a top level {@link javax.validation.Validator#validate(Object, Class[])} },
+ * {@link javax.validation.Validator#validateValue(Class, String, Object, Class[])}  } or {@link javax.validation.Validator#validateProperty(Object, String, Class[])}  call.
+ *
+ * we use this object to collect all failing constraints, but also to cache the caching traversable resolver for a full stack call.
+ *
+ * @author Hardy Ferentschik
+ * @author Emmanuel Bernard
+ */
+public class GlobalExecutionContext<T> {
+
+	/**
+	 * The root bean of the validation.
+	 */
+	private final T rootBean;
+
+	/**
+	 * The root bean class of the validation.
+	 */
+	private final Class<T> rootBeanClass;
+
+	/**
+	 * Maps a group to an identity set to keep track of already validated objects. We have to make sure
+	 * that each object gets only validated once per group and property path.
+	 */
+	private final Map<Class<?>, IdentitySet> processedObjects;
+
+	/**
+	 * Maps an object to a list of paths in which it has been invalidated.
+	 */
+	private final Map<Object, Set<PathImpl>> processedPaths;
+
+	/**
+	 * A list of all failing constraints so far.
+	 */
+	private final List<ConstraintViolation<T>> failingConstraintViolations;
+
+	/**
+	 * Flag indicating whether an object can only be validated once per group or once per group AND validation path.
+	 *
+	 * @todo Make this boolean a configurable item.
+	 */
+	private boolean allowOneValidationPerPath = true;
+
+	/**
+	 * The message resolver which should be used in this context.
+	 */
+	private final MessageInterpolator messageInterpolator;
+
+	/**
+	 * The constraint factory which should be used in this context.
+	 */
+	private final ConstraintValidatorFactory constraintValidatorFactory;
+
+	/**
+	 * Allows a JPA provider to decide whether a property should be validated.
+	 */
+	private final TraversableResolver traversableResolver;
+
+	public static <T> GlobalExecutionContext<T> getContextForValidate(T object, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
+		@SuppressWarnings("unchecked")
+		Class<T> rootBeanClass = ( Class<T> ) object.getClass();
+		return new GlobalExecutionContext<T>(
+				rootBeanClass, object, messageInterpolator, constraintValidatorFactory, traversableResolver
+		);
+	}
+
+	public static <T> GlobalExecutionContext<T> getContextForValidateProperty(T rootBean, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
+		@SuppressWarnings("unchecked")
+		Class<T> rootBeanClass = ( Class<T> ) rootBean.getClass();
+		return new GlobalExecutionContext<T>(
+				rootBeanClass, rootBean, messageInterpolator, constraintValidatorFactory, traversableResolver
+		);
+	}
+
+	public static <T> GlobalExecutionContext<T> getContextForValidateValue(Class<T> rootBeanClass, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
+		return new GlobalExecutionContext<T>(
+				rootBeanClass,
+				null,
+				messageInterpolator,
+				constraintValidatorFactory,
+				traversableResolver
+		);
+	}
+
+	private GlobalExecutionContext(Class<T> rootBeanClass, T rootBean, MessageInterpolator messageInterpolator, ConstraintValidatorFactory constraintValidatorFactory, TraversableResolver traversableResolver) {
+		this.rootBean = rootBean;
+		this.rootBeanClass = rootBeanClass;
+		this.messageInterpolator = messageInterpolator;
+		this.constraintValidatorFactory = constraintValidatorFactory;
+		this.traversableResolver = traversableResolver;
+
+		processedObjects = new HashMap<Class<?>, IdentitySet>();
+		processedPaths = new IdentityHashMap<Object, Set<PathImpl>>();
+		failingConstraintViolations = new ArrayList<ConstraintViolation<T>>();
+	}
+
+	public T getRootBean() {
+		return rootBean;
+	}
+
+	public Class<T> getRootBeanClass() {
+		return rootBeanClass;
+	}
+
+	public TraversableResolver getTraversableResolver() {
+		return traversableResolver;
+	}
+
+	public MessageInterpolator getMessageInterpolator() {
+		return messageInterpolator;
+	}
+
+	public <U, V> ConstraintViolationImpl<T> createConstraintViolation(LocalExecutionContext<U, V> localContext, ConstraintValidatorContextImpl.ErrorMessage error, ConstraintDescriptor<?> descriptor) {
+		String messageTemplate = error.getMessage();
+		String interpolatedMessage = messageInterpolator.interpolate(
+				messageTemplate,
+				new MessageInterpolatorContext( descriptor, localContext.getCurrentBean() )
+		);
+		return new ConstraintViolationImpl<T>(
+				messageTemplate,
+				interpolatedMessage,
+				getRootBeanClass(),
+				getRootBean(),
+				localContext.getCurrentBean(),
+				localContext.getCurrentValidatedValue(),
+				error.getPath(),
+				descriptor
+		);
+	}
+
+	public <U, V> List<ConstraintViolationImpl<T>> createConstraintViolations(LocalExecutionContext<U, V> localContext, ConstraintValidatorContextImpl constraintValidatorContext) {
+		List<ConstraintViolationImpl<T>> constraintViolations = new ArrayList<ConstraintViolationImpl<T>>();
+		for ( ConstraintValidatorContextImpl.ErrorMessage error : constraintValidatorContext.getErrorMessages() ) {
+			ConstraintViolationImpl<T> violation = createConstraintViolation(
+					localContext, error, constraintValidatorContext.getConstraintDescriptor()
+			);
+			constraintViolations.add( violation );
+		}
+		return constraintViolations;
+	}
+
+	public ConstraintValidatorFactory getConstraintValidatorFactory() {
+		return constraintValidatorFactory;
+	}
+
+	public boolean isAlreadyValidated(Object value, Class<?> group, PathImpl path) {
+		boolean alreadyValidated;
+		alreadyValidated = isAlreadyValidatedForCurrentGroup( value, group );
+
+		if ( alreadyValidated && allowOneValidationPerPath ) {
+			alreadyValidated = isAlreadyValidatedForPath( value, path );
+		}
+		return alreadyValidated;
+	}
+
+	public void markProcessed(Object value, Class<?> group, PathImpl path) {
+		markProcessForCurrentGroup( value, group );
+		if ( allowOneValidationPerPath ) {
+			markProcessedForCurrentPath( value, path );
+		}
+	}
+
+	private void addConstraintFailure(ConstraintViolation<T> failingConstraintViolation) {
+		int i = failingConstraintViolations.indexOf( failingConstraintViolation );
+		if ( i == -1 ) {
+			failingConstraintViolations.add( failingConstraintViolation );
+		}
+	}
+
+	public void addConstraintFailures(List<ConstraintViolation<T>> failingConstraintViolations) {
+		for ( ConstraintViolation<T> violation : failingConstraintViolations ) {
+			addConstraintFailure( violation );
+		}
+	}
+
+	public List<ConstraintViolation<T>> getFailingConstraints() {
+		return failingConstraintViolations;
+	}
+
+	private boolean isAlreadyValidatedForPath(Object value, PathImpl path) {
+		Set<PathImpl> pathSet = processedPaths.get( value );
+		if ( pathSet == null ) {
+			return false;
+		}
+
+		for ( PathImpl p : pathSet ) {
+			if ( p.isSubPathOf( path ) || path.isSubPathOf( p ) ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	private boolean isAlreadyValidatedForCurrentGroup(Object value, Class<?> group) {
+		final IdentitySet objectsProcessedInCurrentGroups = processedObjects.get( group );
+		return objectsProcessedInCurrentGroups != null && objectsProcessedInCurrentGroups.contains( value );
+	}
+
+	private void markProcessedForCurrentPath(Object value, PathImpl path) {
+		if ( processedPaths.containsKey( value ) ) {
+			processedPaths.get( value ).add( path.getPathWithoutLeafNode() );
+		}
+		else {
+			Set<PathImpl> set = new HashSet<PathImpl>();
+			set.add( path.getPathWithoutLeafNode() );
+			processedPaths.put( value, set );
+		}
+	}
+
+
+	private void markProcessForCurrentGroup(Object value, Class<?> group) {
+		if ( processedObjects.containsKey( group ) ) {
+			processedObjects.get( group ).add( value );
+		}
+		else {
+			IdentitySet set = new IdentitySet();
+			set.add( value );
+			processedObjects.put( group, set );
+		}
+	}
+}
\ No newline at end of file

Added: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/LocalExecutionContext.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/LocalExecutionContext.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/LocalExecutionContext.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -0,0 +1,113 @@
+// $Id:$
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2008, Red Hat Middleware LLC, and individual contributors
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+* http://www.apache.org/licenses/LICENSE-2.0
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.hibernate.validation.engine;
+
+/**
+ * An instance of this class is used to collect all the relevant information for validating a single entity/bean.
+ *
+ * @author Hardy Ferentschik
+ */
+public class LocalExecutionContext<T,V> {
+
+	/**
+	 * The current bean which gets validated. This is the bean hosting the constraints which get validated.
+	 */
+	private final T currentBean;
+
+	/**
+	 * The class of the current bean.
+	 */
+	private final Class<T> currentBeanType;
+
+	/**
+	 * The current property path we are validating.
+	 */
+	private PathImpl propertyPath;
+
+	/**
+	 * The current group we are validating.
+	 */
+	private Class<?> currentGroup;
+
+	/**
+	 * The value which gets currently evaluated.
+	 */
+	private V currentValue;
+
+	public static <T,V> LocalExecutionContext<T,V> getLocalExecutionContext(T value) {
+		@SuppressWarnings("unchecked")
+		Class<T> rootBeanClass = ( Class<T> ) value.getClass();
+		return new LocalExecutionContext<T,V>( value, rootBeanClass );
+	}
+
+	public static <T,V> LocalExecutionContext<T,V> getLocalExecutionContext(Class<T> type) {
+		return new LocalExecutionContext<T,V>( null, type );
+	}
+
+	public LocalExecutionContext(T currentBean, Class<T> currentBeanType) {
+		this.currentBean = currentBean;
+		this.currentBeanType = currentBeanType;
+	}
+
+	public PathImpl getPropertyPath() {
+		return propertyPath;
+	}
+
+	public Class<?> getCurrentGroup() {
+		return currentGroup;
+	}
+
+	public T getCurrentBean() {
+		return currentBean;
+	}
+
+	public Class<T> getCurrentBeanType() {
+		return currentBeanType;
+	}
+
+	public V getCurrentValidatedValue() {
+		return currentValue;
+	}
+
+	public void setPropertyPath(PathImpl propertyPath) {
+		this.propertyPath = propertyPath;
+	}
+
+	public void setCurrentGroup(Class<?> currentGroup) {
+		this.currentGroup = currentGroup;
+	}
+
+	public void setCurrentValidatedValue(V currentValue) {
+		this.currentValue = currentValue;
+	}
+
+	public void markCurrentPropertyAsIterable() {
+		propertyPath.getLeafNode().setInIterable( true );
+	}
+
+	@Override
+	public String toString() {
+		return "LocalExecutionContext{" +
+				"currentBean=" + currentBean +
+				", currentBeanType=" + currentBeanType +
+				", propertyPath=" + propertyPath +
+				", currentGroup=" + currentGroup +
+				", currentValue=" + currentValue +
+				'}';
+	}
+}

Modified: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/PathImpl.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/PathImpl.java	2009-06-24 14:43:50 UTC (rev 16944)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/PathImpl.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -67,7 +67,11 @@
 		return new PathImpl();
 	}
 
-	public PathImpl(PathImpl path) {
+	public static PathImpl createShallowCopy(PathImpl path) {
+		return new PathImpl(path);
+	}
+
+	private PathImpl(PathImpl path) {
 		this.nodeList = new ArrayList<Node>();
 		Iterator<Node> iter = path.iterator();
 		while ( iter.hasNext() ) {
@@ -87,7 +91,7 @@
 		}
 	}
 
-	public Path getPathWithoutLeafNode() {
+	public PathImpl getPathWithoutLeafNode() {
 		List<Node> nodes = new ArrayList<Node>( nodeList );
 		if ( nodes.size() > 1 ) {
 			nodes.remove( nodes.size() - 1 );
@@ -154,6 +158,7 @@
 	}
 
 	@Override
+	@SuppressWarnings( "SimplifiableIfStatement")
 	public boolean equals(Object o) {
 		if ( this == o ) {
 			return true;

Modified: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java	2009-06-24 14:43:50 UTC (rev 16944)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -18,6 +18,8 @@
 package org.hibernate.validation.engine;
 
 import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
@@ -58,23 +60,11 @@
  *
  * @author Emmanuel Bernard
  * @author Hardy Ferentschik
- * @todo Make all properties transient for serializability.
  */
 public class ValidatorImpl implements Validator {
 	private static final Logger log = LoggerFactory.make();
 
 	/**
-	 * Set of classes which can be used as index in a map.
-	 */
-	private static final Set<Class<?>> VALID_MAP_INDEX_CLASSES = new HashSet<Class<?>>();
-
-	static {
-		VALID_MAP_INDEX_CLASSES.add( Integer.class );
-		VALID_MAP_INDEX_CLASSES.add( Long.class );
-		VALID_MAP_INDEX_CLASSES.add( String.class );
-	}
-
-	/**
 	 * The default group array used in case any of the validate methods is called without a group.
 	 */
 	private static final Class<?>[] DEFAULT_GROUP_ARRAY = new Class<?>[] { Default.class };
@@ -82,7 +72,7 @@
 	/**
 	 * Used to resolve the group execution order for a validate call.
 	 */
-	private GroupChainGenerator groupChainGenerator;
+	private final transient GroupChainGenerator groupChainGenerator;
 
 	private final ConstraintValidatorFactory constraintValidatorFactory;
 	private final MessageInterpolator messageInterpolator;
@@ -102,26 +92,22 @@
 		groupChainGenerator = new GroupChainGenerator();
 	}
 
-	/**
-	 * {@inheritDoc}
-	 */
 	public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
 		if ( object == null ) {
 			throw new IllegalArgumentException( "Validation of a null object" );
 		}
 		GroupChain groupChain = determineGroupExecutionOrder( groups );
 
-		ExecutionContext<T> context = ExecutionContext.getContextForValidate(
+		GlobalExecutionContext<T> context = GlobalExecutionContext.getContextForValidate(
 				object, messageInterpolator, constraintValidatorFactory, getCachingTraversableResolver()
 		);
 
-		List<ConstraintViolation<T>> list = validateInContext( context, groupChain );
+		List<ConstraintViolation<T>> list = validateInContext(
+				object, context, groupChain, PathImpl.createNewRootPath()
+		);
 		return new HashSet<ConstraintViolation<T>>( list );
 	}
 
-	/**
-	 * {@inheritDoc}
-	 */
 	public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
 		if ( object == null ) {
 			throw new IllegalArgumentException( "Validated object cannot be null." );
@@ -136,9 +122,6 @@
 		return new HashSet<ConstraintViolation<T>>( failingConstraintViolations );
 	}
 
-	/**
-	 * {@inheritDoc}
-	 */
 	public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
 		if ( beanType == null ) {
 			throw new IllegalArgumentException( "The bean type cannot be null." );
@@ -185,43 +168,51 @@
 	}
 
 	/**
-	 * Validates the object contained in <code>context</code>.
+	 * Validates the given object using the available context information.
 	 *
-	 * @param context A context object containing the object to validate together with other state information needed
-	 * for validation.
-	 * @param groupChain A <code>GroupChain</code> instance containing the resolved group sequence to execute
+	 * @param value The value to validate.
+	 * @param context Global context. Used amongst others to collect all failing constraints.
+	 * @param groupChain Contains the information which and in which oder groups have to be executed
+	 * @param path The current path of the valiation.
+	 * @param <T> The root bean type.
+	 * @param <V> The type of the current object on the validation stack.
 	 *
-	 * @return List of invalid constraints.
+	 * @return List of constraint violations or the empty set if there were no violations.
 	 */
-	private <T> List<ConstraintViolation<T>> validateInContext(ExecutionContext<T> context, GroupChain groupChain) {
-		if ( context.peekCurrentBean() == null ) {
+	private <T, U, V> List<ConstraintViolation<T>> validateInContext(U value, GlobalExecutionContext<T> context, GroupChain groupChain, PathImpl path) {
+		if ( value == null ) {
 			return Collections.emptyList();
 		}
 
-		// process all groups breadth-first
+		path = PathImpl.createShallowCopy( path );
+
+		LocalExecutionContext<U, V> localExecutionContext = LocalExecutionContext.getLocalExecutionContext( value );
+
+		// process first single groups. For these we can skip some object traversal, by first running all validations on the current bean
+		// before traversing the object.
 		Iterator<Group> groupIterator = groupChain.getGroupIterator();
 		while ( groupIterator.hasNext() ) {
 			Group group = groupIterator.next();
-			context.setCurrentGroup( group.getGroup() );
-			validateConstraints( context );
+			localExecutionContext.setCurrentGroup( group.getGroup() );
+			validateConstraints( context, localExecutionContext, path );
 		}
 		groupIterator = groupChain.getGroupIterator();
 		while ( groupIterator.hasNext() ) {
 			Group group = groupIterator.next();
-			context.setCurrentGroup( group.getGroup() );
-			validateCascadedConstraints( context );
+			localExecutionContext.setCurrentGroup( group.getGroup() );
+			validateCascadedConstraints( context, localExecutionContext, path );
 		}
 
-		// process group sequences depth-first to guarantee that groups following a violation within a group won't get executed.
+		// now we process sequences. For sequences I have to traverse the object graph since I have to stop processing when an error occurs.
 		Iterator<List<Group>> sequenceIterator = groupChain.getSequenceIterator();
 		while ( sequenceIterator.hasNext() ) {
 			List<Group> sequence = sequenceIterator.next();
 			for ( Group group : sequence ) {
 				int numberOfViolations = context.getFailingConstraints().size();
-				context.setCurrentGroup( group.getGroup() );
+				localExecutionContext.setCurrentGroup( group.getGroup() );
 
-				validateConstraints( context );
-				validateCascadedConstraints( context );
+				validateConstraints( context, localExecutionContext, path );
+				validateCascadedConstraints( context, localExecutionContext, path );
 
 				if ( context.getFailingConstraints().size() > numberOfViolations ) {
 					break;
@@ -232,15 +223,18 @@
 	}
 
 	/**
-	 * Validates the non-cascaded constraints.
+	 * Validates non cascaded constraints
 	 *
-	 * @param executionContext The current validation context.
+	 * @param executionContext The global execution context
+	 * @param localExecutionContext Collected information for single validation
+	 * @param path The current path of the validation
+	 * @param <T> The type of the root bean
 	 */
-	private <T> void validateConstraints(ExecutionContext<T> executionContext) {
+	private <T, U, V> void validateConstraints(GlobalExecutionContext<T> executionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path) {
 		//casting rely on the fact that root object is at the top of the stack
 		@SuppressWarnings("unchecked")
-		BeanMetaData<T> beanMetaData = getBeanMetaData( ( Class<T> ) executionContext.peekCurrentBeanType() );
-		if ( executionContext.getCurrentGroup().getName().equals( Default.class.getName() ) ) {
+		BeanMetaData<T> beanMetaData = getBeanMetaData( ( Class<T> ) localExecutionContext.getCurrentBeanType() );
+		if ( localExecutionContext.getCurrentGroup().getName().equals( Default.class.getName() ) ) {
 			List<Class<?>> defaultGroupSequence = beanMetaData.getDefaultGroupSequence();
 			if ( log.isTraceEnabled() && defaultGroupSequence.size() > 0 && defaultGroupSequence.get( 0 ) != Default.class ) {
 				log.trace(
@@ -250,56 +244,71 @@
 				);
 			}
 			for ( Class<?> defaultSequenceMember : defaultGroupSequence ) {
-				executionContext.setCurrentGroup( defaultSequenceMember );
-				boolean validationSuccessful = validateConstraintsForCurrentGroup( executionContext, beanMetaData );
+				localExecutionContext.setCurrentGroup( defaultSequenceMember );
+				boolean validationSuccessful = validateConstraintsForCurrentGroup(
+						executionContext, localExecutionContext, beanMetaData, path
+				);
 				if ( !validationSuccessful ) {
 					break;
 				}
 			}
 		}
 		else {
-			validateConstraintsForCurrentGroup( executionContext, beanMetaData );
+			validateConstraintsForCurrentGroup( executionContext, localExecutionContext, beanMetaData, path );
 		}
 	}
 
-	/**
-	 * Validates all constraints for the given bean using the current group set in the execution context.
-	 *
-	 * @param executionContext The execution context.
-	 * @param beanMetaData The bean metadata object for the bean to validate.
-	 *
-	 * @return <code>true</code> if the validation was successful (meaning no constraint violations), <code>false</code>
-	 *         otherwise.
-	 */
-	private <T> boolean validateConstraintsForCurrentGroup(ExecutionContext<T> executionContext, BeanMetaData<T> beanMetaData) {
+	private <T, U, V> boolean validateConstraintsForCurrentGroup(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, BeanMetaData<T> beanMetaData, PathImpl path) {
 		boolean validationSuccessful = true;
 		for ( MetaConstraint<T, ?> metaConstraint : beanMetaData.geMetaConstraintList() ) {
-			executionContext.pushProperty( metaConstraint.getPropertyName() );
-			if ( executionContext.isValidationRequired( metaConstraint ) ) {
-				boolean tmp = metaConstraint.validateConstraint( executionContext );
+			PathImpl newPath = PathImpl.createShallowCopy( path );
+			newPath.addNode( new NodeImpl( metaConstraint.getPropertyName() ) );
+			localExecutionContext.setPropertyPath( newPath );
+			if ( isValidationRequired( globalExecutionContext, localExecutionContext, metaConstraint ) ) {
+				Object valueToValidate = metaConstraint.getValue( localExecutionContext.getCurrentBean() );
+				localExecutionContext.setCurrentValidatedValue( ( V ) valueToValidate );
+				boolean tmp = metaConstraint.validateConstraint( globalExecutionContext, localExecutionContext );
 				validationSuccessful = validationSuccessful && tmp;
 			}
-			executionContext.popProperty();
 		}
+		globalExecutionContext.markProcessed(
+				localExecutionContext.getCurrentBean(),
+				localExecutionContext.getCurrentGroup(),
+				localExecutionContext.getPropertyPath()
+		);
 		return validationSuccessful;
 	}
 
-	//this method must always be called after validateConstraints for the same context
-	private <T> void validateCascadedConstraints(ExecutionContext<T> context) {
-		List<Member> cascadedMembers = getBeanMetaData( context.peekCurrentBeanType() )
+	/**
+	 * Validates all cascaded constraints for the given bean using the current group set in the execution context.
+	 * This method must always be called after validateConstraints for the same context.
+	 *
+	 * @param globalExecutionContext The execution context
+	 * @param localExecutionContext Collected information for single validation
+	 * @param path The current path of the valiation.
+	 */
+	private <T, U, V> void validateCascadedConstraints(GlobalExecutionContext<T> globalExecutionContext, LocalExecutionContext<U, V> localExecutionContext, PathImpl path) {
+		List<Member> cascadedMembers = getBeanMetaData( localExecutionContext.getCurrentBeanType() )
 				.getCascadedMembers();
 		for ( Member member : cascadedMembers ) {
 			Type type = ReflectionHelper.typeOf( member );
-			context.pushProperty( ReflectionHelper.getPropertyName( member ) );
-			if ( context.isCascadeRequired( member ) ) {
-				Object value = ReflectionHelper.getValue( member, context.peekCurrentBean() );
+			PathImpl newPath = PathImpl.createShallowCopy( path );
+			newPath.addNode( new NodeImpl( ReflectionHelper.getPropertyName( member ) ) );
+			localExecutionContext.setPropertyPath( newPath );
+			if ( isCascadeRequired( globalExecutionContext, localExecutionContext, member ) ) {
+				Object value = ReflectionHelper.getValue( member, localExecutionContext.getCurrentBean() );
 				if ( value != null ) {
-					Iterator<?> iter = createIteratorForCascadedValue( context, type, value );
+					Iterator<?> iter = createIteratorForCascadedValue( localExecutionContext, type, value );
 					boolean isIndexable = isIndexable( type );
-					validateCascadedConstraint( context, iter, isIndexable );
+					validateCascadedConstraint(
+							globalExecutionContext,
+							iter,
+							isIndexable,
+							localExecutionContext.getCurrentGroup(),
+							localExecutionContext.getPropertyPath()
+					);
 				}
 			}
-			context.popProperty();
 		}
 	}
 
@@ -313,7 +322,7 @@
 	 *
 	 * @return An iterator over the value of a cascaded property.
 	 */
-	private <T> Iterator<?> createIteratorForCascadedValue(ExecutionContext<T> context, Type type, Object value) {
+	private <U, V> Iterator<?> createIteratorForCascadedValue(LocalExecutionContext<U, V> context, Type type, Object value) {
 		Iterator<?> iter;
 		if ( ReflectionHelper.isIterable( type ) ) {
 			iter = ( ( Iterable<?> ) value ).iterator();
@@ -360,30 +369,36 @@
 	}
 
 	@SuppressWarnings("RedundantArrayCreation")
-	private <T> void validateCascadedConstraint(ExecutionContext<T> context, Iterator<?> iter, boolean isIndexable) {
-		Object actualValue;
-		String propertyIndex;
+	private <T> void validateCascadedConstraint(GlobalExecutionContext<T> context, Iterator<?> iter, boolean isIndexable, Class<?> currentGroup, PathImpl currentPath) {
+		Object value;
+		Integer index;
+		Object mapKey = null;
 		int i = 0;
 		while ( iter.hasNext() ) {
-			actualValue = iter.next();
-			propertyIndex = String.valueOf( i );
-			if ( actualValue instanceof Map.Entry ) {
-				Object key = ( ( Map.Entry ) actualValue ).getKey();
-				if ( VALID_MAP_INDEX_CLASSES.contains( key.getClass() ) ) {
-					propertyIndex = key.toString();
-				}
-				actualValue = ( ( Map.Entry ) actualValue ).getValue();
+			value = iter.next();
+			index = i;
+			if ( value instanceof Map.Entry ) {
+				mapKey = ( ( Map.Entry ) value ).getKey();
+				value = ( ( Map.Entry ) value ).getValue();
 			}
 
-			if ( !context.isAlreadyValidated( actualValue ) ) {
-				if ( isIndexable ) {
-					context.setPropertyIndex( propertyIndex );
-				}
-				context.pushCurrentBean( actualValue );
-				GroupChain groupChain = groupChainGenerator.getGroupChainFor( Arrays.asList( new Class<?>[] { context.getCurrentGroup() } ) );
-				validateInContext( context, groupChain );
-				context.popCurrentBean();
+			if ( isIndexable ) {
+				// only one of the two values index/mapKey will be set. The other will stay null.
+				currentPath.getLeafNode().setIndex( index );
+				currentPath.getLeafNode().setKey( mapKey );
 			}
+
+			if ( !context.isAlreadyValidated( value, currentGroup, currentPath ) ) {
+
+				GroupChain groupChain = groupChainGenerator.getGroupChainFor(
+						Arrays.asList(
+								new Class<?>[] {
+										currentGroup
+								}
+						)
+				);
+				validateInContext( value, context, groupChain, currentPath );
+			}
 			i++;
 		}
 	}
@@ -445,12 +460,12 @@
 		}
 	}
 
-	private <T> void validatePropertyForGroup(
+	private <T, U, V> void validatePropertyForGroup(
 			T object,
 			PathImpl path,
 			List<ConstraintViolation<T>> failingConstraintViolations,
 			Set<MetaConstraint<T, ?>> metaConstraints,
-			Object hostingBeanInstance,
+			U hostingBeanInstance,
 			Group group,
 			TraversableResolver cachedTraversableResolver) {
 		int numberOfConstraintViolationsBefore = failingConstraintViolations.size();
@@ -467,17 +482,21 @@
 
 		for ( Class<?> groupClass : groupList ) {
 			for ( MetaConstraint<T, ?> metaConstraint : metaConstraints ) {
-				ExecutionContext<T> context = ExecutionContext.getContextForValidateProperty(
+				GlobalExecutionContext<T> context = GlobalExecutionContext.getContextForValidateProperty(
 						object,
-						hostingBeanInstance,
 						messageInterpolator,
 						constraintValidatorFactory,
 						cachedTraversableResolver
 				);
-				context.setProperty( path );
-				context.setCurrentGroup( groupClass );
-				if ( context.isValidationRequired( metaConstraint ) ) {
-					metaConstraint.validateConstraint( context );
+				LocalExecutionContext<U, V> localContext = LocalExecutionContext.getLocalExecutionContext(
+						hostingBeanInstance
+				);
+				localContext.setPropertyPath( path );
+				localContext.setCurrentGroup( groupClass );
+				if ( isValidationRequired( context, localContext, metaConstraint ) ) {
+					Object valueToValidate = metaConstraint.getValue( localContext.getCurrentBean() );
+					localContext.setCurrentValidatedValue( ( V ) valueToValidate );
+					metaConstraint.validateConstraint( context, localContext );
 					failingConstraintViolations.addAll( context.getFailingConstraints() );
 				}
 			}
@@ -537,16 +556,16 @@
 		}
 	}
 
-	private <T> void validateValueForGroup(
-			Class<T> beanType,
-			Object value,
+	private <U, V> void validateValueForGroup(
+			Class<U> beanType,
+			V value,
 			PathImpl path,
-			List<ConstraintViolation<T>> failingConstraintViolations,
-			Set<MetaConstraint<T, ?>> metaConstraints,
+			List<ConstraintViolation<U>> failingConstraintViolations,
+			Set<MetaConstraint<U, ?>> metaConstraints,
 			Group group,
 			TraversableResolver cachedTraversableResolver) {
 		int numberOfConstraintViolations = failingConstraintViolations.size();
-		BeanMetaData<T> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
+		BeanMetaData<U> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
 
 		List<Class<?>> groupList;
 		if ( group.isDefaultGroup() ) {
@@ -557,16 +576,17 @@
 			groupList.add( group.getGroup() );
 		}
 
-
 		for ( Class<?> groupClass : groupList ) {
-			for ( MetaConstraint<T, ?> metaConstraint : metaConstraints ) {
-				ExecutionContext<T> context = ExecutionContext.getContextForValidateValue(
-						beanType, value, messageInterpolator, constraintValidatorFactory, cachedTraversableResolver
+			for ( MetaConstraint<U, ?> metaConstraint : metaConstraints ) {
+				GlobalExecutionContext<U> context = GlobalExecutionContext.getContextForValidateValue(
+						beanType, messageInterpolator, constraintValidatorFactory, cachedTraversableResolver
 				);
-				context.setProperty( path );
-				context.setCurrentGroup( groupClass );
-				if ( context.isValidationRequired( metaConstraint ) ) {
-					metaConstraint.validateConstraint( value, context );
+				LocalExecutionContext<U, V> localContext = LocalExecutionContext.getLocalExecutionContext( beanType );
+				localContext.setPropertyPath( path );
+				localContext.setCurrentGroup( groupClass );
+				localContext.setCurrentValidatedValue( value );
+				if ( isValidationRequired( context, localContext, metaConstraint ) ) {
+					metaConstraint.validateConstraint( context, localContext );
 					failingConstraintViolations.addAll( context.getFailingConstraints() );
 				}
 			}
@@ -589,9 +609,7 @@
 	 *
 	 * @return Returns the bean hosting the constraints which match the specified property path.
 	 */
-	@SuppressWarnings("unchecked")
 	private <T> Object collectMetaConstraintsForPath(Class<T> clazz, Object value, Iterator<Path.Node> propertyIter, Set<MetaConstraint<T, ?>> metaConstraints) {
-
 		Path.Node elem = propertyIter.next();
 		if ( elem.getName() == null ) { // skip root node
 			elem = propertyIter.next();
@@ -601,11 +619,11 @@
 		if ( !propertyIter.hasNext() ) {
 
 			//use metadata first as ReflectionHelper#containsMember is slow
-			//TODO store some metadata here?
-			if ( metaData.getPropertyDescriptor( elem.getName() ) == null
-					&& !ReflectionHelper.containsMember( clazz, elem.getName() ) ) {
-				//TODO better error report
-				throw new IllegalArgumentException( "Invalid property path." );
+			if ( metaData.getPropertyDescriptor( elem.getName() ) == null ) {
+				throw new IllegalArgumentException(
+						"Invalid property path. There is no property " + elem.getName() + " in entity " + metaData.getBeanClass()
+								.getName()
+				);
 			}
 
 			List<MetaConstraint<T, ? extends Annotation>> metaConstraintList = metaData.geMetaConstraintList();
@@ -630,8 +648,12 @@
 						}
 						type = ReflectionHelper.getIndexedType( type );
 					}
+
+					@SuppressWarnings("unchecked")
+					Class<T> valueClass = ( Class<T> ) ( value == null ? type : value.getClass() );
+
 					return collectMetaConstraintsForPath(
-							( Class<T> ) ( value == null ? type : value.getClass() ),
+							valueClass,
 							value,
 							propertyIter,
 							metaConstraints
@@ -642,9 +664,6 @@
 		return value;
 	}
 
-	/**
-	 * {@inheritDoc}
-	 */
 	private <T> BeanMetaData<T> getBeanMetaData(Class<T> beanClass) {
 		BeanMetaDataImpl<T> metadata = beanMetaDataCache.getBeanMetaData( beanClass );
 		if ( metadata == null ) {
@@ -657,8 +676,43 @@
 	/**
 	 * Must be called and stored for the duration of the stack call
 	 * A new instance is returned each time
+	 *
+	 * @return The resolver for the duration of a full validation.
 	 */
 	private TraversableResolver getCachingTraversableResolver() {
 		return new SingleThreadCachedTraversableResolver( traversableResolver );
 	}
+
+	@SuppressWarnings("SimplifiableIfStatement")
+	private boolean isValidationRequired(GlobalExecutionContext globalContext, LocalExecutionContext localContext, MetaConstraint metaConstraint) {
+		if ( !metaConstraint.getGroupList().contains( localContext.getCurrentGroup() ) ) {
+			return false;
+		}
+
+		return globalContext.getTraversableResolver().isReachable(
+				localContext.getCurrentBean(),
+				localContext.getPropertyPath().getLeafNode(),
+				globalContext.getRootBeanClass(),
+				localContext.getPropertyPath().getPathWithoutLeafNode(),
+				metaConstraint.getElementType()
+		);
+	}
+
+	private boolean isCascadeRequired(GlobalExecutionContext globalContext, LocalExecutionContext localContext, Member member) {
+		final ElementType type = member instanceof Field ? ElementType.FIELD : ElementType.METHOD;
+		return globalContext.getTraversableResolver().isReachable(
+				localContext.getCurrentBean(),
+				localContext.getPropertyPath().getLeafNode(),
+				globalContext.getRootBeanClass(),
+				localContext.getPropertyPath().getPathWithoutLeafNode(),
+				type
+		)
+				&& globalContext.getTraversableResolver().isCascadable(
+				localContext.getCurrentBean(),
+				localContext.getPropertyPath().getLeafNode(),
+				globalContext.getRootBeanClass(),
+				localContext.getPropertyPath().getPathWithoutLeafNode(),
+				type
+		);
+	}
 }

Modified: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/metadata/MetaConstraint.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/metadata/MetaConstraint.java	2009-06-24 14:43:50 UTC (rev 16944)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/metadata/MetaConstraint.java	2009-06-24 14:45:17 UTC (rev 16945)
@@ -26,12 +26,13 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-import javax.validation.metadata.ConstraintDescriptor;
 import javax.validation.ConstraintViolation;
+import javax.validation.metadata.ConstraintDescriptor;
 
-import org.hibernate.validation.util.ReflectionHelper;
 import org.hibernate.validation.engine.ConstraintTree;
-import org.hibernate.validation.engine.ExecutionContext;
+import org.hibernate.validation.engine.GlobalExecutionContext;
+import org.hibernate.validation.engine.LocalExecutionContext;
+import org.hibernate.validation.util.ReflectionHelper;
 
 /**
  * Instances of this class abstract the constraint type  (class, method or field constraint) and gives access to
@@ -120,11 +121,11 @@
 		return constraintTree;
 	}
 
-	public <T> boolean validateConstraint(ExecutionContext<T> executionContext) {
-		final Object leafBeanInstance = executionContext.peekCurrentBean();
-		Object value = getValue( leafBeanInstance );
+	public <T, U, V> boolean validateConstraint(GlobalExecutionContext<T> executionContext, LocalExecutionContext<U, V> localExecutionContext) {
 		List<ConstraintViolation<T>> constraintViolations = new ArrayList<ConstraintViolation<T>>();
-		constraintTree.validateConstraints( value, typeOfAnnoatedElement(), executionContext, constraintViolations );
+		constraintTree.validateConstraints(
+				typeOfAnnoatedElement(), executionContext, localExecutionContext, constraintViolations
+		);
 		if ( constraintViolations.size() > 0 ) {
 			executionContext.addConstraintFailures( constraintViolations );
 			return false;
@@ -132,14 +133,21 @@
 		return true;
 	}
 
-	public <T> boolean validateConstraint(Object value, ExecutionContext<T> executionContext) {
-		List<ConstraintViolation<T>> constraintViolations = new ArrayList<ConstraintViolation<T>>();
-		constraintTree.validateConstraints( value, typeOfAnnoatedElement(), executionContext, constraintViolations );
-		if ( constraintViolations.size() > 0 ) {
-			executionContext.addConstraintFailures( constraintViolations );
-			return false;
+	/**
+	 * @param o the object from which to retrieve the value.
+	 *
+	 * @return Returns the value for this constraint from the specified object. Depending on the type either the value itself
+	 *         is returned of method or field access is used to access the value.
+	 */
+	public Object getValue(Object o) {
+		switch ( elementType ) {
+			case TYPE: {
+				return o;
+			}
+			default: {
+				return ReflectionHelper.getValue( member, o );
+			}
 		}
-		return true;
 	}
 
 	private Type typeOfAnnoatedElement() {
@@ -151,28 +159,11 @@
 			}
 			default: {
 				t = ReflectionHelper.typeOf( member );
-				if ( t instanceof Class && ((Class) t).isPrimitive()) {
+				if ( t instanceof Class && ( ( Class ) t ).isPrimitive() ) {
 					t = ReflectionHelper.boxedTyp( t );
 				}
 			}
 		}
 		return t;
 	}
-
-	/**
-	 * @param o the object from which to retrieve the value.
-	 *
-	 * @return Returns the value for this constraint from the specified object. Depending on the type either the value itself
-	 *         is returned of method or field access is used to access the value.
-	 */
-	private Object getValue(Object o) {
-		switch ( elementType ) {
-			case TYPE: {
-				return o;
-			}
-			default: {
-				return ReflectionHelper.getValue( member, o );
-			}
-		}
-	}
 }




More information about the hibernate-commits mailing list