Skip to content

HV-1363 support for non-standard Java beans #938

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@
import org.hibernate.validator.cdi.internal.ValidatorFactoryBean;
import org.hibernate.validator.cdi.internal.interceptor.ValidationEnabledAnnotatedType;
import org.hibernate.validator.cdi.internal.interceptor.ValidationInterceptor;
import org.hibernate.validator.cdi.internal.util.GetterPropertyMatcherHelper;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.ExecutableHelper;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.TypeResolutionHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
Expand Down Expand Up @@ -99,6 +99,7 @@ public class ValidationExtension implements Extension {
*/
private final Validator validator;
private final ValidatorFactory validatorFactory;
private final GetterPropertyMatcherHelper getterPropertyMatcherHelper;
private final Set<ExecutableType> globalExecutableTypes;
private final boolean isExecutableValidationEnabled;

Expand All @@ -119,6 +120,7 @@ public ValidationExtension() {
isExecutableValidationEnabled = bootstrap.isExecutableValidationEnabled();
validatorFactory = config.buildValidatorFactory();
validator = validatorFactory.getValidator();
getterPropertyMatcherHelper = GetterPropertyMatcherHelper.forValidationFactory( validatorFactory );

executableHelper = new ExecutableHelper( new TypeResolutionHelper() );
}
Expand Down Expand Up @@ -263,7 +265,7 @@ private <T> void determineConstrainedMethods(AnnotatedType<T> type, BeanDescript
for ( AnnotatedMethod<? super T> annotatedMethod : type.getMethods() ) {
Method method = annotatedMethod.getJavaMember();

boolean isGetter = ReflectionHelper.isGetterMethod( method );
boolean isGetter = getterPropertyMatcherHelper.isProperty( method );

// obtain @ValidateOnExecution from the top-most method in the hierarchy
Method methodForExecutableTypeRetrieval = replaceWithOverriddenOrInterfaceMethod( method, overriddenAndImplementedMethods );
Expand Down Expand Up @@ -316,7 +318,7 @@ private boolean isNonGetterConstrained(Method method, BeanDescriptor beanDescrip
}

private boolean isGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
String propertyName = ReflectionHelper.getPropertyName( method );
String propertyName = getterPropertyMatcherHelper.getPropertyName( method );
PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty( propertyName );
return propertyDescriptor != null && propertyDescriptor.findConstraints()
.declaredOn( ElementType.METHOD )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.cdi.internal.util;

import java.lang.reflect.Method;
import java.lang.reflect.Type;

import javax.validation.ValidatorFactory;

import org.hibernate.validator.HibernateValidatorFactory;
import org.hibernate.validator.internal.properties.DefaultGetterPropertyMatcher;
import org.hibernate.validator.spi.properties.ConstrainableExecutable;
import org.hibernate.validator.spi.properties.GetterPropertyMatcher;

/**
* A wrapper around {@link GetterPropertyMatcher}.
*
* @author Marko Bekhta
*/
public class GetterPropertyMatcherHelper {

private final GetterPropertyMatcher getterPropertyMatcher;

private GetterPropertyMatcherHelper(GetterPropertyMatcher getterPropertyMatcher) {
this.getterPropertyMatcher = getterPropertyMatcher;
}

public boolean isProperty(Method method) {
return getterPropertyMatcher.isProperty( new ConstrainableMethod( method ) );
}

public String getPropertyName(Method method) {
return getterPropertyMatcher.getPropertyName( new ConstrainableMethod( method ) );
}

public static GetterPropertyMatcherHelper forValidationFactory(ValidatorFactory factory) {
GetterPropertyMatcher getterPropertyMatcher;
if ( factory instanceof HibernateValidatorFactory ) {
getterPropertyMatcher = factory.unwrap( HibernateValidatorFactory.class ).getGetterPropertyMatcher();
}
else {
getterPropertyMatcher = new DefaultGetterPropertyMatcher();
}
return new GetterPropertyMatcherHelper( getterPropertyMatcher );
}

private static class ConstrainableMethod implements ConstrainableExecutable {

private final Method method;

private ConstrainableMethod(Method method) {
this.method = method;
}

@Override public Class<?> getReturnType() {
return method.getReturnType();
}

@Override public String getName() {
return method.getName();
}

@Override public Type[] getParameterTypes() {
return method.getParameterTypes();
}

public Method getMethod() {
return method;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.hibernate.validator.cfg.ConstraintMapping;
import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.spi.properties.GetterPropertyMatcher;
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;
Expand Down Expand Up @@ -107,6 +108,15 @@ public interface HibernateValidatorConfiguration extends Configuration<Hibernate
@Incubating
String TEMPORAL_VALIDATION_TOLERANCE = "hibernate.validator.temporal_validation_tolerance";

/**
* Property for configuring the getter property matcher, allowing to set which rules will be applied
* to determine if a method is a valid JavaBean getter.
*
* @since 6.1.0
*/
@Incubating
String GETTER_PROPERTY_MATCHER_CLASSNAME = "hibernate.validator.getter_property_matcher";

/**
* <p>
* Returns the {@link ResourceBundleLocator} used by the
Expand Down Expand Up @@ -311,4 +321,16 @@ public interface HibernateValidatorConfiguration extends Configuration<Hibernate
*/
@Incubating
HibernateValidatorConfiguration constraintValidatorPayload(Object constraintValidatorPayload);

/**
* Allows to set a getter property matcher which defines rules of what a JavaBean getter is.
*
* @param getterPropertyMatcher the {@link GetterPropertyMatcher} to be used
*
* @return {@code this} following the chaining method pattern
*
* @since 6.1.0
*/
@Incubating
HibernateValidatorConfiguration getterPropertyMatcher(GetterPropertyMatcher getterPropertyMatcher);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.hibernate.validator.constraints.ParameterScriptAssert;
import org.hibernate.validator.constraints.ScriptAssert;
import org.hibernate.validator.spi.properties.GetterPropertyMatcher;
import org.hibernate.validator.spi.scripting.ScriptEvaluator;
import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory;

Expand Down Expand Up @@ -47,6 +48,16 @@ public interface HibernateValidatorFactory extends ValidatorFactory {
@Incubating
Duration getTemporalValidationTolerance();

/**
* Returns the getter property matcher which defines what rules of what a JavaBeans getter is.
*
* @return the getter property matcher of current {@link ValidatorFactory}
*
* @since 6.1.0
*/
@Incubating
GetterPropertyMatcher getGetterPropertyMatcher();

/**
* Returns a context for validator configuration via options from the
* Bean Validation API as well as specific ones from Hibernate Validator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
import java.lang.annotation.ElementType;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.security.AccessController;
Expand All @@ -24,7 +20,9 @@
import org.hibernate.validator.cfg.AnnotationDef;
import org.hibernate.validator.cfg.ConstraintDef;
import org.hibernate.validator.internal.metadata.location.ConstraintLocation;
import org.hibernate.validator.internal.util.ExecutableHelper;
import org.hibernate.validator.internal.properties.Callable;
import org.hibernate.validator.internal.properties.Property;
import org.hibernate.validator.internal.properties.javabean.JavaBeanField;
import org.hibernate.validator.internal.util.annotation.AnnotationDescriptor;
import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor;
import org.hibernate.validator.internal.util.logging.Log;
Expand Down Expand Up @@ -58,42 +56,33 @@ static <A extends Annotation> ConfiguredConstraint<A> forType(ConstraintDef<?, A
return new ConfiguredConstraint<>( constraint, ConstraintLocation.forClass( beanType ), ElementType.TYPE );
}

static <A extends Annotation> ConfiguredConstraint<A> forProperty(ConstraintDef<?, A> constraint, Member member) {
if ( member instanceof Field ) {
return new ConfiguredConstraint<>(
constraint,
ConstraintLocation.forField( (Field) member ),
ElementType.FIELD
);
}
else {
return new ConfiguredConstraint<>(
static <A extends Annotation> ConfiguredConstraint<A> forProperty(ConstraintDef<?, A> constraint, Property property) {
return new ConfiguredConstraint<>(
constraint,
ConstraintLocation.forGetter( (Method) member ),
ElementType.METHOD
);
}
ConstraintLocation.forProperty( property ),
property instanceof JavaBeanField ? ElementType.FIELD : ElementType.METHOD
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit surprised we don't also have an abstraction on ElementType. I think it's going to be in the way.

It's pretty obvious from the way we have to special case things whereas the ElementType could be returned by the Property object or the Callable in the case of a method/constructor.

Maybe we could call it ConstraintLocationType.

AFAICS, it's only exposed in BV in the TraversableResolver (and considering it only applies to Java Bean properties (either fields or getters), we could check that the ConstraintLocationType is valid and translate it then call a getElementType() method which would be present on JavaBeanField and JavaBeanGetter) and in ConstraintFinder (we probably could have a translation layer there too).

Did you think about it?

);
}

public static <A extends Annotation> ConfiguredConstraint<A> forParameter(ConstraintDef<?, A> constraint, Executable executable, int parameterIndex) {
public static <A extends Annotation> ConfiguredConstraint<A> forParameter(ConstraintDef<?, A> constraint, Callable callable, int parameterIndex) {
return new ConfiguredConstraint<>(
constraint, ConstraintLocation.forParameter( executable, parameterIndex ), ExecutableHelper.getElementType( executable )
constraint, ConstraintLocation.forParameter( callable, parameterIndex ), callable.isConstructor() ? ElementType.CONSTRUCTOR : ElementType.METHOD
);
}

public static <A extends Annotation> ConfiguredConstraint<A> forExecutable(ConstraintDef<?, A> constraint, Executable executable) {
public static <A extends Annotation> ConfiguredConstraint<A> forExecutable(ConstraintDef<?, A> constraint, Callable callable) {
return new ConfiguredConstraint<>(
constraint, ConstraintLocation.forReturnValue( executable ), ExecutableHelper.getElementType( executable )
constraint, ConstraintLocation.forReturnValue( callable ), callable.isConstructor() ? ElementType.CONSTRUCTOR : ElementType.METHOD
);
}

public static <A extends Annotation> ConfiguredConstraint<A> forCrossParameter(ConstraintDef<?, A> constraint, Executable executable) {
public static <A extends Annotation> ConfiguredConstraint<A> forCrossParameter(ConstraintDef<?, A> constraint, Callable callable) {
return new ConfiguredConstraint<>(
constraint, ConstraintLocation.forCrossParameter( executable ), ExecutableHelper.getElementType( executable )
constraint, ConstraintLocation.forCrossParameter( callable ), callable.isConstructor() ? ElementType.CONSTRUCTOR : ElementType.METHOD
);
}

public static <A extends Annotation> ConfiguredConstraint<A> forTypeArgument(ConstraintDef<?, A> constraint,ConstraintLocation delegate, TypeVariable<?> typeArgument, Type typeOfAnnotatedElement) {
public static <A extends Annotation> ConfiguredConstraint<A> forTypeArgument(ConstraintDef<?, A> constraint, ConstraintLocation delegate, TypeVariable<?> typeArgument, Type typeOfAnnotatedElement) {
return new ConfiguredConstraint<>(
constraint,
ConstraintLocation.forTypeArgument( delegate, typeArgument, typeOfAnnotatedElement ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
*/
package org.hibernate.validator.internal.cfg.context;

import java.lang.reflect.Constructor;

import org.hibernate.validator.cfg.context.ConstructorConstraintMappingContext;
import org.hibernate.validator.internal.properties.Callable;

/**
* Constraint mapping creational context representing a constructor.
Expand All @@ -17,15 +16,15 @@
*/
class ConstructorConstraintMappingContextImpl extends ExecutableConstraintMappingContextImpl implements ConstructorConstraintMappingContext {

<T> ConstructorConstraintMappingContextImpl(TypeConstraintMappingContextImpl<T> typeContext, Constructor<T> constructor) {
<T> ConstructorConstraintMappingContextImpl(TypeConstraintMappingContextImpl<T> typeContext, Callable constructor) {
super( typeContext, constructor );
}

@Override
public ConstructorConstraintMappingContext ignoreAnnotations(boolean ignoreAnnotations) {
typeContext.mapping
.getAnnotationProcessingOptions()
.ignoreConstraintAnnotationsOnMember( executable, ignoreAnnotations );
.ignoreConstraintAnnotationsOnMember( callable, ignoreAnnotations );

return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ final class CrossParameterConstraintMappingContextImpl

@Override
public CrossParameterConstraintMappingContext constraint(ConstraintDef<?, ?> definition) {
super.addConstraint( ConfiguredConstraint.forCrossParameter( definition, executableContext.getExecutable() ) );
super.addConstraint( ConfiguredConstraint.forCrossParameter( definition, executableContext.getCallable() ) );
return this;
}

@Override
public CrossParameterConstraintMappingContext ignoreAnnotations(boolean ignoreAnnotations) {
mapping.getAnnotationProcessingOptions().ignoreConstraintAnnotationsForCrossParameterConstraint(
executableContext.getExecutable(), ignoreAnnotations
executableContext.getCallable(), ignoreAnnotations
);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.hibernate.validator.internal.util.TypeResolutionHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.spi.properties.GetterPropertyMatcher;

/**
* Default implementation of {@link ConstraintMapping}.
Expand All @@ -40,13 +41,15 @@ public class DefaultConstraintMapping implements ConstraintMapping {

private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

private final GetterPropertyMatcher getterPropertyMatcher;
private final AnnotationProcessingOptionsImpl annotationProcessingOptions;
private final Set<Class<?>> configuredTypes;
private final Set<TypeConstraintMappingContextImpl<?>> typeContexts;
private final Set<Class<?>> definedConstraints;
private final Set<ConstraintDefinitionContextImpl<?>> constraintContexts;

public DefaultConstraintMapping() {
public DefaultConstraintMapping(GetterPropertyMatcher getterPropertyMatcher) {
this.getterPropertyMatcher = getterPropertyMatcher;
this.annotationProcessingOptions = new AnnotationProcessingOptionsImpl();
this.configuredTypes = newHashSet();
this.typeContexts = newHashSet();
Expand All @@ -62,7 +65,7 @@ public final <C> TypeConstraintMappingContext<C> type(Class<C> type) {
throw LOG.getBeanClassHasAlreadyBeConfiguredViaProgrammaticApiException( type );
}

TypeConstraintMappingContextImpl<C> typeContext = new TypeConstraintMappingContextImpl<>( this, type );
TypeConstraintMappingContextImpl<C> typeContext = new TypeConstraintMappingContextImpl<>( this, type, getterPropertyMatcher );
typeContexts.add( typeContext );
configuredTypes.add( type );

Expand Down
Loading