[hibernate-commits] Hibernate SVN: r16497 - in validator/trunk/hibernate-validator/src: main/java/org/hibernate/validation/engine/resolver and 2 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Fri May 1 07:00:52 EDT 2009


Author: epbernard
Date: 2009-05-01 07:00:52 -0400 (Fri, 01 May 2009)
New Revision: 16497

Added:
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/resolver/SingleThreadCachedTraversableResolver.java
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/CachedTraversableResolverTest.java
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Cloth.java
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Jacket.java
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Suit.java
   validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Trousers.java
Modified:
   validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java
Log:
HV-155 Use a TraversableResolver caching results within a call stack to reduce overhead

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-05-01 10:44:22 UTC (rev 16496)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/ValidatorImpl.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -42,6 +42,7 @@
 import org.hibernate.validation.engine.groups.Group;
 import org.hibernate.validation.engine.groups.GroupChain;
 import org.hibernate.validation.engine.groups.GroupChainGenerator;
+import org.hibernate.validation.engine.resolver.SingleThreadCachedTraversableResolver;
 import org.hibernate.validation.util.LoggerFactory;
 import org.hibernate.validation.util.PropertyIterator;
 import org.hibernate.validation.util.ReflectionHelper;
@@ -79,6 +80,7 @@
 
 	private final ConstraintValidatorFactory constraintValidatorFactory;
 	private final MessageInterpolator messageInterpolator;
+	//never use it directly, always use getCachingTraversableResolver() to retrieved the single threaded caching wrapper.
 	private final TraversableResolver traversableResolver;
 	private final ConstraintHelper constraintHelper;
 	private final BeanMetaDataCache beanMetaDataCache;
@@ -104,7 +106,7 @@
 		GroupChain groupChain = determineGroupExecutionOrder( groups );
 
 		ExecutionContext<T> context = ExecutionContext.getContextForValidate(
-				object, messageInterpolator, constraintValidatorFactory, traversableResolver
+				object, messageInterpolator, constraintValidatorFactory, getCachingTraversableResolver()
 		);
 
 		List<ConstraintViolation<T>> list = validateInContext( context, groupChain );
@@ -181,6 +183,8 @@
 			return Collections.emptyList();
 		}
 
+		//FIXME if context not accessible do not call isTraversable??
+
 		// process all groups breadth-first
 		Iterator<Group> groupIterator = groupChain.getGroupIterator();
 		while ( groupIterator.hasNext() ) {
@@ -256,6 +260,7 @@
 	 */
 	private <T> boolean validateConstraintsForCurrentGroup(ExecutionContext<T> executionContext, BeanMetaData<T> beanMetaData) {
 		boolean validationSuccessful = true;
+		//FIXME isValidationRequired for all constraints in a given context May need to divide by elementType
 		for ( MetaConstraint<T, ?> metaConstraint : beanMetaData.geMetaConstraintList() ) {
 			executionContext.pushProperty( metaConstraint.getPropertyName() );
 			if ( executionContext.isValidationRequired( metaConstraint ) ) {
@@ -270,6 +275,7 @@
 	private <T> void validateCascadedConstraints(ExecutionContext<T> context) {
 		List<Member> cascadedMembers = getBeanMetaData( context.peekCurrentBeanType() )
 				.getCascadedMembers();
+		//FIXME isValidationRequired for all constraints in a given context May need to divide by elementType
 		for ( Member member : cascadedMembers ) {
 			Type type = ReflectionHelper.typeOf( member );
 			context.pushProperty( ReflectionHelper.getPropertyName( member ) );
@@ -359,12 +365,14 @@
 			return;
 		}
 
+		//this method is at the root of validateProperty calls, share the same cachedTR
+		TraversableResolver cachedResolver = getCachingTraversableResolver();
 
 		Iterator<Group> groupIterator = groupChain.getGroupIterator();
 		while ( groupIterator.hasNext() ) {
 			Group group = groupIterator.next();
 			validatePropertyForGroup(
-					object, propertyIter, failingConstraintViolations, metaConstraints, hostingBeanInstance, group
+					object, propertyIter, failingConstraintViolations, metaConstraints, hostingBeanInstance, group, cachedResolver
 			);
 		}
 
@@ -374,7 +382,7 @@
 			int numberOfConstraintViolationsBefore = failingConstraintViolations.size();
 			for ( Group group : sequence ) {
 				validatePropertyForGroup(
-						object, propertyIter, failingConstraintViolations, metaConstraints, hostingBeanInstance, group
+						object, propertyIter, failingConstraintViolations, metaConstraints, hostingBeanInstance, group, cachedResolver
 				);
 
 				if ( failingConstraintViolations.size() > numberOfConstraintViolationsBefore ) {
@@ -384,7 +392,14 @@
 		}
 	}
 
-	private <T> void validatePropertyForGroup(T object, PropertyIterator propertyIter, List<ConstraintViolation<T>> failingConstraintViolations, Set<MetaConstraint<T, ?>> metaConstraints, Object hostingBeanInstance, Group group) {
+	private <T> void validatePropertyForGroup(
+			T object,
+			PropertyIterator propertyIter,
+			List<ConstraintViolation<T>> failingConstraintViolations,
+			Set<MetaConstraint<T, ?>> metaConstraints,
+			Object hostingBeanInstance,
+			Group group,
+			TraversableResolver cachedTraversableResolver) {
 		int numberOfConstraintViolationsBefore = failingConstraintViolations.size();
 		BeanMetaData<T> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
 
@@ -404,7 +419,7 @@
 						hostingBeanInstance,
 						messageInterpolator,
 						constraintValidatorFactory,
-						traversableResolver
+						cachedTraversableResolver
 				);
 				context.pushProperty( propertyIter.getOriginalProperty() );
 				context.setCurrentGroup( groupClass );
@@ -429,6 +444,9 @@
 			return;
 		}
 
+		//root of validateValue calls, share the same cached TraversableResolver
+		TraversableResolver cachedTraversableResolver = getCachingTraversableResolver();
+
 		// process groups
 		Iterator<Group> groupIterator = groupChain.getGroupIterator();
 		while ( groupIterator.hasNext() ) {
@@ -439,7 +457,8 @@
 					propertyIter,
 					failingConstraintViolations,
 					metaConstraints,
-					group
+					group,
+					cachedTraversableResolver
 			);
 		}
 
@@ -455,7 +474,8 @@
 						propertyIter,
 						failingConstraintViolations,
 						metaConstraints,
-						group
+						group,
+						cachedTraversableResolver
 				);
 
 				if ( failingConstraintViolations.size() > numberOfConstraintViolations ) {
@@ -465,7 +485,14 @@
 		}
 	}
 
-	private <T> void validateValueForGroup(Class<T> beanType, Object value, PropertyIterator propertyIter, List<ConstraintViolation<T>> failingConstraintViolations, Set<MetaConstraint<T, ?>> metaConstraints, Group group) {
+	private <T> void validateValueForGroup(
+			Class<T> beanType,
+			Object value,
+			PropertyIterator propertyIter,
+			List<ConstraintViolation<T>> failingConstraintViolations,
+			Set<MetaConstraint<T, ?>> metaConstraints,
+			Group group,
+			TraversableResolver cachedTraversableResolver) {
 		int numberOfConstraintViolations = failingConstraintViolations.size();
 		BeanMetaData<T> beanMetaData = getBeanMetaData( metaConstraints.iterator().next().getBeanClass() );
 
@@ -478,10 +505,11 @@
 			groupList.add( group.getGroup() );
 		}
 
+
 		for ( Class<?> groupClass : groupList ) {
 			for ( MetaConstraint<T, ?> metaConstraint : metaConstraints ) {
 				ExecutionContext<T> context = ExecutionContext.getContextForValidateValue(
-						beanType, value, messageInterpolator, constraintValidatorFactory, traversableResolver
+						beanType, value, messageInterpolator, constraintValidatorFactory, cachedTraversableResolver
 				);
 				context.pushProperty( propertyIter.getOriginalProperty() );
 				context.setCurrentGroup( groupClass );
@@ -561,4 +589,12 @@
 		}
 		return metadata;
 	}
+
+	/**
+	 * Must be called and stored for the duration of the stack call
+	 * A new instance is returned each time
+	 */
+	private TraversableResolver getCachingTraversableResolver() {
+		return new SingleThreadCachedTraversableResolver( traversableResolver );
+	}
 }

Added: validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/resolver/SingleThreadCachedTraversableResolver.java
===================================================================
--- validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/resolver/SingleThreadCachedTraversableResolver.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/main/java/org/hibernate/validation/engine/resolver/SingleThreadCachedTraversableResolver.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,123 @@
+package org.hibernate.validation.engine.resolver;
+
+import java.lang.annotation.ElementType;
+import java.util.HashMap;
+import java.util.Map;
+import javax.validation.TraversableResolver;
+
+/**
+ * Cache results of a delegated traversable resovler to optimize calls
+ * It works only for a single validate* call and should not be used if
+ * the TraversableResolver is accessed concurrently
+ *
+ * @author Emmanuel Bernard
+ */
+public class SingleThreadCachedTraversableResolver implements TraversableResolver {
+	private TraversableResolver delegate;
+	private Map<LazyHolder, LazyHolder> lazys = new HashMap<LazyHolder, LazyHolder>();
+	private Map<TraversableHolder, TraversableHolder> traversables = new HashMap<TraversableHolder, TraversableHolder>();
+
+	public SingleThreadCachedTraversableResolver(TraversableResolver delegate) {
+		this.delegate = delegate;
+	}
+
+	public boolean isTraversable(Object traversableObject, String traversableProperty, Class<?> rootBeanType, String pathToTraversableObject, ElementType elementType) {
+		Boolean delegateResult = null;
+
+		//if traversableObject is null we can't determine lazyness
+		if (traversableObject != null) {
+			LazyHolder currentLH = new LazyHolder( traversableObject, traversableProperty );
+			LazyHolder cachedLH = lazys.get( currentLH );
+			if (cachedLH == null) {
+				delegateResult = delegate.isTraversable(
+						traversableObject,
+						traversableProperty,
+						rootBeanType,
+						pathToTraversableObject,
+						elementType );
+				currentLH.isTraversable = delegateResult;
+				lazys.put( currentLH, currentLH );
+				cachedLH = currentLH;
+			}
+			if ( ! cachedLH.isTraversable ) return false;
+		}
+
+
+		TraversableHolder currentTH = new TraversableHolder( rootBeanType, pathToTraversableObject, elementType );
+		TraversableHolder cachedTH = traversables.get(currentTH);
+		if ( cachedTH == null ) {
+			if (delegateResult == null) {
+				delegateResult = delegate.isTraversable(
+					traversableObject,
+					traversableProperty,
+					rootBeanType,
+					pathToTraversableObject,
+					elementType );
+			}
+			currentTH.isTraversable = delegateResult;
+			traversables.put( currentTH, currentTH );
+			cachedTH = currentTH;
+		}
+		return cachedTH.isTraversable;
+	}
+
+	private static class LazyHolder {
+		private final Object traversableObject;
+		private final String traversableProperty;
+		private final int hashCode;
+		private boolean isTraversable;
+
+		private LazyHolder(Object traversableObject, String traversableProperty) {
+			this.traversableObject = traversableObject;
+			this.traversableProperty = traversableProperty == null ? "" : traversableProperty;
+			hashCode = this.traversableObject.hashCode() + this.traversableProperty.hashCode();
+		}
+
+		@Override
+		public int hashCode() {
+			return hashCode;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if ( ! (obj instanceof LazyHolder) ) {
+				return false;
+			}
+			LazyHolder that = (LazyHolder) obj;
+			return traversableObject == that.traversableObject
+					&& traversableProperty.equals( that.traversableProperty ); 
+		}
+	}
+	
+	private static class TraversableHolder {
+		private final Class<?> rootBeanType;
+		private final String pathToTraversableObject;
+		private final ElementType elementType;
+		private final int hashCode;
+		
+		private boolean isTraversable;
+
+		private TraversableHolder(Class<?> rootBeanType, String pathToTraversableObject, ElementType elementType) {
+			this.rootBeanType = rootBeanType;
+			this.pathToTraversableObject = pathToTraversableObject == null ? "" : pathToTraversableObject;
+			this.elementType = elementType;
+			hashCode = this.rootBeanType.hashCode() + this.pathToTraversableObject.hashCode() + this.elementType.hashCode();
+		}
+
+		@Override
+		public int hashCode() {
+			return hashCode;
+		}
+
+		@Override
+		public boolean equals(Object obj) {
+			if ( ! (obj instanceof TraversableHolder) ) {
+				return false;
+			}
+			TraversableHolder that = (TraversableHolder) obj;
+			return rootBeanType == that.rootBeanType
+					&& pathToTraversableObject.equals( that.pathToTraversableObject )
+					&& elementType == that.elementType; 
+		}
+	}
+}

Added: validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/CachedTraversableResolverTest.java
===================================================================
--- validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/CachedTraversableResolverTest.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/CachedTraversableResolverTest.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,97 @@
+package org.hibernate.validation.engine.resolver;
+
+import java.lang.annotation.ElementType;
+import java.util.HashSet;
+import java.util.Set;
+import javax.validation.TraversableResolver;
+import javax.validation.Validation;
+import javax.validation.ValidatorFactory;
+import javax.validation.Validator;
+import javax.validation.groups.Default;
+
+import org.testng.annotations.Test;
+import static org.testng.Assert.*;
+
+import org.hibernate.validation.engine.HibernateValidatorConfiguration;
+
+/**
+ * @author Emmanuel Bernard
+ */
+//this test is specific to Hibernate Validator
+public class CachedTraversableResolverTest {
+	@Test
+	public void testCache() {
+		TraversableResolver resolver = new AskOnceTR();
+		ValidatorFactory factory = Validation.byProvider( HibernateValidatorConfiguration.class )
+				.configure().traversableResolver( resolver )
+				.buildValidatorFactory();
+		Suit suit = new Suit();
+		suit.setTrousers( new Trousers() );
+		suit.setJacket( new Jacket() );
+		suit.setSize( 3333 );
+		suit.getTrousers().setLength( 32321 );
+		suit.getJacket().setWidth( 432432 );
+		Validator v = factory.getValidator();
+		try {
+			v.validate( suit, Default.class, Cloth.class );
+		}
+		catch ( IllegalStateException e ) {
+			fail("Traversable Called several times for a given object");
+		}
+
+		v = factory.usingContext().traversableResolver( new AskOnceTR() ).getValidator();
+		try {
+			v.validateProperty( suit, "size", Default.class, Cloth.class );
+		}
+		catch ( IllegalStateException e ) {
+			fail("Traversable Called several times for a given object");
+		}
+
+		v = factory.usingContext().traversableResolver( new AskOnceTR() ).getValidator();
+		try {
+			v.validateValue( Suit.class, "size", 2, Default.class, Cloth.class );
+		}
+		catch ( IllegalStateException e ) {
+			fail("Traversable Called several times for a given object");
+		}
+	}
+
+	private static class AskOnceTR implements TraversableResolver {
+		private Set<Holder> asked = new HashSet<Holder>();
+
+		public Set<Holder> getAsked() {
+			return asked;
+		}
+
+		public boolean isTraversable(Object traversableObject, String traversableProperty, Class<?> rootBeanType, String pathToTraversableObject, ElementType elementType) {
+			Holder h = new Holder(traversableObject, traversableProperty);
+			if (asked.contains( h )) throw new IllegalStateException( "Called twice" );
+			asked.add( h );
+			return true;
+		}
+
+		public static class Holder {
+			Object NULL = new Object();
+			Object to;
+			String tp;
+
+			public Holder(Object traversableObject, String traversableProperty) {
+				to = traversableObject == null ? NULL : traversableObject;
+				tp = traversableProperty;
+			}
+
+			@Override
+			public int hashCode() {
+				return to.hashCode() + tp.hashCode();
+			}
+
+			@Override
+			public boolean equals(Object obj) {
+				if ( ! ( obj instanceof Holder ) ) return false;
+				Holder that = (Holder) obj;
+
+				return to != NULL && to == that.to && tp.equals( that.tp );
+			}
+		}
+	}
+}

Added: validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Cloth.java
===================================================================
--- validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Cloth.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Cloth.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,7 @@
+package org.hibernate.validation.engine.resolver;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public interface Cloth {
+}

Added: validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Jacket.java
===================================================================
--- validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Jacket.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Jacket.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,20 @@
+package org.hibernate.validation.engine.resolver;
+
+import javax.validation.constraints.Max;
+
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class Jacket {
+	Integer width;
+
+	@Max(30)
+	public Integer getWidth() {
+		return width;
+	}
+
+	public void setWidth(Integer width) {
+		this.width = width;
+	}
+}

Added: validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Suit.java
===================================================================
--- validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Suit.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Suit.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,44 @@
+package org.hibernate.validation.engine.resolver;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.Valid;
+import javax.validation.GroupSequence;
+import javax.validation.groups.Default;
+
+/**
+ * @author Emmanuel Bernard
+ */
+ at GroupSequence( {Suit.class, Cloth.class })
+public class Suit {
+	@Max(value=50, groups = { Default.class, Cloth.class})
+	@Min(1)
+	private Integer size;
+	@Valid private Trousers trousers;
+	private Jacket jacket;
+
+	public Trousers getTrousers() {
+		return trousers;
+	}
+
+	public void setTrousers(Trousers trousers) {
+		this.trousers = trousers;
+	}
+
+	@Valid
+	public Jacket getJacket() {
+		return jacket;
+	}
+
+	public void setJacket(Jacket jacket) {
+		this.jacket = jacket;
+	}
+
+	public Integer getSize() {
+		return size;
+	}
+
+	public void setSize(Integer size) {
+		this.size = size;
+	}
+}

Added: validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Trousers.java
===================================================================
--- validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Trousers.java	                        (rev 0)
+++ validator/trunk/hibernate-validator/src/test/java/org/hibernate/validation/engine/resolver/Trousers.java	2009-05-01 11:00:52 UTC (rev 16497)
@@ -0,0 +1,22 @@
+package org.hibernate.validation.engine.resolver;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.Max;
+import javax.validation.groups.Default;
+
+/**
+ * @author Emmanuel Bernard
+ */
+public class Trousers {
+	@Min(value=70, groups = {Default.class, Cloth.class})
+	@Max(value=220)
+	private Integer length;
+
+	public Integer getLength() {
+		return length;
+	}
+
+	public void setLength(Integer length) {
+		this.length = length;
+	}
+}




More information about the hibernate-commits mailing list