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
+ */
+@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;
+ }
+}