Skip to content

Commit

Permalink
HV-1487 If we are using a traverse all TraversableResolver, don't wrap
Browse files Browse the repository at this point in the history
it into a caching one

It avoids quite a lot of useless map lookups.

Originally suggested by Chris Narburgh.
  • Loading branch information
gsmet authored and gunnarmorling committed Oct 18, 2017
1 parent fc454b6 commit 8b2f0d6
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 53 deletions.
Expand Up @@ -37,7 +37,7 @@
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl;
import org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver;
import org.hibernate.validator.internal.engine.resolver.TraversableResolvers;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
import org.hibernate.validator.internal.util.Contracts;
Expand Down Expand Up @@ -121,7 +121,7 @@ private ConfigurationImpl() {
this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(
ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES
);
this.defaultTraversableResolver = new DefaultTraversableResolver();
this.defaultTraversableResolver = TraversableResolvers.getDefault();
this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
this.defaultParameterNameProvider = new DefaultParameterNameProvider();
this.defaultClockProvider = DefaultClockProvider.INSTANCE;
Expand Down
Expand Up @@ -46,6 +46,7 @@
import org.hibernate.validator.internal.engine.path.NodeImpl;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.hibernate.validator.internal.engine.resolver.CachingTraversableResolverForSingleValidation;
import org.hibernate.validator.internal.engine.resolver.TraverseAllTraversableResolver;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper;
import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager;
Expand Down Expand Up @@ -331,7 +332,7 @@ private ValidationContextBuilder getValidationContextBuilder() {
constraintValidatorManager,
messageInterpolator,
constraintValidatorFactory,
getCachingTraversableResolver(),
wrapTraversableResolverForCachingIfRequired( traversableResolver ),
clockProvider,
failFast
);
Expand Down Expand Up @@ -1283,12 +1284,20 @@ private <V> ValueContext<?, V> getValueContextForValueValidation(ValidationConte
}

/**
* Must be called and stored for the duration of the stack call
* A new instance is returned each time
* Potentially wrap the {@link TraversableResolver} into a caching one.
* <p>
* If {@code traversableResolver} is {@code TraverseAllTraversableResolver.INSTANCE}, we don't wrap it and it is
* returned directly.
* <p>
* If it is not, we wrap the resolver for caching. In this case, a new instance is returned each time and it should
* be used only for the duration of a validation call.
*
* @return The resolver for the duration of a full validation.
*/
private TraversableResolver getCachingTraversableResolver() {
private static TraversableResolver wrapTraversableResolverForCachingIfRequired(TraversableResolver traversableResolver) {
if ( traversableResolver.getClass() == TraverseAllTraversableResolver.class ) {
return traversableResolver;
}
return new CachingTraversableResolverForSingleValidation( traversableResolver );
}

Expand Down
Expand Up @@ -15,9 +15,9 @@
import org.hibernate.validator.internal.util.logging.LoggerFactory;

/**
* An implementation of {@code TraversableResolver} which is aware of JPA 2 and utilizes {@code PersistenceUtil} to get
* An implementation of {@code TraversableResolver} which is aware of JPA 2 and utilizes {@code PersistenceUtil} to
* query the reachability of a property.
* This resolver will be automatically enabled if JPA 2 is on the classpath and the {@code DefaultTraversableResolver} is
* This resolver will be automatically enabled if JPA 2 is on the classpath and the default {@code TraversableResolver} is
* used.
*
* @author Hardy Ferentschik
Expand Down
Expand Up @@ -6,12 +6,10 @@
*/
package org.hibernate.validator.internal.engine.resolver;

import java.lang.annotation.ElementType;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;

import javax.validation.Path;
import javax.validation.TraversableResolver;
import javax.validation.ValidationException;

Expand All @@ -22,13 +20,7 @@
import org.hibernate.validator.internal.util.privilegedactions.LoadClass;
import org.hibernate.validator.internal.util.privilegedactions.NewInstance;

/**
* A JPA 2 aware {@code TraversableResolver}.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public class DefaultTraversableResolver implements TraversableResolver {
public class TraversableResolvers {

private static final Log log = LoggerFactory.make();

Expand All @@ -47,31 +39,28 @@ public class DefaultTraversableResolver implements TraversableResolver {
*/
private static final String JPA_AWARE_TRAVERSABLE_RESOLVER_CLASS_NAME = "org.hibernate.validator.internal.engine.resolver.JPATraversableResolver";

/**
* A JPA 2 aware traversable resolver.
*/
private TraversableResolver jpaTraversableResolver;


public DefaultTraversableResolver() {
detectJPA();
private TraversableResolvers() {
}

/**
* Tries to load detect and load JPA.
* Initializes and returns the default {@link TraversableResolver} depending on the environment.
* <p>
* If JPA 2 is present in the classpath, a {@link JPATraversableResolver} instance is returned.
* <p>
* Otherwise, it returns an instance of the default {@link TraverseAllTraversableResolver}.
*/
private void detectJPA() {
public static TraversableResolver getDefault() {
// check whether we have Persistence on the classpath
Class<?> persistenceClass;
try {
persistenceClass = run( LoadClass.action( PERSISTENCE_CLASS_NAME, this.getClass().getClassLoader() ) );
persistenceClass = run( LoadClass.action( PERSISTENCE_CLASS_NAME, TraversableResolvers.class.getClassLoader() ) );
}
catch (ValidationException e) {
log.debugf(
"Cannot find %s on classpath. Assuming non JPA 2 environment. All properties will per default be traversable.",
PERSISTENCE_CLASS_NAME
);
return;
return getTraverseAllTraversableResolver();
}

// check whether Persistence contains getPersistenceUtil
Expand All @@ -82,7 +71,7 @@ private void detectJPA() {
PERSISTENCE_CLASS_NAME,
PERSISTENCE_UTIL_METHOD
);
return;
return getTraverseAllTraversableResolver();
}

// try to invoke the method to make sure that we are dealing with a complete JPA2 implementation
Expand All @@ -97,7 +86,7 @@ private void detectJPA() {
PERSISTENCE_CLASS_NAME,
PERSISTENCE_UTIL_METHOD
);
return;
return getTraverseAllTraversableResolver();
}

log.debugf(
Expand All @@ -109,32 +98,23 @@ private void detectJPA() {
try {
@SuppressWarnings("unchecked")
Class<? extends TraversableResolver> jpaAwareResolverClass = (Class<? extends TraversableResolver>)
run( LoadClass.action( JPA_AWARE_TRAVERSABLE_RESOLVER_CLASS_NAME, this.getClass().getClassLoader() ) );
jpaTraversableResolver = run( NewInstance.action( jpaAwareResolverClass, "" ) );
run( LoadClass.action( JPA_AWARE_TRAVERSABLE_RESOLVER_CLASS_NAME, TraversableResolvers.class.getClassLoader() ) );
log.debugf(
"Instantiated JPA aware TraversableResolver of type %s.", JPA_AWARE_TRAVERSABLE_RESOLVER_CLASS_NAME
);
return run( NewInstance.action( jpaAwareResolverClass, "" ) );
}
catch (ValidationException e) {
log.debugf(
"Unable to load or instantiate JPA aware resolver %s. All properties will per default be traversable.",
JPA_AWARE_TRAVERSABLE_RESOLVER_CLASS_NAME
);
return getTraverseAllTraversableResolver();
}
}

@Override
public boolean isReachable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return jpaTraversableResolver == null || jpaTraversableResolver.isReachable(
traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType
);
}

@Override
public boolean isCascadable(Object traversableObject, Path.Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject, ElementType elementType) {
return jpaTraversableResolver == null || jpaTraversableResolver.isCascadable(
traversableObject, traversableProperty, rootBeanType, pathToTraversableObject, elementType
);
private static TraversableResolver getTraverseAllTraversableResolver() {
return new TraverseAllTraversableResolver();
}

/**
Expand All @@ -143,7 +123,7 @@ public boolean isCascadable(Object traversableObject, Path.Node traversablePrope
* <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
* privileged actions within HV's protection domain.
*/
private <T> T run(PrivilegedAction<T> action) {
private static <T> T run(PrivilegedAction<T> action) {
return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
}
}
@@ -0,0 +1,39 @@
/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.internal.engine.resolver;

import java.lang.annotation.ElementType;

import javax.validation.Path;
import javax.validation.Path.Node;
import javax.validation.TraversableResolver;


/**
* {@link TraversableResolver} considering that all properties are reachable and cascadable.
* <p>
* This is the default behavior if JPA is not detected in the classpath.
*
* @author Guillaume Smet
*/
public class TraverseAllTraversableResolver implements TraversableResolver {

TraverseAllTraversableResolver() {
}

@Override
public boolean isReachable(Object traversableObject, Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject,
ElementType elementType) {
return true;
}

@Override
public boolean isCascadable(Object traversableObject, Node traversableProperty, Class<?> rootBeanType, Path pathToTraversableObject,
ElementType elementType) {
return true;
}
}
Expand Up @@ -6,19 +6,19 @@
*/
package org.hibernate.validator.test.internal.engine.traversableresolver;

import static org.testng.Assert.assertTrue;

import java.util.Set;

import javax.validation.Configuration;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver;
import org.hibernate.validator.internal.engine.resolver.TraversableResolvers;
import org.hibernate.validator.testutil.TestForIssue;
import org.hibernate.validator.testutils.ValidatorUtil;

import static org.testng.Assert.assertTrue;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;


/**
Expand All @@ -32,7 +32,7 @@ public class JpaTraversableResolverTest {
@BeforeTest
public void setUp() {
Configuration<?> configuration = ValidatorUtil.getConfiguration();
configuration.traversableResolver( new DefaultTraversableResolver() );
configuration.traversableResolver( TraversableResolvers.getDefault() );
validator = configuration.buildValidatorFactory().getValidator();
}

Expand Down

0 comments on commit 8b2f0d6

Please sign in to comment.