diff --git a/engine/src/main/java/org/hibernate/validator/HibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/HibernateValidatorConfiguration.java index 37ef2cba01..2d0e01f282 100644 --- a/engine/src/main/java/org/hibernate/validator/HibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/HibernateValidatorConfiguration.java @@ -18,6 +18,7 @@ import javax.validation.valueextraction.ValueExtractor; import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMapping; import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; @@ -64,8 +65,8 @@ public interface HibernateValidatorConfiguration extends Configuration. + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Facet of a constraint mapping creational context which allows to mark the underlying + * element as to be validated in a cascaded way. + * + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + */ +public interface Cascadable> { + + /** + * Marks the current element (property, parameter etc.) as cascadable. + * + * @return The current creational context following the method chaining pattern. + */ + C valid(String mapping); + + /** + * Adds a group conversion for this cascadable element. Several conversions may be configured for one element. + * + * @param from the source group of the conversion to be configured + * + * @return a creational context allow to set the target group of the conversion + */ + GroupConversionTargetContext convertGroup(Class from); +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadableContainerElementConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadableContainerElementConstraintMappingContext.java new file mode 100644 index 0000000000..9348ff43a3 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadableContainerElementConstraintMappingContext.java @@ -0,0 +1,14 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * @author Marko Bekhta + */ +public interface CascadableContainerElementConstraintMappingContext extends ContainerElementConstraintMappingContext, + Cascadable { +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadablePropertyConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadablePropertyConstraintMappingContext.java new file mode 100644 index 0000000000..e40ba67905 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/CascadablePropertyConstraintMappingContext.java @@ -0,0 +1,22 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Constraint mapping creational context representing a property of a property holder. Allows + * to place constraints on the property, mark the property as cascadable and to + * navigate to other constraint targets. + * + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Marko Bekhta + */ +public interface CascadablePropertyConstraintMappingContext extends Constrainable, + PropertyHolderTarget, + PropertyTarget, + Cascadable { +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/Constrainable.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/Constrainable.java new file mode 100644 index 0000000000..00b03b7aaa --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/Constrainable.java @@ -0,0 +1,27 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +import org.hibernate.validator.cfg.ConstraintDef; + +/** + * Facet of a property holder constraint mapping creational context which allows to place + * constraints on the underlying element. + * + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + */ +public interface Constrainable> { + /** + * Adds a new constraint. + * + * @param definition The constraint to add. + * + * @return The current creational context following the method chaining pattern. + */ + C constraint(ConstraintDef definition); +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementConstraintMappingContext.java new file mode 100644 index 0000000000..763096706a --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementConstraintMappingContext.java @@ -0,0 +1,21 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Constraint mapping creational context representing a type argument of a property, parameter or method return value + * with a generic (return) type. Allows to place constraints on that type argument, mark it as cascadable and to + * navigate to other constraint targets. + * + * @author Gunnar Morling + * @since 6.0 + */ +public interface ContainerElementConstraintMappingContext extends Constrainable, + PropertyTarget, + PropertyHolderTarget, + ContainerElementTarget { +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementTarget.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementTarget.java new file mode 100644 index 0000000000..df37095186 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/ContainerElementTarget.java @@ -0,0 +1,27 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +import org.hibernate.validator.Incubating; + +/** + * Facet of a constraint mapping creational context which allows to select a type argument or the component type of the + * (return) type of the current property as target for the next operations. + * + * @author Marko Bekhta + */ +@Incubating +public interface ContainerElementTarget { + + ContainerElementConstraintMappingContext containerElementType(Class type); + + ContainerElementConstraintMappingContext containerElementType(Class type, int index, int... nestedIndexes); + + CascadableContainerElementConstraintMappingContext containerElementType(String mapping); + + CascadableContainerElementConstraintMappingContext containerElementType(String mapping, int index, int... nestedIndexes); +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/GroupConversionTargetContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/GroupConversionTargetContext.java new file mode 100644 index 0000000000..0cbffc5523 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/GroupConversionTargetContext.java @@ -0,0 +1,25 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Creational context which allows to set the target group of a group conversion configured via + * {@link Cascadable#convertGroup(Class)}. + * + * @author Gunnar Morling + */ +public interface GroupConversionTargetContext { + + /** + * Sets the target group of the conversion to be configured. + * + * @param to the target group of the conversion + * + * @return The current creational context following the method chaining pattern. + */ + C to(Class to); +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyConstraintMappingContext.java new file mode 100644 index 0000000000..e0c8898f70 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyConstraintMappingContext.java @@ -0,0 +1,22 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Constraint mapping creational context representing a property of a property holder. Allows + * to place constraints on the property, mark the property as cascadable and to + * navigate to other constraint targets. + * + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Marko Bekhta + */ +public interface PropertyConstraintMappingContext extends Constrainable, + PropertyTarget, + PropertyHolderTarget, + ContainerElementTarget { +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMapping.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMapping.java new file mode 100644 index 0000000000..a0cc6ef25f --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMapping.java @@ -0,0 +1,28 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Represents a property holder constraint mapping configured via the programmatic API. + * + * @author Marko Bekhta + */ +public interface PropertyHolderConstraintMapping { + + /** + * Starts defining constraints for the specified unique mapping name. Each mapping name may only be used + * once within all property holder constraint mappings used for configuring one validator factory. + * + * @param propertyHolderMappingName The mapping name for which to define constraints. All constraints + * defined after calling this method are added to the bean of the type {@code beanClass} until the + * next call of {@link this#type(String)}. + * + * @return Instance allowing for defining constraints for the specified property holder mapping name. + */ + TypeConstraintMappingContext type(String propertyHolderMappingName); + +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMappingContext.java new file mode 100644 index 0000000000..d8576a5b88 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderConstraintMappingContext.java @@ -0,0 +1,23 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Constraint mapping creational context representing a property of a property holder. Allows + * to place constraints on the property, mark the property as cascadable and to + * navigate to other constraint targets. + * + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Marko Bekhta + */ +public interface PropertyHolderConstraintMappingContext extends Constrainable, + PropertyHolderTarget, + PropertyTarget, + Cascadable { + +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderTarget.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderTarget.java new file mode 100644 index 0000000000..20e8b5207e --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyHolderTarget.java @@ -0,0 +1,33 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Facet of a property holder constraint mapping creational context which allows to specify the + * property to which the next operations should be apply. + * + * @author Marko Bekhta + */ +public interface PropertyHolderTarget { + + /** + * Defines a property, that is a property holder itself, to which the next operations shall apply. + *

+ * Until this method is called constraints apply on property holder level. After calling this method constraints + * apply on the specified property with the given property type. + *

+ *

+ * A given property may only be configured once. + *

+ * + * @param property The property holder on which to apply the following constraints. + * + * @return A creational context representing the selected property. + */ + PropertyHolderConstraintMappingContext propertyHolder(String property); + +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyTarget.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyTarget.java new file mode 100644 index 0000000000..687589785a --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/PropertyTarget.java @@ -0,0 +1,34 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Facet of a property holder constraint mapping creational context which allows to specify the + * property to which the next operations should be apply. + * + * @author Marko Bekhta + */ +public interface PropertyTarget { + + /** + * Defines a property to which the next operations shall apply. + *

+ * Until this method is called constraints apply on property holder level. After calling this method constraints + * apply on the specified property with the given property type. + *

+ *

+ * A given property may only be configured once. + *

+ * + * @param property The property on which to apply the following constraints. + * @param propertyType The type of the specified property. + * + * @return A creational context representing the selected property. + */ + PropertyConstraintMappingContext property(String property, Class propertyType); + +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/TypeConstraintMappingContext.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/TypeConstraintMappingContext.java new file mode 100644 index 0000000000..5c8cf91e5a --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/TypeConstraintMappingContext.java @@ -0,0 +1,27 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cfg.propertyholder; + +/** + * Constraint mapping creational context representing a type. Allows place + * class-level constraints on that type, define its default group sequence (and provider) + * and to navigate to other constraint targets. + * + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Gunnar Morling + */ +public interface TypeConstraintMappingContext extends PropertyTarget, PropertyHolderTarget { + + /** + * Defines the default group sequence for current type. + * + * @param defaultGroupSequence the default group sequence. + * + * @return The current creational context following the method chaining pattern. + */ + TypeConstraintMappingContext defaultGroupSequence(Class... defaultGroupSequence); +} diff --git a/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/package-info.java b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/package-info.java new file mode 100644 index 0000000000..728ce63321 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/cfg/propertyholder/package-info.java @@ -0,0 +1,13 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ + +/** + *

Contains facet and creational context interfaces forming the API for programmatic constraint + * definition for property holders validation.

+ *

This package is part of the public Hibernate Validator API.

+ */ +package org.hibernate.validator.cfg.propertyholder; diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/CascadableConstraintMappingContextImplBase.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/CascadableConstraintMappingContextImplBase.java index a8f40b8944..9838aed7b6 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/CascadableConstraintMappingContextImplBase.java +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/CascadableConstraintMappingContextImplBase.java @@ -11,11 +11,11 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -24,7 +24,7 @@ import org.hibernate.validator.cfg.context.ContainerElementTarget; import org.hibernate.validator.cfg.context.GroupConversionTargetContext; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; @@ -160,19 +160,15 @@ protected Set> getTypeArgumentConstraints(ConstraintHelper con } protected CascadingMetaDataBuilder getCascadingMetaDataBuilder() { - Map, CascadingMetaDataBuilder> typeParametersCascadingMetaData = containerElementContexts.values().stream() - .filter( c -> c.getContainerElementCascadingMetaDataBuilder() != null ) - .collect( Collectors.toMap( c -> c.getContainerElementCascadingMetaDataBuilder().getTypeParameter(), - c -> c.getContainerElementCascadingMetaDataBuilder() ) ); - - for ( ContainerElementConstraintMappingContextImpl typeArgumentContext : containerElementContexts.values() ) { - CascadingMetaDataBuilder cascadingMetaDataBuilder = typeArgumentContext.getContainerElementCascadingMetaDataBuilder(); - if ( cascadingMetaDataBuilder != null ) { - typeParametersCascadingMetaData.put( cascadingMetaDataBuilder.getTypeParameter(), cascadingMetaDataBuilder ); - } - } - - return CascadingMetaDataBuilder.annotatedObject( configuredType, isCascading, typeParametersCascadingMetaData, groupConversions ); + return CascadingMetaDataBuilder.annotatedObject( + configuredType, + isCascading, + containerElementContexts.values().stream() + .map( ContainerElementConstraintMappingContextImpl::getContainerElementCascadingMetaDataBuilder ) + .filter( Objects::nonNull ) + .collect( Collectors.toList() ), + groupConversions + ); } private static class ContainerElementPathKey { diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ContainerElementConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ContainerElementConstraintMappingContextImpl.java index 7a00c66cd4..4bb4654157 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ContainerElementConstraintMappingContextImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ContainerElementConstraintMappingContextImpl.java @@ -17,7 +17,6 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -33,7 +32,7 @@ import org.hibernate.validator.cfg.context.ReturnValueTarget; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.core.MetaConstraints; @@ -219,15 +218,15 @@ protected ConstraintType getConstraintType() { } CascadingMetaDataBuilder getContainerElementCascadingMetaDataBuilder() { - return new CascadingMetaDataBuilder( - parentLocation.getTypeForValidatorResolution(), - typeParameter, - isCascading, - nestedContainerElementContexts.values() - .stream() - .map( ContainerElementConstraintMappingContextImpl::getContainerElementCascadingMetaDataBuilder ) - .collect( Collectors.toMap( CascadingMetaDataBuilder::getTypeParameter, Function.identity() ) ), - groupConversions + return CascadingMetaDataBuilder.typeArgument( + parentLocation.getTypeForValidatorResolution(), + typeParameter, + isCascading, + nestedContainerElementContexts.values() + .stream() + .map( ContainerElementConstraintMappingContextImpl::getContainerElementCascadingMetaDataBuilder ) + .collect( Collectors.toList() ), + groupConversions ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ExecutableConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ExecutableConstraintMappingContextImpl.java index 503c284637..d451cae961 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ExecutableConstraintMappingContextImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/context/ExecutableConstraintMappingContextImpl.java @@ -16,7 +16,7 @@ import org.hibernate.validator.cfg.context.ParameterConstraintMappingContext; import org.hibernate.validator.cfg.context.ReturnValueConstraintMappingContext; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/CascadablePropertyHolderConstraintMappingContextImplBase.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/CascadablePropertyHolderConstraintMappingContextImplBase.java new file mode 100644 index 0000000000..17a55d0ada --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/CascadablePropertyHolderConstraintMappingContextImplBase.java @@ -0,0 +1,61 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap; + +import java.util.Map; + +import org.hibernate.validator.cfg.propertyholder.Cascadable; +import org.hibernate.validator.cfg.propertyholder.GroupConversionTargetContext; +import org.hibernate.validator.internal.metadata.aggregated.cascading.PropertyHolderCascadingMetaDataBuilder; + +/** + * Base class for all implementations of cascadable context types. + * + * @author Gunnar Morling + * @author Marko Bekhta + */ +abstract class CascadablePropertyHolderConstraintMappingContextImplBase> + extends PropertyConstraintMappingContextImplBase implements Cascadable, GroupConversionTargetContextHelper { + + protected final Map, Class> groupConversions = newHashMap(); + protected boolean isCascading; + private String mapping; + + CascadablePropertyHolderConstraintMappingContextImplBase(PropertyHolderConstraintMappingImpl mapping, String property) { + super( mapping, property ); + } + + protected abstract C getThis(); + + @Override + public void addGroupConversion(Class from, Class to) { + groupConversions.put( from, to ); + } + + @Override + public C valid(String mapping) { + this.mapping = mapping; + this.isCascading = true; + + return getThis(); + } + + @Override + public GroupConversionTargetContext convertGroup(Class from) { + return new GroupConversionTargetContextImpl<>( from, getThis(), this ); + } + + public boolean isCascading() { + return isCascading; + } + + protected PropertyHolderCascadingMetaDataBuilder getCascadingMetaDataBuilder() { + return PropertyHolderCascadingMetaDataBuilder.simplePropertyHolder( mapping, isCascading, groupConversions ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ConstraintContextImplBase.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ConstraintContextImplBase.java new file mode 100644 index 0000000000..925a91c7a5 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ConstraintContextImplBase.java @@ -0,0 +1,47 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; + +import java.util.Set; + +import org.hibernate.validator.cfg.propertyholder.TypeConstraintMappingContext; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; + +/** + * Base class for implementations of constraint-related context types. + * + * @author Marko Bekhta + */ +abstract class ConstraintContextImplBase { + + protected final PropertyHolderConstraintMappingImpl mapping; + + private final Set> constraints; + + public ConstraintContextImplBase(PropertyHolderConstraintMappingImpl mapping) { + this.mapping = mapping; + this.constraints = newHashSet(); + } + + public TypeConstraintMappingContext type(String propertyHolderMappingName) { + return mapping.type( propertyHolderMappingName ); + } + + protected PropertyHolderConstraintMappingImpl getConstraintMapping() { + return mapping; + } + + protected void addConstraint(MetaConstraintBuilder constraint) { + constraints.add( constraint ); + } + + protected Set> getConstraints() { + return constraints; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ContainerElementConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ContainerElementConstraintMappingContextImpl.java new file mode 100644 index 0000000000..ce87a8576f --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/ContainerElementConstraintMappingContextImpl.java @@ -0,0 +1,198 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.cfg.propertyholder.CascadableContainerElementConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.ContainerElementConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.ContainerElementTarget; +import org.hibernate.validator.cfg.propertyholder.PropertyConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMappingContext; +import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.core.MetaConstraint; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.location.ConstraintLocation; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * Context for simple container elements that are not property holders themselve. Hence no cascading on them should be allowed. + * + * @author Marko Bekhta + */ +public class ContainerElementConstraintMappingContextImpl implements + ContainerElementConstraintMappingContext { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final PropertyHolderTypeConstraintMappingContextImpl typeContext; + private final ContainerElementTarget parentContainerElementTarget; + + /** + * The type configured through this context. Either a {@code ParameterizedType} or an array type. + */ + private final Class configuredType; + + /** + * The index of the type parameter configured through this context. Always 0 in case of an array type. + */ + private final int index; + + /** + * The type parameter configured through this context. An instance of {@link ArrayElement} in case of an array type. + */ + private final TypeVariable typeParameter; + + private final Class containerElementType; + + /** + * Contexts for configuring nested container elements, if any. Indexed by type parameter. + */ + protected final Map nestedContainerElementContexts; + + private final Set> constraints; + + ContainerElementConstraintMappingContextImpl( + PropertyHolderTypeConstraintMappingContextImpl typeContext, + ContainerElementTarget parentContainerElementTarget, + Class type, + int index, + Class containerElementType) { + this.typeContext = typeContext; + this.parentContainerElementTarget = parentContainerElementTarget; + + // HV-1428 Container element support is disabled for arrays + if ( type.isArray() ) { + throw LOG.getContainerElementConstraintsAndCascadedValidationNotSupportedOnArraysException( type ); + } + + TypeVariable[] typeParameters = type.getTypeParameters(); + + if ( index > typeParameters.length - 1 ) { + throw LOG.getInvalidTypeArgumentIndexException( type, index ); + } + else { + this.typeParameter = typeParameters[index]; + } + + this.configuredType = type; + this.index = index; + this.containerElementType = containerElementType; + this.constraints = new HashSet<>(); + this.nestedContainerElementContexts = new HashMap<>(); + } + + @Override + public PropertyHolderConstraintMappingContext propertyHolder(String property) { + return typeContext.propertyHolder( property ); + } + + @Override + public PropertyConstraintMappingContext property(String property, Class propertyType) { + return typeContext.property( property, propertyType ); + } + + @Override + public ContainerElementConstraintMappingContext containerElementType(Class containerElementType) { + return parentContainerElementTarget.containerElementType( containerElementType ); + } + + @Override + public ContainerElementConstraintMappingContext containerElementType(Class containerElementType, int index, int... nestedIndexes) { + return parentContainerElementTarget.containerElementType( containerElementType, index, nestedIndexes ); + } + + @Override + public CascadableContainerElementConstraintMappingContext containerElementType(String mapping) { + return parentContainerElementTarget.containerElementType( mapping ); + } + + @Override + public CascadableContainerElementConstraintMappingContext containerElementType(String mapping, int index, int... nestedIndexes) { + return parentContainerElementTarget.containerElementType( mapping, index, nestedIndexes ); + } + + ContainerElementConstraintMappingContext nestedContainerElement(int[] nestedIndexes) { + + ContainerElementConstraintMappingContextImpl nestedContext = nestedContainerElementContexts.get( nestedIndexes[0] ); + if ( nestedContext == null ) { + nestedContext = new ContainerElementConstraintMappingContextImpl( + typeContext, + parentContainerElementTarget, + null, + nestedIndexes[0], + null + ); + nestedContainerElementContexts.put( nestedIndexes[0], nestedContext ); + } + + if ( nestedIndexes.length > 1 ) { + return nestedContext.nestedContainerElement( Arrays.copyOfRange( nestedIndexes, 1, nestedIndexes.length ) ); + } + else { + return nestedContext; + } + } + + @Override + public ContainerElementConstraintMappingContext constraint(ConstraintDef definition) { + constraints.add( + new MetaConstraintBuilder( + definition + ) + // ConfiguredConstraint.forTypeArgument( definition, parentLocation, typeParameter, getContainerElementType() ) + ); + return this; + } + + CascadingMetaDataBuilder getContainerElementCascadingMetaDataBuilder(ConstraintLocation parentLocation) { + ConstraintLocation location = getCurrentConstraintLocation( parentLocation ); + return CascadingMetaDataBuilder.typeArgument( + parentLocation.getTypeForValidatorResolution(), + typeParameter, + false, + nestedContainerElementContexts.values() + .stream() + .map( context -> context.getContainerElementCascadingMetaDataBuilder( location ) ) + .collect( Collectors.toList() ), + Collections.emptyMap() + ); + } + + Set> build(ConstraintHelper constraintHelper, TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, ConstraintLocation parentLocation) { + ConstraintLocation location = getCurrentConstraintLocation( parentLocation ); + return Stream.concat( + constraints.stream() + .map( c -> c.build( typeResolutionHelper, constraintHelper, valueExtractorManager, location ) ), + nestedContainerElementContexts.values() + .stream() + .map( c -> c.build( constraintHelper, typeResolutionHelper, valueExtractorManager, location ) ) + .flatMap( Set::stream ) + ) + .collect( Collectors.toSet() ); + } + + private ConstraintLocation getCurrentConstraintLocation(ConstraintLocation parentLocation) { + return ConstraintLocation.forTypeArgument( parentLocation, typeParameter, containerElementType ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextHelper.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextHelper.java new file mode 100644 index 0000000000..28f0df555a --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextHelper.java @@ -0,0 +1,24 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import org.hibernate.validator.cfg.propertyholder.GroupConversionTargetContext; + +/** + * Adds internal method to {@link GroupConversionTargetContext} that allows adding group conversions. + * + * @author Marko Bekhta + */ +interface GroupConversionTargetContextHelper { + /** + * Adds a group conversion for this element. + * + * @param from the source group of the conversion + * @param to the target group of the conversion + */ + void addGroupConversion(Class from, Class to); +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextImpl.java new file mode 100644 index 0000000000..7a57badafe --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/GroupConversionTargetContextImpl.java @@ -0,0 +1,33 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import org.hibernate.validator.cfg.propertyholder.GroupConversionTargetContext; + +/** + * Context allowing to set the target of a group conversion. + * + * @author Gunnar Morling + */ +class GroupConversionTargetContextImpl implements GroupConversionTargetContext { + + private final C cascadableContext; + private final Class from; + private final GroupConversionTargetContextHelper target; + + GroupConversionTargetContextImpl(Class from, C cascadableContext, GroupConversionTargetContextHelper target) { + this.from = from; + this.cascadableContext = cascadableContext; + this.target = target; + } + + @Override + public C to(Class to) { + target.addGroupConversion( from, to ); + return cascadableContext; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImpl.java new file mode 100644 index 0000000000..c961932378 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImpl.java @@ -0,0 +1,185 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.cfg.propertyholder.CascadableContainerElementConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.ContainerElementConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.PropertyConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMappingContext; +import org.hibernate.validator.internal.metadata.aggregated.cascading.NonCascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.propertyholder.ConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.metadata.raw.propertyholder.SimpleConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.util.Contracts; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * Constraint mapping creational context which allows to configure the constraints for one proeprty holder simple property. + * The type of the property should be a simple type or a collection and no cascading is allowed for it. + * + * @author Marko Bekhta + */ +final class PropertyConstraintMappingContextImpl extends PropertyConstraintMappingContextImplBase + implements PropertyConstraintMappingContext { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final PropertyHolderTypeConstraintMappingContextImpl typeContext; + + private final Map containerElementContexts = new HashMap<>(); + private final Set configuredPaths = new HashSet<>(); + + private final Class type; + + + PropertyConstraintMappingContextImpl(PropertyHolderTypeConstraintMappingContextImpl typeContext, String property, Class type) { + super( typeContext.getConstraintMapping(), property ); + this.typeContext = typeContext; + this.type = type; + } + + @Override + public PropertyConstraintMappingContext constraint(ConstraintDef definition) { + super.addConstraint( + new MetaConstraintBuilder( + definition + ) + ); + return this; + } + + @Override + public PropertyConstraintMappingContext property(String property, Class propertyType) { + return typeContext.property( property, propertyType ); + } + + @Override + public PropertyHolderConstraintMappingContext propertyHolder(String property) { + return typeContext.propertyHolder( property ); + } + + @Override + public ContainerElementConstraintMappingContext containerElementType(Class containerElementType) { + if ( type.getTypeParameters().length > 1 ) { + throw LOG.getNoTypeArgumentIndexIsGivenForTypeWithMultipleTypeArgumentsException( type ); + } + return containerElementType( containerElementType, 0 ); + } + + @Override + public ContainerElementConstraintMappingContext containerElementType(Class containerElementType, int index, int... nestedIndexes) { + Contracts.assertTrue( index >= 0, "Type argument index must not be negative" ); + + // HV-1428 Container element support is disabled for arrays + if ( type.isArray() ) { + throw LOG.getContainerElementConstraintsAndCascadedValidationNotSupportedOnArraysException( type ); + } + + if ( ( type.getTypeParameters().length == 0 ) ) { + throw LOG.getTypeIsNotAParameterizedNorArrayTypeException( type ); + } + + ContainerElementPathKey key = new ContainerElementPathKey( index, nestedIndexes ); + boolean configuredBefore = !configuredPaths.add( key ); + if ( configuredBefore ) { + throw LOG.getContainerElementTypeHasAlreadyBeenConfiguredViaProgrammaticApiException( + // TODO: add another exception method to log + null + ); + } + + // As we already checked that the specific path was not yet configured we should not worry about returning the same context here, + // as it means that there are some nested indexes which make a difference, And at the end a new context will be returned by call + // to containerElementContext#nestedContainerElement(). + ContainerElementConstraintMappingContextImpl containerElementContext = containerElementContexts.get( index ); + if ( containerElementContext == null ) { + containerElementContext = new ContainerElementConstraintMappingContextImpl( typeContext, this, type, index, containerElementType ); + containerElementContexts.put( index, containerElementContext ); + } + + if ( nestedIndexes.length > 0 ) { + return containerElementContext.nestedContainerElement( nestedIndexes ); + } + else { + return containerElementContext; + } + } + + @Override + public CascadableContainerElementConstraintMappingContext containerElementType(String mapping) { + return containerElementType( mapping, 0 ); + } + + @Override + public CascadableContainerElementConstraintMappingContext containerElementType(String mapping, int index, int... nestedIndexes) { + return null; + } + + @Override + protected ConstrainedPropertyHolderElementBuilder build() { + return new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + property, + type, + getConstraints(), + Collections.emptySet(), + NonCascadingMetaDataBuilder.INSTANCE + ); + } + + private static class ContainerElementPathKey { + + private final int index; + private final int[] nestedIndexes; + + public ContainerElementPathKey(int index, int[] nestedIndexes) { + this.index = index; + this.nestedIndexes = nestedIndexes; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + index; + result = prime * result + Arrays.hashCode( nestedIndexes ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + ContainerElementPathKey other = (ContainerElementPathKey) obj; + if ( index != other.index ) { + return false; + } + if ( !Arrays.equals( nestedIndexes, other.nestedIndexes ) ) { + return false; + } + return true; + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImplBase.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImplBase.java new file mode 100644 index 0000000000..ef7858b292 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyConstraintMappingContextImplBase.java @@ -0,0 +1,26 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import org.hibernate.validator.internal.metadata.raw.propertyholder.ConstrainedPropertyHolderElementBuilder; + +/** + * Base class for implementations of constraint mapping creational context types. + * + * @author Marko Bekhta + */ +abstract class PropertyConstraintMappingContextImplBase extends ConstraintContextImplBase { + + protected final String property; + + PropertyConstraintMappingContextImplBase(PropertyHolderConstraintMappingImpl mapping, String property) { + super( mapping ); + this.property = property; + } + + protected abstract ConstrainedPropertyHolderElementBuilder build(); +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingContextImpl.java new file mode 100644 index 0000000000..48998844b7 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingContextImpl.java @@ -0,0 +1,70 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import java.util.Collections; + +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.cfg.propertyholder.PropertyConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMappingContext; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.propertyholder.CascadingConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.metadata.raw.propertyholder.ConstrainedPropertyHolderElementBuilder; + +/** + * Constraint mapping creational context which allows to configure the constraints for one property holder + * property that is a property holder itself. Hence cascading for it is allowed. + * + * @author Marko Bekhta + */ +final class PropertyHolderConstraintMappingContextImpl + extends CascadablePropertyHolderConstraintMappingContextImplBase + implements PropertyHolderConstraintMappingContext { + + private final PropertyHolderTypeConstraintMappingContextImpl typeContext; + + PropertyHolderConstraintMappingContextImpl(PropertyHolderTypeConstraintMappingContextImpl typeContext, String property) { + super( typeContext.getConstraintMapping(), property ); + this.typeContext = typeContext; + } + + @Override + protected PropertyHolderConstraintMappingContextImpl getThis() { + return this; + } + + @Override + public PropertyHolderConstraintMappingContext constraint(ConstraintDef definition) { + super.addConstraint( + new MetaConstraintBuilder( definition ) + ); + + return this; + } + + @Override + public PropertyConstraintMappingContext property(String property, Class propertyType) { + return typeContext.property( property, propertyType ); + } + + @Override + public PropertyHolderConstraintMappingContext propertyHolder(String property) { + return typeContext.propertyHolder( property ); + } + + @Override + protected ConstrainedPropertyHolderElementBuilder build() { + return new CascadingConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + property, + getConstraints(), + Collections.emptySet(), + getCascadingMetaDataBuilder() + ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingImpl.java new file mode 100644 index 0000000000..b2e0189afa --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderConstraintMappingImpl.java @@ -0,0 +1,80 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; +import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; + +import java.lang.invoke.MethodHandles; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.validation.valueextraction.ValueExtractor; + +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMapping; +import org.hibernate.validator.cfg.propertyholder.TypeConstraintMappingContext; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.util.Contracts; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * Implementation of {@link PropertyHolderConstraintMapping}. + * + * @author Marko Bekhta + */ +public class PropertyHolderConstraintMappingImpl implements PropertyHolderConstraintMapping { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final Set configuredMappingNames; + private final Set typeContexts; + + public PropertyHolderConstraintMappingImpl() { + this.configuredMappingNames = newHashSet(); + this.typeContexts = newHashSet(); + } + + @Override + public final TypeConstraintMappingContext type(String propertyHolderMappingName) { + Contracts.assertNotNull( propertyHolderMappingName, MESSAGES.mappingNameMustNotBeNull() ); + + if ( configuredMappingNames.contains( propertyHolderMappingName ) ) { + throw LOG.getPropertyHolderMappingHasAlreadyBeenConfiguredViaProgrammaticApiException( propertyHolderMappingName ); + } + + PropertyHolderTypeConstraintMappingContextImpl typeContext = new PropertyHolderTypeConstraintMappingContextImpl( this, propertyHolderMappingName ); + typeContexts.add( typeContext ); + configuredMappingNames.add( propertyHolderMappingName ); + + return typeContext; + } + + public Set getConfiguredMappingNames() { + return configuredMappingNames; + } + + /** + * Returns all property holder configurations configured through this constraint mapping. + * + * @param constraintHelper constraint helper required for building constraint descriptors + * @param typeResolutionHelper type resolution helper + * @param valueExtractorManager the {@link ValueExtractor} manager + * + * @return a set of {@link PropertyHolderConfiguration}s with an element for each type configured through this mapping + */ + public Set getPropertyHolderConfigurations(ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager) { + return typeContexts.stream() + .map( context -> context.build() ) + .collect( Collectors.toSet() ); + } + +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderTypeConstraintMappingContextImpl.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderTypeConstraintMappingContextImpl.java new file mode 100644 index 0000000000..3810d59694 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/PropertyHolderTypeConstraintMappingContextImpl.java @@ -0,0 +1,116 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.cfg.propertyholder; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; +import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; + +import java.lang.invoke.MethodHandles; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import org.hibernate.validator.cfg.propertyholder.PropertyConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMappingContext; +import org.hibernate.validator.cfg.propertyholder.TypeConstraintMappingContext; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.propertyholder.ConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.util.Contracts; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * Constraint mapping creational context which allows to configure the class-level constraints for one bean. + * + * @author Hardy Ferentschik + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Marko Bekhta + */ +public final class PropertyHolderTypeConstraintMappingContextImpl extends ConstraintContextImplBase + implements TypeConstraintMappingContext { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final String propertyHolderMappingName; + + private final Set propertyContexts = newHashSet(); + private final Set configuredMembers = newHashSet(); + + private List> defaultGroupSequence; + + PropertyHolderTypeConstraintMappingContextImpl(PropertyHolderConstraintMappingImpl mapping, String propertyHolderMappingName) { + super( mapping ); + this.propertyHolderMappingName = propertyHolderMappingName; + } + + @Override + public TypeConstraintMappingContext defaultGroupSequence(Class... defaultGroupSequence) { + this.defaultGroupSequence = Arrays.asList( defaultGroupSequence ); + return this; + } + + @Override + public PropertyConstraintMappingContext property(String property, Class propertyType) { + Contracts.assertNotNull( property, "The property name must not be null." ); + Contracts.assertNotEmpty( property, MESSAGES.propertyNameMustNotBeEmpty() ); + + if ( configuredMembers.contains( property ) ) { + throw LOG.getPropertyHolderMappingPropertyHasAlreadyBeenConfiguredViaProgrammaticApiException( propertyHolderMappingName, property ); + } + + PropertyConstraintMappingContextImpl context = new PropertyConstraintMappingContextImpl( + this, + property, + propertyType + ); + + configuredMembers.add( property ); + propertyContexts.add( context ); + return context; + } + + @Override + public PropertyHolderConstraintMappingContext propertyHolder(String property) { + Contracts.assertNotNull( property, "The property name must not be null." ); + Contracts.assertNotEmpty( property, MESSAGES.propertyNameMustNotBeEmpty() ); + + if ( configuredMembers.contains( property ) ) { + throw LOG.getPropertyHolderMappingPropertyHasAlreadyBeenConfiguredViaProgrammaticApiException( propertyHolderMappingName, property ); + } + + PropertyHolderConstraintMappingContextImpl context = new PropertyHolderConstraintMappingContextImpl( + this, + property + ); + + configuredMembers.add( property ); + propertyContexts.add( context ); + return context; + } + + PropertyHolderConfiguration build() { + return new PropertyHolderConfiguration( + ConfigurationSource.API, + propertyHolderMappingName, + buildConstraintElements(), + defaultGroupSequence + ); + } + + private Set buildConstraintElements() { + Set elements = newHashSet(); + + //properties + for ( PropertyConstraintMappingContextImplBase propertyContext : propertyContexts ) { + elements.add( propertyContext.build() ); + } + + return elements; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/package-info.java b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/package-info.java new file mode 100644 index 0000000000..703f748cba --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/cfg/propertyholder/package-info.java @@ -0,0 +1,12 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ + +/** + * Creational context implementations of the API for programmatic + * constraint definition. + */ +package org.hibernate.validator.internal.cfg.propertyholder; diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ConfigurationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ConfigurationImpl.java index 986bbe5b51..369d9468d9 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ConfigurationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ConfigurationImpl.java @@ -37,7 +37,9 @@ import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMapping; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; +import org.hibernate.validator.internal.cfg.propertyholder.PropertyHolderConstraintMappingImpl; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl; import org.hibernate.validator.internal.engine.resolver.TraversableResolvers; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; @@ -99,6 +101,7 @@ public class ConfigurationImpl implements HibernateValidatorConfiguration, Confi // HV-specific options private final Set programmaticMappings = newHashSet(); + private final Set propertyHolderConstraintMappings = newHashSet(); private boolean failFast; private ClassLoader externalClassLoader; private final MethodValidationConfiguration.Builder methodValidationConfigurationBuilder = new MethodValidationConfiguration.Builder(); @@ -327,6 +330,11 @@ public final DefaultConstraintMapping createConstraintMapping() { return new DefaultConstraintMapping( new JavaBeanHelper( getterPropertySelectionStrategyToUse ) ); } + @Override + public PropertyHolderConstraintMapping createPropertyHolderConstraintMapping() { + return new PropertyHolderConstraintMappingImpl(); + } + @Override public final HibernateValidatorConfiguration addMapping(ConstraintMapping mapping) { Contracts.assertNotNull( mapping, MESSAGES.parameterMustNotBeNull( "mapping" ) ); @@ -335,6 +343,14 @@ public final HibernateValidatorConfiguration addMapping(ConstraintMapping mappin return this; } + @Override + public final HibernateValidatorConfiguration addPropertyHolderMapping(PropertyHolderConstraintMapping mapping) { + Contracts.assertNotNull( mapping, MESSAGES.parameterMustNotBeNull( "mapping" ) ); + + this.propertyHolderConstraintMappings.add( (PropertyHolderConstraintMappingImpl) mapping ); + return this; + } + @Override public final HibernateValidatorConfiguration addProperty(String name, String value) { if ( value != null ) { @@ -535,6 +551,10 @@ public final Set getProgrammaticMappings() { return programmaticMappings; } + public final Set getPropertyHolderConstraintMappings() { + return propertyHolderConstraintMappings; + } + private boolean isSpecificProvider() { return validationBootstrapParameters.getProvider() != null; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java index 8da528f0be..4881f5101c 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -36,19 +36,23 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping; +import org.hibernate.validator.internal.cfg.propertyholder.PropertyHolderConstraintMappingImpl; import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionContribution; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.constraintvalidation.HibernateConstraintValidatorInitializationContextImpl; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.scripting.DefaultScriptEvaluatorFactory; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; import org.hibernate.validator.internal.metadata.provider.ProgrammaticMetaDataProvider; +import org.hibernate.validator.internal.metadata.provider.proeprtyholder.ProgrammaticPropertyHolderMetaDataProvider; +import org.hibernate.validator.internal.metadata.provider.proeprtyholder.PropertyHolderMetaDataProvider; import org.hibernate.validator.internal.metadata.provider.XmlMetaDataProvider; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -98,6 +102,13 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { @Immutable private final Set constraintMappings; + /** + * Programmatic constraints for property holders passed via the Hibernate Validator specific API. + * Empty if there are no programmatic constraints. + */ + @Immutable + private final Set propertyHolderConstraintMappings; + /** * Helper for dealing with built-in validators and determining custom constraint annotations. */ @@ -131,10 +142,12 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { * provider. See also HV-659. */ @ThreadSafe - private final ConcurrentMap beanMetaDataManagers; + private final ConcurrentMap beanMetaDataManagers; private final ValueExtractorManager valueExtractorManager; + private final PropertyAccessorCreatorProvider propertyAccessorCreatorProvider; + private final JavaBeanHelper javaBeanHelper; private final ValidationOrderGenerator validationOrderGenerator; @@ -143,6 +156,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { ClassLoader externalClassLoader = getExternalClassLoader( configurationState ); this.valueExtractorManager = new ValueExtractorManager( configurationState.getValueExtractors() ); + this.propertyAccessorCreatorProvider = new PropertyAccessorCreatorProvider(); this.beanMetaDataManagers = new ConcurrentHashMap<>(); this.constraintHelper = new ConstraintHelper(); this.typeResolutionHelper = new TypeResolutionHelper(); @@ -176,6 +190,10 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { ) ); + this.propertyHolderConstraintMappings = Collections.unmodifiableSet( + getPropertyHolderConstraintMappings( configurationState ) + ); + registerCustomConstraintValidators( constraintMappings, constraintHelper ); this.methodValidationConfiguration = new MethodValidationConfiguration.Builder() @@ -248,6 +266,17 @@ private static Set getConstraintMappings(TypeResolutio return constraintMappings; } + private static Set getPropertyHolderConstraintMappings(ConfigurationState configurationState) { + Set constraintMappings = newHashSet(); + + if ( configurationState instanceof ConfigurationImpl ) { + ConfigurationImpl hibernateConfiguration = (ConfigurationImpl) configurationState; + constraintMappings.addAll( hibernateConfiguration.getPropertyHolderConstraintMappings() ); + } + + return constraintMappings; + } + @Override public Validator getValidator() { return createValidator( @@ -336,8 +365,8 @@ public HibernateValidatorContext usingContext() { public void close() { constraintValidatorManager.clear(); constraintHelper.clear(); - for ( BeanMetaDataManager beanMetaDataManager : beanMetaDataManagers.values() ) { - beanMetaDataManager.clear(); + for ( ConstraintMetaDataManager constraintMetaDataManager : beanMetaDataManagers.values() ) { + constraintMetaDataManager.clear(); } validatorFactoryScopedContext.getScriptEvaluatorFactory().clear(); valueExtractorManager.clear(); @@ -352,24 +381,27 @@ Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) { - BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent( + ConstraintMetaDataManager constraintMetaDataManager = beanMetaDataManagers.computeIfAbsent( new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, methodValidationConfiguration ), - key -> new BeanMetaDataManager( + key -> new ConstraintMetaDataManager( constraintHelper, executableHelper, typeResolutionHelper, validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, + propertyAccessorCreatorProvider, javaBeanHelper, validationOrderGenerator, - buildDataProviders(), - methodValidationConfiguration + methodValidationConfiguration, + buildMetaDataProviders(), + buildPropertyHolderMetaDataProvider() + ) ); return new ValidatorImpl( constraintValidatorFactory, - beanMetaDataManager, + constraintMetaDataManager, valueExtractorManager, constraintValidatorManager, validationOrderGenerator, @@ -377,7 +409,7 @@ Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ); } - private List buildDataProviders() { + private List buildMetaDataProviders() { List metaDataProviders = newArrayList(); if ( xmlMetaDataProvider != null ) { metaDataProviders.add( xmlMetaDataProvider ); @@ -396,6 +428,17 @@ private List buildDataProviders() { return metaDataProviders; } + private List buildPropertyHolderMetaDataProvider() { + if ( !propertyHolderConstraintMappings.isEmpty() ) { + return Collections.singletonList( new ProgrammaticPropertyHolderMetaDataProvider( + constraintHelper, typeResolutionHelper, valueExtractorManager, propertyHolderConstraintMappings + ) ); + } + else { + return Collections.emptyList(); + } + } + private static boolean checkPropertiesForBoolean(Map properties, String propertyKey, boolean programmaticValue) { boolean value = programmaticValue; String propertyStringValue = properties.get( propertyKey ); diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java index 9bbe13193e..40899c93e7 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java @@ -51,10 +51,10 @@ import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; -import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData; +import org.hibernate.validator.internal.metadata.aggregated.cascading.ContainerCascadingMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData; import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData; @@ -108,7 +108,7 @@ public class ValidatorImpl implements Validator, ExecutableValidator { * Used to get access to the bean meta data. Used to avoid to parsing the constraint configuration for each call * of a given entity. */ - private final BeanMetaDataManager beanMetaDataManager; + private final ConstraintMetaDataManager constraintMetaDataManager; /** * Manages the life cycle of constraint validator instances @@ -129,13 +129,13 @@ public class ValidatorImpl implements Validator, ExecutableValidator { private final HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext; public ValidatorImpl(ConstraintValidatorFactory constraintValidatorFactory, - BeanMetaDataManager beanMetaDataManager, + ConstraintMetaDataManager constraintMetaDataManager, ValueExtractorManager valueExtractorManager, ConstraintValidatorManager constraintValidatorManager, ValidationOrderGenerator validationOrderGenerator, ValidatorFactoryScopedContext validatorFactoryScopedContext) { this.constraintValidatorFactory = constraintValidatorFactory; - this.beanMetaDataManager = beanMetaDataManager; + this.constraintMetaDataManager = constraintMetaDataManager; this.valueExtractorManager = valueExtractorManager; this.constraintValidatorManager = constraintValidatorManager; this.validationOrderGenerator = validationOrderGenerator; @@ -246,6 +246,27 @@ public Set> validateReturnValue(T object, Method meth return validateReturnValue( object, (Executable) method, returnValue, groups ); } + public Set> validatePropertyHolder(T object, String mapping, Class... groups) { + Contracts.assertNotNull( object, MESSAGES.validatedObjectMustNotBeNull() ); + sanityCheckGroups( groups ); + + BaseBeanValidationContext validationContext = getValidationContextBuilder().forPropertyHolder( object, mapping ); + + if ( !validationContext.getRootBeanMetaData().hasConstraints() ) { + return Collections.emptySet(); + } + + ValidationOrder validationOrder = determineGroupValidationOrder( groups ); + ValueContext valueContext = ValueContext.getLocalExecutionContext( + validatorScopedContext.getParameterNameProvider(), + object, + validationContext.getRootBeanMetaData(), + PathImpl.createRootPath() + ); + + return validateInContext( validationContext, valueContext, validationOrder ); + } + private Set> validateParameters(T object, Executable executable, Object[] parameterValues, Class... groups) { sanityCheckGroups( groups ); @@ -288,7 +309,7 @@ private Set> validateReturnValue(T object, Executable @Override public final BeanDescriptor getConstraintsForClass(Class clazz) { - return beanMetaDataManager.getBeanMetaData( clazz ).getBeanDescriptor(); + return constraintMetaDataManager.getBeanMetaData( clazz ).getBeanDescriptor(); } @Override @@ -310,13 +331,12 @@ public ExecutableValidator forExecutables() { private ValidationContextBuilder getValidationContextBuilder() { return new ValidationContextBuilder( - beanMetaDataManager, + constraintMetaDataManager, constraintValidatorManager, constraintValidatorFactory, validatorScopedContext, TraversableResolvers.wrapWithCachingForSingleValidation( traversableResolver, validatorScopedContext.isTraversableResolverResultCacheEnabled() ), constraintValidatorInitializationContext - ); } @@ -432,42 +452,60 @@ private void validateConstraintsForDefaultGroup(BaseBeanValidationContext final BeanMetaData beanMetaData = valueContext.getCurrentBeanMetaData(); final Map, Class> validatedInterfaces = new HashMap<>(); - // evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions - for ( Class clazz : beanMetaData.getClassHierarchy() ) { - BeanMetaData hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz ); - boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.isDefaultGroupSequenceRedefined(); + boolean defaultGroupSequenceIsRedefined = beanMetaData.isDefaultGroupSequenceRedefined(); + if ( defaultGroupSequenceIsRedefined ) { + validateConstraintsForRedefinedGroupSequence( validationContext, valueContext, validatedInterfaces, beanMetaData.getBeanClass(), beanMetaData ); + validationContext.markCurrentBeanAsProcessed( valueContext ); + } + else { + validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, beanMetaData.getBeanClass(), beanMetaData.getDirectMetaConstraints(), Group.DEFAULT_GROUP ); + List> classHierarchy = beanMetaData.getClassHierarchy(); + + validationContext.markCurrentBeanAsProcessed( valueContext ); + // we start from the second element as first one (the most specific class) was already validated ^ - // if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy. - if ( defaultGroupSequenceIsRedefined ) { - Iterator defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() ); - Set> metaConstraints = hostingBeanMetaData.getMetaConstraints(); + // evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions + for ( int i = 1; i < classHierarchy.size(); i++ ) { + Class clazz = classHierarchy.get( i ); - while ( defaultGroupSequence.hasNext() ) { - for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) { - boolean validationSuccessful = true; + BeanMetaData hostingBeanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); + defaultGroupSequenceIsRedefined = hostingBeanMetaData.isDefaultGroupSequenceRedefined(); - for ( Group defaultSequenceMember : groupOfGroups ) { - validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, - metaConstraints, defaultSequenceMember ); - } - if ( !validationSuccessful ) { - break; - } - } + // if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy. + if ( defaultGroupSequenceIsRedefined ) { + validateConstraintsForRedefinedGroupSequence( validationContext, valueContext, validatedInterfaces, clazz, hostingBeanMetaData ); + } + // fast path in case the default group sequence hasn't been redefined + else { + Set> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints(); + validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints, Group.DEFAULT_GROUP ); + } + + validationContext.markCurrentBeanAsProcessed( valueContext ); + + // all constraints in the hierarchy has been validated, stop validation. + if ( defaultGroupSequenceIsRedefined ) { + break; } } - // fast path in case the default group sequence hasn't been redefined - else { - Set> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints(); - validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints, - Group.DEFAULT_GROUP ); - } + } + } - validationContext.markCurrentBeanAsProcessed( valueContext ); + private void validateConstraintsForRedefinedGroupSequence(BaseBeanValidationContext validationContext, ValueContext valueContext, Map, Class> validatedInterfaces, Class clazz, BeanMetaData hostingBeanMetaData) { + Iterator defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() ); + Set> metaConstraints = hostingBeanMetaData.getMetaConstraints(); - // all constraints in the hierarchy has been validated, stop validation. - if ( defaultGroupSequenceIsRedefined ) { - break; + while ( defaultGroupSequence.hasNext() ) { + for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) { + boolean validationSuccessful = true; + + for ( Group defaultSequenceMember : groupOfGroups ) { + validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, + metaConstraints, defaultSequenceMember ); + } + if ( !validationSuccessful ) { + break; + } } } } @@ -597,13 +635,13 @@ private void validateCascadedAnnotatedObjectForCurrentGroup(Object value, BaseBe // already and need only to pass the current element ValidationOrder validationOrder = validationOrderGenerator.getValidationOrder( currentGroup, currentGroup != originalGroup ); - ValueContext cascadedValueContext = buildNewLocalExecutionContext( valueContext, value ); + ValueContext cascadedValueContext = buildNewLocalExecutionContext( cascadingMetaData, valueContext, value ); validateInContext( validationContext, cascadedValueContext, validationOrder ); } private void validateCascadedContainerElementsForCurrentGroup(Object value, BaseBeanValidationContext validationContext, ValueContext valueContext, - List containerElementTypesCascadingMetaData) { + List containerElementTypesCascadingMetaData) { for ( ContainerCascadingMetaData cascadingMetaData : containerElementTypesCascadingMetaData ) { if ( !cascadingMetaData.isMarkedForCascadingOnAnnotatedObjectOrContainerElements() ) { continue; @@ -675,7 +713,7 @@ private void doValidate(Object value, String nodeName) { // already and need only to pass the current element ValidationOrder validationOrder = validationOrderGenerator.getValidationOrder( currentGroup, currentGroup != originalGroup ); - ValueContext cascadedValueContext = buildNewLocalExecutionContext( valueContext, value ); + ValueContext cascadedValueContext = buildNewLocalExecutionContext( cascadingMetaData, valueContext, value ); if ( cascadingMetaData.getDeclaredContainerClass() != null ) { cascadedValueContext.setTypeParameter( cascadingMetaData.getDeclaredContainerClass(), cascadingMetaData.getDeclaredTypeParameterIndex() ); @@ -688,7 +726,7 @@ private void doValidate(Object value, String nodeName) { // Cascade validation to container elements if we are dealing with a container element if ( cascadingMetaData.hasContainerElementsMarkedForCascading() ) { - ValueContext cascadedTypeArgumentValueContext = buildNewLocalExecutionContext( valueContext, value ); + ValueContext cascadedTypeArgumentValueContext = buildNewLocalExecutionContext( cascadingMetaData, valueContext, value ); if ( cascadingMetaData.getTypeParameter() != null ) { cascadedValueContext.setTypeParameter( cascadingMetaData.getDeclaredContainerClass(), cascadingMetaData.getDeclaredTypeParameterIndex() ); } @@ -737,25 +775,16 @@ private void validateCascadedContainerElementsInContext(Object value, BaseBeanVa } } - private ValueContext buildNewLocalExecutionContext(ValueContext valueContext, Object value) { + private ValueContext buildNewLocalExecutionContext(CascadingMetaData cascadingMetaData, ValueContext valueContext, Object value) { ValueContext newValueContext; - if ( value != null ) { - newValueContext = ValueContext.getLocalExecutionContext( - validatorScopedContext.getParameterNameProvider(), - value, - beanMetaDataManager.getBeanMetaData( value.getClass() ), - valueContext.getPropertyPath() - ); - newValueContext.setCurrentValidatedValue( value ); - } - else { - newValueContext = ValueContext.getLocalExecutionContext( - validatorScopedContext.getParameterNameProvider(), - valueContext.getCurrentBeanType(), - valueContext.getCurrentBeanMetaData(), - valueContext.getPropertyPath() - ); - } + Contracts.assertNotNull( value, "value cannot be null" ); + newValueContext = ValueContext.getLocalExecutionContext( + validatorScopedContext.getParameterNameProvider(), + value, + cascadingMetaData.getBeanMetaDataForCascadable( constraintMetaDataManager, value ), + valueContext.getPropertyPath() + ); + newValueContext.setCurrentValidatedValue( value ); return newValueContext; } @@ -846,7 +875,7 @@ private void validateParametersInContext(ExecutableValidationContext vali } ValueContext cascadingValueContext = ValueContext.getLocalExecutionContext( - beanMetaDataManager, + constraintMetaDataManager, validatorScopedContext.getParameterNameProvider(), parameterValues, executableMetaData.getValidatableParametersMetaData(), @@ -980,7 +1009,7 @@ private ValueContext getExecutableValueContext(T object, Executab if ( object != null ) { valueContext = ValueContext.getLocalExecutionContext( - beanMetaDataManager, + constraintMetaDataManager, validatorScopedContext.getParameterNameProvider(), object, validatable, @@ -989,7 +1018,7 @@ private ValueContext getExecutableValueContext(T object, Executab } else { valueContext = ValueContext.getLocalExecutionContext( - beanMetaDataManager, + constraintMetaDataManager, validatorScopedContext.getParameterNameProvider(), (Class) null, //the type is not required in this case (only for cascaded validation) validatable, @@ -1032,7 +1061,7 @@ private void validateReturnValueInContext(ExecutableValidationContext if ( value != null ) { cascadingValueContext = ValueContext.getLocalExecutionContext( - beanMetaDataManager, + constraintMetaDataManager, validatorScopedContext.getParameterNameProvider(), value, executableMetaData.getReturnValueMetaData(), @@ -1188,11 +1217,11 @@ else if ( propertyPathNode.getKey() != null ) { } clazz = value.getClass(); - beanMetaData = beanMetaDataManager.getBeanMetaData( clazz ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); propertyMetaData = getBeanPropertyMetaData( beanMetaData, propertyPathNode ); } else { - beanMetaData = beanMetaDataManager.getBeanMetaData( clazz ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); } } } @@ -1229,7 +1258,7 @@ private ValueContext getValueContextForValueValidation(Class rootBe while ( propertyPathIter.hasNext() ) { // cast is ok, since we are dealing with engine internal classes NodeImpl propertyPathNode = (NodeImpl) propertyPathIter.next(); - beanMetaData = beanMetaDataManager.getBeanMetaData( clazz ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); propertyMetaData = getBeanPropertyMetaData( beanMetaData, propertyPathNode ); // if the property is not the leaf property, we set up the context for the next iteration @@ -1240,7 +1269,7 @@ private ValueContext getValueContextForValueValidation(Class rootBe propertyPathNode = (NodeImpl) propertyPathIter.next(); clazz = ReflectionHelper.getClassFromType( ReflectionHelper.getCollectionElementType( propertyMetaData.getType() ) ); - beanMetaData = beanMetaDataManager.getBeanMetaData( clazz ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( clazz ); propertyMetaData = getBeanPropertyMetaData( beanMetaData, propertyPathNode ); } else { diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java index 024b5419ec..fa1f834d61 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValueContext.java @@ -13,7 +13,7 @@ import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.metadata.facets.Validatable; @@ -71,11 +71,11 @@ public class ValueContext { */ private ConstraintLocationKind constraintLocationKind; - public static ValueContext getLocalExecutionContext(BeanMetaDataManager beanMetaDataManager, + public static ValueContext getLocalExecutionContext(ConstraintMetaDataManager constraintMetaDataManager, ExecutableParameterNameProvider parameterNameProvider, T value, Validatable validatable, PathImpl propertyPath) { @SuppressWarnings("unchecked") Class rootBeanType = (Class) value.getClass(); - return new ValueContext<>( parameterNameProvider, value, rootBeanType, beanMetaDataManager.getBeanMetaData( rootBeanType ), validatable, propertyPath ); + return new ValueContext<>( parameterNameProvider, value, rootBeanType, constraintMetaDataManager.getBeanMetaData( rootBeanType ), validatable, propertyPath ); } @SuppressWarnings("unchecked") @@ -85,9 +85,9 @@ public static ValueContext getLocalExecutionContext(ExecutableParam return new ValueContext<>( parameterNameProvider, value, rootBeanType, (BeanMetaData) currentBeanMetaData, currentBeanMetaData, propertyPath ); } - public static ValueContext getLocalExecutionContext(BeanMetaDataManager beanMetaDataManager, + public static ValueContext getLocalExecutionContext(ConstraintMetaDataManager constraintMetaDataManager, ExecutableParameterNameProvider parameterNameProvider, Class rootBeanType, Validatable validatable, PathImpl propertyPath) { - BeanMetaData rootBeanMetaData = rootBeanType != null ? beanMetaDataManager.getBeanMetaData( rootBeanType ) : null; + BeanMetaData rootBeanMetaData = rootBeanType != null ? constraintMetaDataManager.getBeanMetaData( rootBeanType ) : null; return new ValueContext<>( parameterNameProvider, null, rootBeanType, rootBeanMetaData, validatable, propertyPath ); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java index 8db1873385..425b13d884 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/ValidationContextBuilder.java @@ -14,7 +14,7 @@ import org.hibernate.validator.constraintvalidation.HibernateConstraintValidatorInitializationContext; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager; import org.hibernate.validator.internal.engine.path.PathImpl; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; /** @@ -25,7 +25,7 @@ */ public class ValidationContextBuilder { - private final BeanMetaDataManager beanMetaDataManager; + private final ConstraintMetaDataManager constraintMetaDataManager; private final ConstraintValidatorManager constraintValidatorManager; private final ConstraintValidatorFactory constraintValidatorFactory; private final TraversableResolver traversableResolver; @@ -33,13 +33,13 @@ public class ValidationContextBuilder { private final ValidatorScopedContext validatorScopedContext; public ValidationContextBuilder( - BeanMetaDataManager beanMetaDataManager, + ConstraintMetaDataManager constraintMetaDataManager, ConstraintValidatorManager constraintValidatorManager, ConstraintValidatorFactory constraintValidatorFactory, ValidatorScopedContext validatorScopedContext, TraversableResolver traversableResolver, HibernateConstraintValidatorInitializationContext constraintValidatorInitializationContext) { - this.beanMetaDataManager = beanMetaDataManager; + this.constraintMetaDataManager = constraintMetaDataManager; this.constraintValidatorManager = constraintValidatorManager; this.constraintValidatorFactory = constraintValidatorFactory; this.traversableResolver = traversableResolver; @@ -58,7 +58,7 @@ public BaseBeanValidationContext forValidate(T rootBean) { constraintValidatorInitializationContext, rootBean, rootBeanClass, - beanMetaDataManager.getBeanMetaData( rootBeanClass ) + constraintMetaDataManager.getBeanMetaData( rootBeanClass ) ); } @@ -73,7 +73,7 @@ public BaseBeanValidationContext forValidateProperty(T rootBean, PathImpl constraintValidatorInitializationContext, rootBean, rootBeanClass, - beanMetaDataManager.getBeanMetaData( rootBeanClass ), + constraintMetaDataManager.getBeanMetaData( rootBeanClass ), propertyPath.getLeafNode().getName() ); } @@ -87,7 +87,7 @@ public BaseBeanValidationContext forValidateValue(Class rootBeanClass, constraintValidatorInitializationContext, null, //root bean rootBeanClass, - beanMetaDataManager.getBeanMetaData( rootBeanClass ), + constraintMetaDataManager.getBeanMetaData( rootBeanClass ), propertyPath.getLeafNode().getName() ); } @@ -98,7 +98,7 @@ public ExecutableValidationContext forValidateParameters( Object[] executableParameters) { @SuppressWarnings("unchecked") Class rootBeanClass = rootBean != null ? (Class) rootBean.getClass() : (Class) executable.getDeclaringClass(); - BeanMetaData rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass ); + BeanMetaData rootBeanMetaData = constraintMetaDataManager.getBeanMetaData( rootBeanClass ); return new ParameterExecutableValidationContext<>( constraintValidatorManager, @@ -121,7 +121,7 @@ public ExecutableValidationContext forValidateReturnValue( Object executableReturnValue) { @SuppressWarnings("unchecked") Class rootBeanClass = rootBean != null ? (Class) rootBean.getClass() : (Class) executable.getDeclaringClass(); - BeanMetaData rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass ); + BeanMetaData rootBeanMetaData = constraintMetaDataManager.getBeanMetaData( rootBeanClass ); return new ReturnValueExecutableValidationContext<>( constraintValidatorManager, constraintValidatorFactory, @@ -130,10 +130,25 @@ public ExecutableValidationContext forValidateReturnValue( constraintValidatorInitializationContext, rootBean, rootBeanClass, - beanMetaDataManager.getBeanMetaData( rootBeanClass ), + constraintMetaDataManager.getBeanMetaData( rootBeanClass ), executable, rootBeanMetaData.getMetaDataFor( executable ), executableReturnValue ); } + + public BaseBeanValidationContext forPropertyHolder(T propertyHolder, String mapping) { + @SuppressWarnings("unchecked") + Class propertyHolderClass = (Class) propertyHolder.getClass(); + return new BeanValidationContext<>( + constraintValidatorManager, + constraintValidatorFactory, + validatorScopedContext, + traversableResolver, + constraintValidatorInitializationContext, + propertyHolder, + propertyHolderClass, + constraintMetaDataManager.getPropertyHolderBeanMetaData( propertyHolderClass, mapping ) + ); + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/valueextraction/ValueExtractorResolver.java b/engine/src/main/java/org/hibernate/validator/internal/engine/valueextraction/ValueExtractorResolver.java index afaa2e247a..327506680d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/valueextraction/ValueExtractorResolver.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/valueextraction/ValueExtractorResolver.java @@ -22,9 +22,9 @@ import javax.validation.ConstraintDeclarationException; import javax.validation.valueextraction.ValueExtractor; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; -import org.hibernate.validator.internal.metadata.aggregated.ContainerCascadingMetaData; -import org.hibernate.validator.internal.metadata.aggregated.PotentiallyContainerCascadingMetaData; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.ContainerCascadingMetaData; +import org.hibernate.validator.internal.metadata.aggregated.cascading.PotentiallyContainerCascadingMetaData; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ReflectionHelper; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java index d0760f45ea..e78f42c0f5 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractConstraintMetaData.java @@ -25,6 +25,7 @@ import javax.validation.metadata.ContainerElementTypeDescriptor; import javax.validation.metadata.GroupConversionDescriptor; +import org.hibernate.validator.internal.metadata.aggregated.cascading.ContainerCascadingMetaData; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ContainerElementTypeDescriptorImpl; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java index 1cb08aea79..7dee6640be 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/AbstractPropertyCascadable.java @@ -10,6 +10,7 @@ import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.properties.Field; import org.hibernate.validator.internal.properties.Getter; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java new file mode 100644 index 0000000000..765fdfe681 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataBuilder.java @@ -0,0 +1,330 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; + +import java.util.List; +import java.util.Set; + +import org.hibernate.validator.internal.engine.MethodValidationConfiguration; +import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.raw.BeanConfiguration; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.ConstrainedElement; +import org.hibernate.validator.internal.metadata.raw.ConstrainedExecutable; +import org.hibernate.validator.internal.metadata.raw.ConstrainedField; +import org.hibernate.validator.internal.metadata.raw.ConstrainedType; +import org.hibernate.validator.internal.properties.Callable; +import org.hibernate.validator.internal.util.ExecutableHelper; +import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; +import org.hibernate.validator.internal.util.StringHelper; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider; + +/** + * @author Hardy Ferentschik + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Chris Beckey <cbeckey@paypal.com> + * @author Guillaume Smet + * @author Marko Bekhta + */ +public class BeanMetaDataBuilder { + + private final ConstraintHelper constraintHelper; + private final ValidationOrderGenerator validationOrderGenerator; + private final Class beanClass; + private final Set builders = newHashSet(); + private final ExecutableHelper executableHelper; + private final TypeResolutionHelper typeResolutionHelper; + private final ValueExtractorManager valueExtractorManager; + private final ExecutableParameterNameProvider parameterNameProvider; + private final MethodValidationConfiguration methodValidationConfiguration; + + private ConfigurationSource sequenceSource; + private ConfigurationSource providerSource; + private List> defaultGroupSequence; + private DefaultGroupSequenceProvider defaultGroupSequenceProvider; + + + private BeanMetaDataBuilder( + ConstraintHelper constraintHelper, + ExecutableHelper executableHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + ExecutableParameterNameProvider parameterNameProvider, + ValidationOrderGenerator validationOrderGenerator, + Class beanClass, + MethodValidationConfiguration methodValidationConfiguration) { + this.beanClass = beanClass; + this.constraintHelper = constraintHelper; + this.validationOrderGenerator = validationOrderGenerator; + this.executableHelper = executableHelper; + this.typeResolutionHelper = typeResolutionHelper; + this.valueExtractorManager = valueExtractorManager; + this.parameterNameProvider = parameterNameProvider; + this.methodValidationConfiguration = methodValidationConfiguration; + } + + public static BeanMetaDataBuilder getInstance( + ConstraintHelper constraintHelper, + ExecutableHelper executableHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + ExecutableParameterNameProvider parameterNameProvider, + ValidationOrderGenerator validationOrderGenerator, + Class beanClass, + MethodValidationConfiguration methodValidationConfiguration) { + return new BeanMetaDataBuilder<>( + constraintHelper, + executableHelper, + typeResolutionHelper, + valueExtractorManager, + parameterNameProvider, + validationOrderGenerator, + beanClass, + methodValidationConfiguration ); + } + + public void add(BeanConfiguration configuration) { + if ( configuration.getBeanClass().equals( beanClass ) ) { + if ( configuration.getDefaultGroupSequence() != null + && ( sequenceSource == null || configuration.getSource() + .getPriority() >= sequenceSource.getPriority() ) ) { + + sequenceSource = configuration.getSource(); + defaultGroupSequence = configuration.getDefaultGroupSequence(); + } + + if ( configuration.getDefaultGroupSequenceProvider() != null + && ( providerSource == null || configuration.getSource() + .getPriority() >= providerSource.getPriority() ) ) { + + providerSource = configuration.getSource(); + defaultGroupSequenceProvider = configuration.getDefaultGroupSequenceProvider(); + } + } + + for ( ConstrainedElement constrainedElement : configuration.getConstrainedElements() ) { + addMetaDataToBuilder( constrainedElement, builders ); + } + } + + private void addMetaDataToBuilder(ConstrainedElement constrainableElement, Set builders) { + for ( BuilderDelegate builder : builders ) { + boolean foundBuilder = builder.add( constrainableElement ); + + if ( foundBuilder ) { + return; + } + } + + builders.add( + new BuilderDelegate( + beanClass, + constrainableElement, + constraintHelper, + executableHelper, + typeResolutionHelper, + valueExtractorManager, + parameterNameProvider, + methodValidationConfiguration + ) + ); + } + + public BeanMetaDataImpl build() { + Set aggregatedElements = newHashSet(); + + for ( BuilderDelegate builder : builders ) { + aggregatedElements.addAll( builder.build() ); + } + + return new BeanMetaDataImpl<>( + beanClass, + defaultGroupSequence, + defaultGroupSequenceProvider, + aggregatedElements, + validationOrderGenerator + ); + } + + private static class BuilderDelegate { + private final Class beanClass; + private final ConstrainedElement constrainedElement; + private final ConstraintHelper constraintHelper; + private final ExecutableHelper executableHelper; + private final TypeResolutionHelper typeResolutionHelper; + private final ValueExtractorManager valueExtractorManager; + private final ExecutableParameterNameProvider parameterNameProvider; + private MetaDataBuilder metaDataBuilder; + private ExecutableMetaData.Builder methodBuilder; + private final MethodValidationConfiguration methodValidationConfiguration; + private final int hashCode; + + public BuilderDelegate( + Class beanClass, + ConstrainedElement constrainedElement, + ConstraintHelper constraintHelper, + ExecutableHelper executableHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + ExecutableParameterNameProvider parameterNameProvider, + MethodValidationConfiguration methodValidationConfiguration + ) { + this.beanClass = beanClass; + this.constrainedElement = constrainedElement; + this.constraintHelper = constraintHelper; + this.executableHelper = executableHelper; + this.typeResolutionHelper = typeResolutionHelper; + this.valueExtractorManager = valueExtractorManager; + this.parameterNameProvider = parameterNameProvider; + this.methodValidationConfiguration = methodValidationConfiguration; + + switch ( constrainedElement.getKind() ) { + case FIELD: + ConstrainedField constrainedField = (ConstrainedField) constrainedElement; + metaDataBuilder = new PropertyMetaData.Builder( + beanClass, + constrainedField, + constraintHelper, + typeResolutionHelper, + valueExtractorManager + ); + break; + case CONSTRUCTOR: + case METHOD: + case GETTER: + ConstrainedExecutable constrainedExecutable = (ConstrainedExecutable) constrainedElement; + Callable callable = constrainedExecutable.getCallable(); + + // HV-890 Not adding meta-data for private super-type methods to the method meta-data of this bean; + // It is not needed and it may conflict with sub-type methods of the same signature + if ( !callable.isPrivate() || beanClass == callable.getDeclaringClass() ) { + methodBuilder = new ExecutableMetaData.Builder( + beanClass, + constrainedExecutable, + constraintHelper, + executableHelper, + typeResolutionHelper, + valueExtractorManager, + parameterNameProvider, + methodValidationConfiguration + ); + } + + if ( constrainedElement.getKind() == ConstrainedElement.ConstrainedElementKind.GETTER ) { + metaDataBuilder = new PropertyMetaData.Builder( + beanClass, + constrainedExecutable, + constraintHelper, + typeResolutionHelper, + valueExtractorManager + ); + } + break; + case TYPE: + ConstrainedType constrainedType = (ConstrainedType) constrainedElement; + metaDataBuilder = new ClassMetaData.Builder( + beanClass, + constrainedType, + constraintHelper, + typeResolutionHelper, + valueExtractorManager + ); + break; + default: + throw new IllegalStateException( + StringHelper.format( "Constrained element kind '%1$s' not supported here.", constrainedElement.getKind() ) ); + } + + this.hashCode = buildHashCode(); + } + + public boolean add(ConstrainedElement constrainedElement) { + boolean added = false; + + if ( methodBuilder != null && methodBuilder.accepts( constrainedElement ) ) { + methodBuilder.add( constrainedElement ); + added = true; + } + + if ( metaDataBuilder != null && metaDataBuilder.accepts( constrainedElement ) ) { + metaDataBuilder.add( constrainedElement ); + + if ( !added && constrainedElement.getKind().isMethod() && methodBuilder == null ) { + ConstrainedExecutable constrainedMethod = (ConstrainedExecutable) constrainedElement; + methodBuilder = new ExecutableMetaData.Builder( + beanClass, + constrainedMethod, + constraintHelper, + executableHelper, + typeResolutionHelper, + valueExtractorManager, + parameterNameProvider, + methodValidationConfiguration + ); + } + + added = true; + } + + return added; + } + + public Set build() { + Set metaDataSet = newHashSet(); + + if ( metaDataBuilder != null ) { + metaDataSet.add( metaDataBuilder.build() ); + } + + if ( methodBuilder != null ) { + metaDataSet.add( methodBuilder.build() ); + } + + return metaDataSet; + } + + @Override + public int hashCode() { + return hashCode; + } + + private int buildHashCode() { + final int prime = 31; + int result = 1; + result = prime * result + beanClass.hashCode(); + result = prime * result + constrainedElement.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( !super.equals( obj ) ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + BuilderDelegate other = (BuilderDelegate) obj; + if ( !beanClass.equals( other.beanClass ) ) { + return false; + } + if ( !constrainedElement.equals( other.constrainedElement ) ) { + return false; + } + return true; + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java index 3926d11e81..4fba8e79b7 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/BeanMetaDataImpl.java @@ -27,31 +27,17 @@ import javax.validation.metadata.ConstructorDescriptor; import javax.validation.metadata.PropertyDescriptor; -import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.Sequence; import org.hibernate.validator.internal.engine.groups.ValidationOrder; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; -import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.BeanDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; import org.hibernate.validator.internal.metadata.descriptor.ExecutableDescriptorImpl; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; -import org.hibernate.validator.internal.metadata.raw.BeanConfiguration; -import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; -import org.hibernate.validator.internal.metadata.raw.ConstrainedElement; -import org.hibernate.validator.internal.metadata.raw.ConstrainedElement.ConstrainedElementKind; -import org.hibernate.validator.internal.metadata.raw.ConstrainedExecutable; -import org.hibernate.validator.internal.metadata.raw.ConstrainedField; -import org.hibernate.validator.internal.metadata.raw.ConstrainedType; -import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.ExecutableHelper; -import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; -import org.hibernate.validator.internal.util.StringHelper; -import org.hibernate.validator.internal.util.TypeResolutionHelper; import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper; import org.hibernate.validator.internal.util.classhierarchy.Filters; import org.hibernate.validator.internal.util.logging.Log; @@ -68,6 +54,7 @@ * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI * @author Chris Beckey <cbeckey@paypal.com> * @author Guillaume Smet + * @author Marko Bekhta */ public final class BeanMetaDataImpl implements BeanMetaData { @@ -267,7 +254,7 @@ public BeanDescriptor getBeanDescriptor() { BeanDescriptor beanDescriptor = this.beanDescriptor; if ( beanDescriptor == null ) { - synchronized ( this ) { + synchronized (this) { beanDescriptor = this.beanDescriptor; if ( beanDescriptor == null ) { @@ -349,9 +336,9 @@ public Iterator getDefaultValidationSequence(T beanState) { if ( hasDefaultGroupSequenceProvider() ) { List> providerDefaultGroupSequence = defaultGroupSequenceProvider.getValidationGroups( beanState ); return validationOrderGenerator.getDefaultValidationOrder( - beanClass, - getValidDefaultGroupSequence( beanClass, providerDefaultGroupSequence ) - ) + beanClass, + getValidDefaultGroupSequence( beanClass, providerDefaultGroupSequence ) + ) .getSequenceIterator(); } else { @@ -435,9 +422,9 @@ private static Map getConstrainedMethodsAsDesc if ( executableMetaData.getKind() == ElementKind.METHOD && executableMetaData.isConstrained() ) { ExecutableDescriptorImpl descriptor = executableMetaData.asDescriptor( - defaultGroupSequenceIsRedefined, - resolvedDefaultGroupSequence - ); + defaultGroupSequenceIsRedefined, + resolvedDefaultGroupSequence + ); for ( String signature : executableMetaData.getSignatures() ) { constrainedMethodDescriptors.put( signature, descriptor ); @@ -571,299 +558,6 @@ public String toString() { + ", defaultGroupSequence=" + getDefaultGroupSequence( null ) + '}'; } - public static class BeanMetaDataBuilder { - - private final ConstraintHelper constraintHelper; - private final ValidationOrderGenerator validationOrderGenerator; - private final Class beanClass; - private final Set builders = newHashSet(); - private final ExecutableHelper executableHelper; - private final TypeResolutionHelper typeResolutionHelper; - private final ValueExtractorManager valueExtractorManager; - private final ExecutableParameterNameProvider parameterNameProvider; - private final MethodValidationConfiguration methodValidationConfiguration; - - private ConfigurationSource sequenceSource; - private ConfigurationSource providerSource; - private List> defaultGroupSequence; - private DefaultGroupSequenceProvider defaultGroupSequenceProvider; - - - private BeanMetaDataBuilder( - ConstraintHelper constraintHelper, - ExecutableHelper executableHelper, - TypeResolutionHelper typeResolutionHelper, - ValueExtractorManager valueExtractorManager, - ExecutableParameterNameProvider parameterNameProvider, - ValidationOrderGenerator validationOrderGenerator, - Class beanClass, - MethodValidationConfiguration methodValidationConfiguration) { - this.beanClass = beanClass; - this.constraintHelper = constraintHelper; - this.validationOrderGenerator = validationOrderGenerator; - this.executableHelper = executableHelper; - this.typeResolutionHelper = typeResolutionHelper; - this.valueExtractorManager = valueExtractorManager; - this.parameterNameProvider = parameterNameProvider; - this.methodValidationConfiguration = methodValidationConfiguration; - } - - public static BeanMetaDataBuilder getInstance( - ConstraintHelper constraintHelper, - ExecutableHelper executableHelper, - TypeResolutionHelper typeResolutionHelper, - ValueExtractorManager valueExtractorManager, - ExecutableParameterNameProvider parameterNameProvider, - ValidationOrderGenerator validationOrderGenerator, - Class beanClass, - MethodValidationConfiguration methodValidationConfiguration) { - return new BeanMetaDataBuilder<>( - constraintHelper, - executableHelper, - typeResolutionHelper, - valueExtractorManager, - parameterNameProvider, - validationOrderGenerator, - beanClass, - methodValidationConfiguration ); - } - - public void add(BeanConfiguration configuration) { - if ( configuration.getBeanClass().equals( beanClass ) ) { - if ( configuration.getDefaultGroupSequence() != null - && ( sequenceSource == null || configuration.getSource() - .getPriority() >= sequenceSource.getPriority() ) ) { - - sequenceSource = configuration.getSource(); - defaultGroupSequence = configuration.getDefaultGroupSequence(); - } - - if ( configuration.getDefaultGroupSequenceProvider() != null - && ( providerSource == null || configuration.getSource() - .getPriority() >= providerSource.getPriority() ) ) { - - providerSource = configuration.getSource(); - defaultGroupSequenceProvider = configuration.getDefaultGroupSequenceProvider(); - } - } - - for ( ConstrainedElement constrainedElement : configuration.getConstrainedElements() ) { - addMetaDataToBuilder( constrainedElement, builders ); - } - } - - private void addMetaDataToBuilder(ConstrainedElement constrainableElement, Set builders) { - for ( BuilderDelegate builder : builders ) { - boolean foundBuilder = builder.add( constrainableElement ); - - if ( foundBuilder ) { - return; - } - } - - builders.add( - new BuilderDelegate( - beanClass, - constrainableElement, - constraintHelper, - executableHelper, - typeResolutionHelper, - valueExtractorManager, - parameterNameProvider, - methodValidationConfiguration - ) - ); - } - - public BeanMetaDataImpl build() { - Set aggregatedElements = newHashSet(); - - for ( BuilderDelegate builder : builders ) { - aggregatedElements.addAll( builder.build() ); - } - - return new BeanMetaDataImpl<>( - beanClass, - defaultGroupSequence, - defaultGroupSequenceProvider, - aggregatedElements, - validationOrderGenerator - ); - } - } - - private static class BuilderDelegate { - private final Class beanClass; - private final ConstrainedElement constrainedElement; - private final ConstraintHelper constraintHelper; - private final ExecutableHelper executableHelper; - private final TypeResolutionHelper typeResolutionHelper; - private final ValueExtractorManager valueExtractorManager; - private final ExecutableParameterNameProvider parameterNameProvider; - private MetaDataBuilder metaDataBuilder; - private ExecutableMetaData.Builder methodBuilder; - private final MethodValidationConfiguration methodValidationConfiguration; - private final int hashCode; - - public BuilderDelegate( - Class beanClass, - ConstrainedElement constrainedElement, - ConstraintHelper constraintHelper, - ExecutableHelper executableHelper, - TypeResolutionHelper typeResolutionHelper, - ValueExtractorManager valueExtractorManager, - ExecutableParameterNameProvider parameterNameProvider, - MethodValidationConfiguration methodValidationConfiguration - ) { - this.beanClass = beanClass; - this.constrainedElement = constrainedElement; - this.constraintHelper = constraintHelper; - this.executableHelper = executableHelper; - this.typeResolutionHelper = typeResolutionHelper; - this.valueExtractorManager = valueExtractorManager; - this.parameterNameProvider = parameterNameProvider; - this.methodValidationConfiguration = methodValidationConfiguration; - - switch ( constrainedElement.getKind() ) { - case FIELD: - ConstrainedField constrainedField = (ConstrainedField) constrainedElement; - metaDataBuilder = new PropertyMetaData.Builder( - beanClass, - constrainedField, - constraintHelper, - typeResolutionHelper, - valueExtractorManager - ); - break; - case CONSTRUCTOR: - case METHOD: - case GETTER: - ConstrainedExecutable constrainedExecutable = (ConstrainedExecutable) constrainedElement; - Callable callable = constrainedExecutable.getCallable(); - - // HV-890 Not adding meta-data for private super-type methods to the method meta-data of this bean; - // It is not needed and it may conflict with sub-type methods of the same signature - if ( !callable.isPrivate() || beanClass == callable.getDeclaringClass() ) { - methodBuilder = new ExecutableMetaData.Builder( - beanClass, - constrainedExecutable, - constraintHelper, - executableHelper, - typeResolutionHelper, - valueExtractorManager, - parameterNameProvider, - methodValidationConfiguration - ); - } - - if ( constrainedElement.getKind() == ConstrainedElementKind.GETTER ) { - metaDataBuilder = new PropertyMetaData.Builder( - beanClass, - constrainedExecutable, - constraintHelper, - typeResolutionHelper, - valueExtractorManager - ); - } - break; - case TYPE: - ConstrainedType constrainedType = (ConstrainedType) constrainedElement; - metaDataBuilder = new ClassMetaData.Builder( - beanClass, - constrainedType, - constraintHelper, - typeResolutionHelper, - valueExtractorManager - ); - break; - default: - throw new IllegalStateException( - StringHelper.format( "Constrained element kind '%1$s' not supported here.", constrainedElement.getKind() ) ); - } - - this.hashCode = buildHashCode(); - } - - public boolean add(ConstrainedElement constrainedElement) { - boolean added = false; - - if ( methodBuilder != null && methodBuilder.accepts( constrainedElement ) ) { - methodBuilder.add( constrainedElement ); - added = true; - } - - if ( metaDataBuilder != null && metaDataBuilder.accepts( constrainedElement ) ) { - metaDataBuilder.add( constrainedElement ); - - if ( !added && constrainedElement.getKind().isMethod() && methodBuilder == null ) { - ConstrainedExecutable constrainedMethod = (ConstrainedExecutable) constrainedElement; - methodBuilder = new ExecutableMetaData.Builder( - beanClass, - constrainedMethod, - constraintHelper, - executableHelper, - typeResolutionHelper, - valueExtractorManager, - parameterNameProvider, - methodValidationConfiguration - ); - } - - added = true; - } - - return added; - } - - public Set build() { - Set metaDataSet = newHashSet(); - - if ( metaDataBuilder != null ) { - metaDataSet.add( metaDataBuilder.build() ); - } - - if ( methodBuilder != null ) { - metaDataSet.add( methodBuilder.build() ); - } - - return metaDataSet; - } - - @Override - public int hashCode() { - return hashCode; - } - - private int buildHashCode() { - final int prime = 31; - int result = 1; - result = prime * result + beanClass.hashCode(); - result = prime * result + constrainedElement.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( !super.equals( obj ) ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; - } - BuilderDelegate other = (BuilderDelegate) obj; - if ( !beanClass.equals( other.beanClass ) ) { - return false; - } - if ( !constrainedElement.equals( other.constrainedElement ) ) { - return false; - } - return true; - } - } - /** * Tuple for returning default group sequence, provider and validation order at once. */ diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaData.java index 86a03e770c..c40a99dc08 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaData.java @@ -15,6 +15,8 @@ import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.ContainerCascadingMetaData; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; /** * An aggregated view of the cascading validation metadata. Note that it also includes the cascading validation metadata @@ -59,4 +61,8 @@ public interface CascadingMetaData { * time. */ CascadingMetaData addRuntimeContainerSupport(ValueExtractorManager valueExtractorManager, Class valueClass); + + default BeanMetaData getBeanMetaDataForCascadable(ConstraintMetaDataManager constraintMetaDataManager, Object value) { + return constraintMetaDataManager.getBeanMetaData( value.getClass() ); + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java index 6c8ab7705f..a2d414d76d 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ExecutableMetaData.java @@ -23,6 +23,7 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.aggregated.rule.MethodConfigurationRule; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java index c8ebe21304..6dd1f2a012 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/FieldCascadable.java @@ -7,6 +7,7 @@ package org.hibernate.validator.internal.metadata.aggregated; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; import org.hibernate.validator.internal.properties.Field; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java index ab5da7d89f..49c64f0a9e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GetterCascadable.java @@ -7,6 +7,7 @@ package org.hibernate.validator.internal.metadata.aggregated; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.facets.Cascadable; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; import org.hibernate.validator.internal.properties.Getter; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java index 839d12f812..b9335c047b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ParameterMetaData.java @@ -15,6 +15,7 @@ import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.descriptor.ParameterDescriptorImpl; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PropertyHolderBeanMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PropertyHolderBeanMetaDataBuilder.java new file mode 100644 index 0000000000..8345d27ad5 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PropertyHolderBeanMetaDataBuilder.java @@ -0,0 +1,236 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; + +import java.util.List; +import java.util.Set; + +import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.ConstrainedElement; +import org.hibernate.validator.internal.metadata.raw.ConstrainedField; +import org.hibernate.validator.internal.metadata.raw.propertyholder.ConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.util.StringHelper; +import org.hibernate.validator.internal.util.TypeResolutionHelper; + +/** + * @author Hardy Ferentschik + * @author Gunnar Morling + * @author Kevin Pollet <kevin.pollet@serli.com> (C) 2011 SERLI + * @author Chris Beckey <cbeckey@paypal.com> + * @author Guillaume Smet + * @author Marko Bekhta + */ +public class PropertyHolderBeanMetaDataBuilder { + + private final ConstraintHelper constraintHelper; + private final ValidationOrderGenerator validationOrderGenerator; + private final Class propertyHolderClass; + private final Set builders = newHashSet(); + private final TypeResolutionHelper typeResolutionHelper; + private final ValueExtractorManager valueExtractorManager; + private final PropertyAccessorCreatorProvider propertyAccessorCreatorProvider; + + private ConfigurationSource sequenceSource; + private ConfigurationSource providerSource; + private List> defaultGroupSequence; + + + private PropertyHolderBeanMetaDataBuilder( + ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, + ValidationOrderGenerator validationOrderGenerator, + Class propertyHolderClass) { + this.propertyHolderClass = propertyHolderClass; + this.constraintHelper = constraintHelper; + this.validationOrderGenerator = validationOrderGenerator; + this.typeResolutionHelper = typeResolutionHelper; + this.valueExtractorManager = valueExtractorManager; + this.propertyAccessorCreatorProvider = propertyAccessorCreatorProvider; + } + + public static PropertyHolderBeanMetaDataBuilder getInstance( + ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, + ValidationOrderGenerator validationOrderGenerator, + Class propertyHolderClass) { + return new PropertyHolderBeanMetaDataBuilder<>( + constraintHelper, + typeResolutionHelper, + valueExtractorManager, + propertyAccessorCreatorProvider, + validationOrderGenerator, + propertyHolderClass ); + } + + public void add(PropertyHolderConfiguration configuration) { + if ( configuration.getDefaultGroupSequence() != null + && ( sequenceSource == null || configuration.getSource() + .getPriority() >= sequenceSource.getPriority() ) ) { + + sequenceSource = configuration.getSource(); + defaultGroupSequence = configuration.getDefaultGroupSequence(); + } + + for ( ConstrainedPropertyHolderElementBuilder constrainedElement : configuration.getConstrainedElements() ) { + if ( constrainedElement.isConstrained() ) { + addMetaDataToBuilder( + constrainedElement.build( + typeResolutionHelper, + constraintHelper, + valueExtractorManager, + propertyAccessorCreatorProvider, + propertyHolderClass + ), + builders + ); + } + } + } + + private void addMetaDataToBuilder(ConstrainedElement constrainableElement, Set builders) { + for ( BuilderDelegate builder : builders ) { + boolean foundBuilder = builder.add( constrainableElement ); + + if ( foundBuilder ) { + return; + } + } + + builders.add( + new BuilderDelegate( + propertyHolderClass, + constrainableElement, + constraintHelper, + typeResolutionHelper, + valueExtractorManager + ) + ); + } + + public BeanMetaDataImpl build() { + Set aggregatedElements = newHashSet(); + + for ( BuilderDelegate builder : builders ) { + aggregatedElements.addAll( builder.build() ); + } + + return new BeanMetaDataImpl<>( + propertyHolderClass, + defaultGroupSequence, + null, + aggregatedElements, + validationOrderGenerator + ); + } + + private static class BuilderDelegate { + private final Class propertyHolderClass; + private final ConstrainedElement constrainedElement; + private final ConstraintHelper constraintHelper; + private final TypeResolutionHelper typeResolutionHelper; + private final ValueExtractorManager valueExtractorManager; + private MetaDataBuilder metaDataBuilder; + private final int hashCode; + + public BuilderDelegate( + Class propertyHolderClass, + ConstrainedElement constrainedElement, + ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager + ) { + this.propertyHolderClass = propertyHolderClass; + this.constrainedElement = constrainedElement; + this.constraintHelper = constraintHelper; + this.typeResolutionHelper = typeResolutionHelper; + this.valueExtractorManager = valueExtractorManager; + + switch ( constrainedElement.getKind() ) { + case FIELD: + ConstrainedField constrainedField = (ConstrainedField) constrainedElement; + metaDataBuilder = new PropertyMetaData.Builder( + propertyHolderClass, + constrainedField, + constraintHelper, + typeResolutionHelper, + valueExtractorManager + ); + break; + default: + throw new IllegalStateException( + StringHelper.format( "Constrained element kind '%1$s' not supported here.", constrainedElement.getKind() ) ); + } + + this.hashCode = buildHashCode(); + } + + public boolean add(ConstrainedElement constrainedElement) { + if ( metaDataBuilder != null && metaDataBuilder.accepts( constrainedElement ) ) { + metaDataBuilder.add( constrainedElement ); + + return true; + } + + return false; + } + + public Set build() { + Set metaDataSet = newHashSet(); + + if ( metaDataBuilder != null ) { + metaDataSet.add( metaDataBuilder.build() ); + } + + return metaDataSet; + } + + @Override + public int hashCode() { + return hashCode; + } + + private int buildHashCode() { + final int prime = 31; + int result = 1; + result = prime * result + propertyHolderClass.hashCode(); + result = prime * result + constrainedElement.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( !super.equals( obj ) ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + BuilderDelegate other = (BuilderDelegate) obj; + if ( !propertyHolderClass.equals( other.propertyHolderClass ) ) { + return false; + } + if ( !constrainedElement.equals( other.constrainedElement ) ) { + return false; + } + return true; + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/CascadingMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/CascadingMetaDataBuilder.java new file mode 100644 index 0000000000..da6e3cb5d7 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/CascadingMetaDataBuilder.java @@ -0,0 +1,56 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated.cascading; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; +import java.util.Map; + +import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; +import org.hibernate.validator.internal.properties.Constrainable; + +/** + * @author Marko Bekhta + */ +public interface CascadingMetaDataBuilder { + + static CascadingMetaDataBuilder nonCascading() { + return NonCascadingMetaDataBuilder.INSTANCE; + } + + static CascadingMetaDataBuilder annotatedObject(Type cascadableType, boolean cascading, List containerElementTypesCascadingMetaData, Map, Class> groupConversions) { + return new SimpleBeanCascadingMetaDataBuilder( cascadableType, AnnotatedObject.INSTANCE, cascading, containerElementTypesCascadingMetaData, groupConversions ); + } + + static CascadingMetaDataBuilder annotatedObject(Type cascadableType, boolean cascading, Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map, Class> groupConversions) { + return new SimpleBeanCascadingMetaDataBuilder( cascadableType, AnnotatedObject.INSTANCE, cascading, containerElementTypesCascadingMetaData, groupConversions ); + } + + static CascadingMetaDataBuilder typeArgument(Type cascadableType, TypeVariable typeParameter, boolean cascading, List containerElementTypesCascadingMetaData, Map, Class> groupConversions) { + return new SimpleBeanCascadingMetaDataBuilder( cascadableType, typeParameter, cascading, containerElementTypesCascadingMetaData, groupConversions ); + } + + boolean isCascading(); + + Map, Class> getGroupConversions(); + + boolean hasContainerElementsMarkedForCascading(); + + boolean isMarkedForCascadingOnAnnotatedObjectOrContainerElements(); + + boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements(); + + Map, CascadingMetaDataBuilder> getContainerElementTypesCascadingMetaData(); + + CascadingMetaDataBuilder merge(CascadingMetaDataBuilder otherCascadingTypeParameter); + + CascadingMetaData build(ValueExtractorManager valueExtractorManager, Constrainable context); + +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ContainerCascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerCascadingMetaData.java similarity index 82% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ContainerCascadingMetaData.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerCascadingMetaData.java index 3ac00782e1..5635182e2f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/ContainerCascadingMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerCascadingMetaData.java @@ -4,7 +4,7 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata.aggregated; +package org.hibernate.validator.internal.metadata.aggregated.cascading; import java.lang.invoke.MethodHandles; import java.lang.reflect.Type; @@ -12,13 +12,13 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import javax.validation.metadata.GroupConversionDescriptor; import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.StringHelper; import org.hibernate.validator.internal.util.TypeVariables; @@ -66,7 +66,7 @@ public class ContainerCascadingMetaData implements CascadingMetaData { * Possibly the cascading type parameters corresponding to this type parameter if it is a parameterized type. */ @Immutable - private final List containerElementTypesCascadingMetaData; + private final List containerElementTypesCascadingMetaData; /** * If this type parameter is marked for cascading. @@ -90,28 +90,7 @@ public class ContainerCascadingMetaData implements CascadingMetaData { */ private final Set valueExtractorCandidates; - public static ContainerCascadingMetaData of(ValueExtractorManager valueExtractorManager, CascadingMetaDataBuilder cascadingMetaDataBuilder, - Object context) { - return new ContainerCascadingMetaData( valueExtractorManager, cascadingMetaDataBuilder ); - } - - private ContainerCascadingMetaData(ValueExtractorManager valueExtractorManager, CascadingMetaDataBuilder cascadingMetaDataBuilder) { - this( - valueExtractorManager, - cascadingMetaDataBuilder.getEnclosingType(), - cascadingMetaDataBuilder.getTypeParameter(), - cascadingMetaDataBuilder.getDeclaredContainerClass(), - cascadingMetaDataBuilder.getDeclaredTypeParameter(), - cascadingMetaDataBuilder.getContainerElementTypesCascadingMetaData().entrySet().stream() - .map( entry -> new ContainerCascadingMetaData( valueExtractorManager, entry.getValue() ) ) - .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) ), - cascadingMetaDataBuilder.isCascading(), - GroupConversionHelper.of( cascadingMetaDataBuilder.getGroupConversions() ), - cascadingMetaDataBuilder.isMarkedForCascadingOnAnnotatedObjectOrContainerElements() - ); - } - - private ContainerCascadingMetaData(ValueExtractorManager valueExtractorManager, Type enclosingType, TypeVariable typeParameter, + ContainerCascadingMetaData(ValueExtractorManager valueExtractorManager, Type enclosingType, TypeVariable typeParameter, Class declaredContainerClass, TypeVariable declaredTypeParameter, List containerElementTypesCascadingMetaData, boolean cascading, GroupConversionHelper groupConversionHelper, boolean markedForCascadingOnContainerElements) { this.enclosingType = enclosingType; @@ -138,7 +117,7 @@ private ContainerCascadingMetaData(ValueExtractorManager valueExtractorManager, } } - ContainerCascadingMetaData(Type enclosingType, List containerElementTypesCascadingMetaData, + ContainerCascadingMetaData(Type enclosingType, List containerElementTypesCascadingMetaData, GroupConversionHelper groupConversionHelper, Set valueExtractorCandidates) { this.enclosingType = enclosingType; this.typeParameter = AnnotatedObject.INSTANCE; @@ -206,7 +185,7 @@ public boolean isMarkedForCascadingOnAnnotatedObjectOrContainerElements() { return cascading || hasContainerElementsMarkedForCascading; } - public List getContainerElementTypesCascadingMetaData() { + public List getContainerElementTypesCascadingMetaData() { return containerElementTypesCascadingMetaData; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerPropertyHolderCascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerPropertyHolderCascadingMetaData.java new file mode 100644 index 0000000000..fb78e56417 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/ContainerPropertyHolderCascadingMetaData.java @@ -0,0 +1,61 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated.cascading; + +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; + +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; +import org.hibernate.validator.internal.util.StringHelper; + +/** + * Extended view of container cascading metadta for property holders that in addition stores mapping name. + * + * @author Marko Bekhta + */ +public class ContainerPropertyHolderCascadingMetaData extends ContainerCascadingMetaData { + + /** + * Name of the constraint mappings to be applied. + */ + private final String mapping; + + protected ContainerPropertyHolderCascadingMetaData(ValueExtractorManager valueExtractorManager, String mapping, Type enclosingType, TypeVariable typeParameter, + Class declaredContainerClass, TypeVariable declaredTypeParameter, List containerElementTypesCascadingMetaData, + boolean cascading, GroupConversionHelper groupConversionHelper, boolean markedForCascadingOnContainerElements) { + super( valueExtractorManager, + enclosingType, + typeParameter, + declaredContainerClass, + declaredTypeParameter, + containerElementTypesCascadingMetaData, + cascading, groupConversionHelper, markedForCascadingOnContainerElements ); + this.mapping = mapping; + } + + @Override + public BeanMetaData getBeanMetaDataForCascadable(ConstraintMetaDataManager constraintMetaDataManager, Object value) { + return constraintMetaDataManager.getPropertyHolderBeanMetaData( value.getClass(), mapping ); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append( getClass().getSimpleName() ); + sb.append( " [" ); + sb.append( "mapping=" ).append( mapping ).append( ", " ); + sb.append( "enclosingType=" ).append( StringHelper.toShortString( getEnclosingType() ) ).append( ", " ); + sb.append( "typeParameter=" ).append( getTypeParameter() ).append( ", " ); + sb.append( "cascading=" ).append( isCascading() ).append( ", " ); + sb.append( "containerElementTypesCascadingMetaData=" ).append( getContainerElementTypesCascadingMetaData() ); + sb.append( "]" ); + return sb.toString(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GroupConversionHelper.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/GroupConversionHelper.java similarity index 97% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GroupConversionHelper.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/GroupConversionHelper.java index 29f9d826fb..6f53e0c66c 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/GroupConversionHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/GroupConversionHelper.java @@ -4,7 +4,7 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata.aggregated; +package org.hibernate.validator.internal.metadata.aggregated.cascading; import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonCascadingMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonCascadingMetaDataBuilder.java new file mode 100644 index 0000000000..6251240124 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonCascadingMetaDataBuilder.java @@ -0,0 +1,63 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated.cascading; + +import java.lang.reflect.TypeVariable; +import java.util.Collections; +import java.util.Map; + +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; +import org.hibernate.validator.internal.properties.Constrainable; + +/** + * @author Marko Bekhta + */ +public final class NonCascadingMetaDataBuilder implements CascadingMetaDataBuilder { + + public static final CascadingMetaDataBuilder INSTANCE = new NonCascadingMetaDataBuilder(); + + @Override + public boolean isCascading() { + return false; + } + + @Override + public Map, Class> getGroupConversions() { + return Collections.emptyMap(); + } + + @Override + public boolean hasContainerElementsMarkedForCascading() { + return false; + } + + @Override + public boolean isMarkedForCascadingOnAnnotatedObjectOrContainerElements() { + return false; + } + + @Override + public boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements() { + return false; + } + + @Override + public Map, CascadingMetaDataBuilder> getContainerElementTypesCascadingMetaData() { + return Collections.emptyMap(); + } + + @Override + public CascadingMetaDataBuilder merge(CascadingMetaDataBuilder otherCascadingTypeParameter) { + return otherCascadingTypeParameter; + } + + @Override + public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Constrainable context) { + return NonContainerCascadingMetaData.of( false, Collections.emptyMap() ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonContainerCascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerCascadingMetaData.java similarity index 77% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonContainerCascadingMetaData.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerCascadingMetaData.java index 7ae7eb15a6..1a2ef03ef6 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/NonContainerCascadingMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerCascadingMetaData.java @@ -4,16 +4,18 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata.aggregated; +package org.hibernate.validator.internal.metadata.aggregated.cascading; import java.lang.invoke.MethodHandles; import java.lang.reflect.TypeVariable; +import java.util.Map; import java.util.Set; import javax.validation.metadata.GroupConversionDescriptor; import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; @@ -26,14 +28,16 @@ * @author Guillaume Smet * @author Marko Bekhta */ -public class NonContainerCascadingMetaData implements CascadingMetaData { +class NonContainerCascadingMetaData implements CascadingMetaData { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); - private static final NonContainerCascadingMetaData NON_CASCADING = new NonContainerCascadingMetaData( false, + private static final NonContainerCascadingMetaData NON_CASCADING = new NonContainerCascadingMetaData( + false, GroupConversionHelper.EMPTY ); - private static final NonContainerCascadingMetaData CASCADING_WITHOUT_GROUP_CONVERSIONS = new NonContainerCascadingMetaData( true, + private static final NonContainerCascadingMetaData CASCADING_WITHOUT_GROUP_CONVERSIONS = new NonContainerCascadingMetaData( + true, GroupConversionHelper.EMPTY ); /** @@ -46,26 +50,21 @@ public class NonContainerCascadingMetaData implements CascadingMetaData { */ private GroupConversionHelper groupConversionHelper; - public static NonContainerCascadingMetaData of(CascadingMetaDataBuilder cascadingMetaDataBuilder, Object context) { - if ( !cascadingMetaDataBuilder.isCascading() ) { + public static NonContainerCascadingMetaData of(boolean cascading, Map, Class> groupConversions) { + if ( !cascading ) { return NON_CASCADING; } - else if ( cascadingMetaDataBuilder.getGroupConversions().isEmpty() ) { + else if ( groupConversions.isEmpty() ) { return CASCADING_WITHOUT_GROUP_CONVERSIONS; } else { - return new NonContainerCascadingMetaData( cascadingMetaDataBuilder ); + return new NonContainerCascadingMetaData( + cascading, + GroupConversionHelper.of( groupConversions ) ); } } - private NonContainerCascadingMetaData(CascadingMetaDataBuilder cascadingMetaDataBuilder) { - this( - cascadingMetaDataBuilder.isCascading(), - GroupConversionHelper.of( cascadingMetaDataBuilder.getGroupConversions() ) - ); - } - - private NonContainerCascadingMetaData(boolean cascading, GroupConversionHelper groupConversionHelper) { + protected NonContainerCascadingMetaData(boolean cascading, GroupConversionHelper groupConversionHelper) { this.cascading = cascading; this.groupConversionHelper = groupConversionHelper; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerPropertyHolderCascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerPropertyHolderCascadingMetaData.java new file mode 100644 index 0000000000..8a3fb044ae --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/NonContainerPropertyHolderCascadingMetaData.java @@ -0,0 +1,53 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated.cascading; + +import java.util.Map; + +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; +import org.hibernate.validator.internal.util.Contracts; + +/** + * Extended view of non container cascading metadta for property holders that in addition stores mapping name. + * + * @author Marko Bekhta + */ +public class NonContainerPropertyHolderCascadingMetaData extends NonContainerCascadingMetaData { + + /** + * Name of the constraint mappings to be applied. + */ + private final String mapping; + + public static NonContainerPropertyHolderCascadingMetaData of(String mapping, Map, Class> groupConversions) { + Contracts.assertNotEmpty( mapping, "Property holder mapping cannot be an empty string." ); + + return new NonContainerPropertyHolderCascadingMetaData( mapping, groupConversions ); + } + + private NonContainerPropertyHolderCascadingMetaData(String mapping, Map, Class> groupConversions) { + super( true, GroupConversionHelper.of( groupConversions ) ); + this.mapping = mapping; + } + + @Override + public BeanMetaData getBeanMetaDataForCascadable(ConstraintMetaDataManager constraintMetaDataManager, Object value) { + return constraintMetaDataManager.getPropertyHolderBeanMetaData( value.getClass(), mapping ); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append( getClass().getSimpleName() ); + sb.append( " [" ); + sb.append( "mapping=" ).append( mapping ).append( ", " ); + sb.append( "cascading=" ).append( isCascading() ).append( ", " ); + sb.append( "]" ); + return sb.toString(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PotentiallyContainerCascadingMetaData.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PotentiallyContainerCascadingMetaData.java similarity index 75% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PotentiallyContainerCascadingMetaData.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PotentiallyContainerCascadingMetaData.java index d5efd88d08..1ac1f65e38 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PotentiallyContainerCascadingMetaData.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PotentiallyContainerCascadingMetaData.java @@ -4,11 +4,12 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata.aggregated; +package org.hibernate.validator.internal.metadata.aggregated.cascading; import java.lang.invoke.MethodHandles; import java.lang.reflect.TypeVariable; import java.util.Collections; +import java.util.Map; import java.util.Set; import javax.validation.metadata.GroupConversionDescriptor; @@ -16,6 +17,7 @@ import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; import org.hibernate.validator.internal.util.logging.Log; import org.hibernate.validator.internal.util.logging.LoggerFactory; @@ -37,12 +39,12 @@ public class PotentiallyContainerCascadingMetaData implements CascadingMetaData private final Set potentialValueExtractorDescriptors; - public static PotentiallyContainerCascadingMetaData of(CascadingMetaDataBuilder cascadingMetaDataBuilder, Set potentialValueExtractorDescriptors, Object context) { - return new PotentiallyContainerCascadingMetaData( cascadingMetaDataBuilder, potentialValueExtractorDescriptors ); + public static PotentiallyContainerCascadingMetaData of(Map, Class> groupConversions, Set potentialValueExtractorDescriptors) { + return new PotentiallyContainerCascadingMetaData( groupConversions, potentialValueExtractorDescriptors ); } - private PotentiallyContainerCascadingMetaData(CascadingMetaDataBuilder cascadingMetaDataBuilder, Set potentialValueExtractorDescriptors) { - this( potentialValueExtractorDescriptors, GroupConversionHelper.of( cascadingMetaDataBuilder.getGroupConversions() ) ); + protected PotentiallyContainerCascadingMetaData(Map, Class> groupConversions, Set potentialValueExtractorDescriptors) { + this( potentialValueExtractorDescriptors, GroupConversionHelper.of( groupConversions ) ); } private PotentiallyContainerCascadingMetaData(Set potentialValueExtractorDescriptors, GroupConversionHelper groupConversionHelper) { @@ -91,19 +93,23 @@ public CascadingMetaData addRuntimeContainerSupport(ValueExtractorManager valueE return new ContainerCascadingMetaData( valueClass, Collections.singletonList( - new ContainerCascadingMetaData( - compliantValueExtractor.getContainerType(), - compliantValueExtractor.getExtractedTypeParameter(), - compliantValueExtractor.getContainerType(), - compliantValueExtractor.getExtractedTypeParameter(), - groupConversionHelper.isEmpty() ? GroupConversionHelper.EMPTY : groupConversionHelper - ) + createInnerMetadata( compliantValueExtractor, groupConversionHelper ) ), groupConversionHelper, Collections.singleton( compliantValueExtractor ) ); } + protected ContainerCascadingMetaData createInnerMetadata(ValueExtractorDescriptor compliantValueExtractor, GroupConversionHelper groupConversionHelper) { + return new ContainerCascadingMetaData( + compliantValueExtractor.getContainerType(), + compliantValueExtractor.getExtractedTypeParameter(), + compliantValueExtractor.getContainerType(), + compliantValueExtractor.getExtractedTypeParameter(), + groupConversionHelper.isEmpty() ? GroupConversionHelper.EMPTY : groupConversionHelper + ); + } + @Override @SuppressWarnings("unchecked") public T as(Class clazz) { diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PropertyHolderCascadingMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PropertyHolderCascadingMetaDataBuilder.java new file mode 100644 index 0000000000..cbfe5e2ace --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/PropertyHolderCascadingMetaDataBuilder.java @@ -0,0 +1,148 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.aggregated.cascading; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; + +/** + * @author Marko Bekhta + */ +public class PropertyHolderCascadingMetaDataBuilder extends SimpleBeanCascadingMetaDataBuilder { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final String mapping; + + public PropertyHolderCascadingMetaDataBuilder( + Type enclosingType, + String mapping, + TypeVariable typeParameter, + boolean cascading, + Map, Class> groupConversions) { + super( enclosingType, typeParameter, cascading, Collections.emptyList(), groupConversions ); + this.mapping = mapping; + } + + public PropertyHolderCascadingMetaDataBuilder( + Type enclosingType, + String mapping, + TypeVariable typeParameter, + boolean cascading, + Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, + Map, Class> groupConversions) { + super( enclosingType, typeParameter, cascading, containerElementTypesCascadingMetaData, groupConversions ); + this.mapping = mapping; + } + + public static PropertyHolderCascadingMetaDataBuilder simplePropertyHolder( + String mappingName, + boolean cascading, + Map, Class> groupConversions) { + return new PropertyHolderCascadingMetaDataBuilder( null, mappingName, AnnotatedObject.INSTANCE, cascading, groupConversions ); + } + + public static PropertyHolderCascadingMetaDataBuilder propertyHolderContainer( + boolean cascading, + Class enclosingType, + Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, + Map, Class> groupConversions) { + return new PropertyHolderCascadingMetaDataBuilder( + enclosingType, + null, + AnnotatedObject.INSTANCE, + cascading, + containerElementTypesCascadingMetaData, + groupConversions ); + } + + public static PropertyHolderCascadingMetaDataBuilder propertyHolderContainer( + String mapping, + boolean cascading, + Class declaredContainerClass, + TypeVariable declaredTypeVariable, + Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, + Map, Class> groupConversions) { + return new PropertyHolderCascadingMetaDataBuilder( + declaredContainerClass, + mapping, + declaredTypeVariable, + cascading, + containerElementTypesCascadingMetaData, + groupConversions + ); + } + + @Override + public CascadingMetaDataBuilder merge(CascadingMetaDataBuilder otherCascadingTypeParameter) { + if ( otherCascadingTypeParameter == NonCascadingMetaDataBuilder.INSTANCE ) { + return this; + } + + boolean cascading = this.cascading || otherCascadingTypeParameter.isCascading(); + + Map, Class> groupConversions = mergeGroupConversion( this.groupConversions, otherCascadingTypeParameter.getGroupConversions() ); + + Map, CascadingMetaDataBuilder> nestedCascadingTypeParameterMap = Stream + .concat( + this.containerElementTypesCascadingMetaData.entrySet().stream(), + otherCascadingTypeParameter.getContainerElementTypesCascadingMetaData().entrySet().stream() ) + .collect( + Collectors.toMap( entry -> entry.getKey(), entry -> entry.getValue(), (value1, value2) -> value1.merge( value2 ) ) ); + + return new SimpleBeanCascadingMetaDataBuilder( this.enclosingType, this.typeParameter, cascading, nestedCascadingTypeParameterMap, groupConversions ); + } + + @Override + protected Set findContainerDetectionValueExtractorCandidates(ValueExtractorManager valueExtractorManager) { + return mapping != null ? Collections.emptySet() : super.findContainerDetectionValueExtractorCandidates( valueExtractorManager ); + } + + @Override + protected Set findPotentialValueExtractorCandidates(ValueExtractorManager valueExtractorManager) { + return Collections.emptySet(); + } + + @Override + protected NonContainerCascadingMetaData nonContainerCascadingMetaData() { + return mapping != null ? NonContainerPropertyHolderCascadingMetaData.of( mapping, groupConversions ) : super.nonContainerCascadingMetaData(); + } + + @Override + protected ContainerCascadingMetaData toContainerCascadingMetaData(ValueExtractorManager valueExtractorManager) { + if ( mapping == null ) { + return super.toContainerCascadingMetaData( valueExtractorManager ); + } + return new ContainerPropertyHolderCascadingMetaData( + valueExtractorManager, + mapping, + enclosingType, + typeParameter, + declaredContainerClass, + declaredTypeParameter, + containerElementTypesCascadingMetaData.entrySet().stream() + .map( entry -> toContainerCascadingMetaData( entry.getValue(), valueExtractorManager ) ) + .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) ), + cascading, + GroupConversionHelper.of( groupConversions ), + isMarkedForCascadingOnAnnotatedObjectOrContainerElements() + ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaDataBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/SimpleBeanCascadingMetaDataBuilder.java similarity index 67% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaDataBuilder.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/SimpleBeanCascadingMetaDataBuilder.java index a44d16b6ab..e31be8eb8e 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/CascadingMetaDataBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/cascading/SimpleBeanCascadingMetaDataBuilder.java @@ -4,13 +4,14 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata.aggregated; +package org.hibernate.validator.internal.metadata.aggregated.cascading; import java.lang.invoke.MethodHandles; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -20,12 +21,14 @@ import javax.validation.GroupSequence; -import org.hibernate.validator.internal.engine.valueextraction.AnnotatedObject; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorHelper; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; +import org.hibernate.validator.internal.properties.Constrainable; import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ReflectionHelper; import org.hibernate.validator.internal.util.StringHelper; import org.hibernate.validator.internal.util.TypeVariableBindings; @@ -39,50 +42,48 @@ * gets. * * @author Guillaume Smet + * @author Marko Bekhta */ -public class CascadingMetaDataBuilder { +public class SimpleBeanCascadingMetaDataBuilder implements CascadingMetaDataBuilder { private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); - private static final CascadingMetaDataBuilder NON_CASCADING = - new CascadingMetaDataBuilder( null, null, null, null, false, Collections.emptyMap(), Collections.emptyMap() ); - /** * The enclosing type that defines this type parameter. */ - private final Type enclosingType; + protected final Type enclosingType; /** * The type parameter. */ - private final TypeVariable typeParameter; + protected final TypeVariable typeParameter; /** * The declared container class: it is the one used in the node of the property path. */ - private final Class declaredContainerClass; + protected final Class declaredContainerClass; /** * The declared type parameter: it is the one used in the node of the property path. */ - private final TypeVariable declaredTypeParameter; + protected final TypeVariable declaredTypeParameter; /** * Possibly the cascading type parameters corresponding to this type parameter if it is a parameterized type. */ @Immutable - private final Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData; + protected final Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData; /** * If this type parameter is marked for cascading. */ - private final boolean cascading; + protected final boolean cascading; /** * Group conversions defined for this type parameter. */ @Immutable - private final Map, Class> groupConversions; + protected final Map, Class> groupConversions; /** * Whether any container element (it can be nested) is marked for cascaded validation. @@ -94,14 +95,27 @@ public class CascadingMetaDataBuilder { */ private final boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements; - public CascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParameter, boolean cascading, + public SimpleBeanCascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParameter, boolean cascading, + List containerElementTypesCascadingMetaData, Map, Class> groupConversions) { + this( + enclosingType, + typeParameter, + TypeVariables.getContainerClass( typeParameter ), + TypeVariables.getActualTypeParameter( typeParameter ), + cascading, + convertContainerElementTypesCascadingMetaData( containerElementTypesCascadingMetaData ), + groupConversions + ); + } + + public SimpleBeanCascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParameter, boolean cascading, Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map, Class> groupConversions) { this( enclosingType, typeParameter, TypeVariables.getContainerClass( typeParameter ), TypeVariables.getActualTypeParameter( typeParameter ), cascading, containerElementTypesCascadingMetaData, groupConversions ); } - private CascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParameter, Class declaredContainerClass, TypeVariable declaredTypeParameter, + private SimpleBeanCascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParameter, Class declaredContainerClass, TypeVariable declaredTypeParameter, boolean cascading, Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map, Class> groupConversions) { this.enclosingType = enclosingType; @@ -112,88 +126,68 @@ private CascadingMetaDataBuilder(Type enclosingType, TypeVariable typeParamet this.groupConversions = CollectionHelper.toImmutableMap( groupConversions ); this.containerElementTypesCascadingMetaData = CollectionHelper.toImmutableMap( containerElementTypesCascadingMetaData ); - boolean tmpHasContainerElementsMarkedForCascading = false; + boolean tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements = !groupConversions.isEmpty(); for ( CascadingMetaDataBuilder nestedCascadingTypeParameter : containerElementTypesCascadingMetaData.values() ) { - tmpHasContainerElementsMarkedForCascading = tmpHasContainerElementsMarkedForCascading - || nestedCascadingTypeParameter.cascading || nestedCascadingTypeParameter.hasContainerElementsMarkedForCascading; tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements = tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements - || nestedCascadingTypeParameter.hasGroupConversionsOnAnnotatedObjectOrContainerElements; + || nestedCascadingTypeParameter.hasGroupConversionsOnAnnotatedObjectOrContainerElements(); } - hasContainerElementsMarkedForCascading = tmpHasContainerElementsMarkedForCascading; hasGroupConversionsOnAnnotatedObjectOrContainerElements = tmpHasGroupConversionsOnAnnotatedObjectOrContainerElements; + hasContainerElementsMarkedForCascading = hasContainerElementsMarkedForCascading( containerElementTypesCascadingMetaData ); } - public static CascadingMetaDataBuilder nonCascading() { - return NON_CASCADING; - } - - public static CascadingMetaDataBuilder annotatedObject(Type cascadableType, boolean cascading, Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map, Class> groupConversions) { - return new CascadingMetaDataBuilder( cascadableType, AnnotatedObject.INSTANCE, cascading, containerElementTypesCascadingMetaData, groupConversions ); - } - - public TypeVariable getTypeParameter() { - return typeParameter; - } - - public Type getEnclosingType() { - return enclosingType; - } - - public Class getDeclaredContainerClass() { - return declaredContainerClass; - } - - public TypeVariable getDeclaredTypeParameter() { - return declaredTypeParameter; - } - + @Override public boolean isCascading() { return cascading; } + @Override public Map, Class> getGroupConversions() { return groupConversions; } + @Override public boolean hasContainerElementsMarkedForCascading() { return hasContainerElementsMarkedForCascading; } + @Override public boolean isMarkedForCascadingOnAnnotatedObjectOrContainerElements() { return cascading || hasContainerElementsMarkedForCascading; } + @Override public boolean hasGroupConversionsOnAnnotatedObjectOrContainerElements() { return hasGroupConversionsOnAnnotatedObjectOrContainerElements; } + @Override public Map, CascadingMetaDataBuilder> getContainerElementTypesCascadingMetaData() { return containerElementTypesCascadingMetaData; } + @Override public CascadingMetaDataBuilder merge(CascadingMetaDataBuilder otherCascadingTypeParameter) { - if ( this == NON_CASCADING ) { - return otherCascadingTypeParameter; - } - if ( otherCascadingTypeParameter == NON_CASCADING ) { + if ( otherCascadingTypeParameter == NonCascadingMetaDataBuilder.INSTANCE ) { return this; } - boolean cascading = this.cascading || otherCascadingTypeParameter.cascading; + boolean cascading = this.cascading || otherCascadingTypeParameter.isCascading(); - Map, Class> groupConversions = mergeGroupConversion( this.groupConversions, otherCascadingTypeParameter.groupConversions ); + Map, Class> groupConversions = mergeGroupConversion( this.groupConversions, otherCascadingTypeParameter.getGroupConversions() ); Map, CascadingMetaDataBuilder> nestedCascadingTypeParameterMap = Stream - .concat( this.containerElementTypesCascadingMetaData.entrySet().stream(), - otherCascadingTypeParameter.containerElementTypesCascadingMetaData.entrySet().stream() ) + .concat( + this.containerElementTypesCascadingMetaData.entrySet().stream(), + otherCascadingTypeParameter.getContainerElementTypesCascadingMetaData().entrySet().stream() ) .collect( - Collectors.toMap( entry -> entry.getKey(), entry -> entry.getValue(), ( value1, value2 ) -> value1.merge( value2 ) ) ); + Collectors.toMap( entry -> entry.getKey(), entry -> entry.getValue(), (value1, value2) -> value1.merge( value2 ) ) ); - return new CascadingMetaDataBuilder( this.enclosingType, this.typeParameter, cascading, nestedCascadingTypeParameterMap, groupConversions ); + return new SimpleBeanCascadingMetaDataBuilder( this.enclosingType, this.typeParameter, cascading, nestedCascadingTypeParameterMap, groupConversions ); } - public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Object context) { + @Override + public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Constrainable context) { validateGroupConversions( context ); // In the case the whole object is not annotated as cascading, we don't need to enable @@ -201,11 +195,11 @@ public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Obje if ( !cascading ) { // We have cascading enabled for at least one of the container elements if ( !containerElementTypesCascadingMetaData.isEmpty() && hasContainerElementsMarkedForCascading ) { - return ContainerCascadingMetaData.of( valueExtractorManager, this, context ); + return toContainerCascadingMetaData( valueExtractorManager ); } // It is not a container or it doesn't have cascading enabled on any container element else { - return NonContainerCascadingMetaData.of( this, context ); + return nonContainerCascadingMetaData(); } } @@ -223,8 +217,7 @@ public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Obje // // The value extractor returned here is just used to add the proper cascading metadata to the type // argument of the container. Proper value extractor resolution is executed at runtime. - Set containerDetectionValueExtractorCandidates = valueExtractorManager.getResolver() - .getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation( enclosingType ); + Set containerDetectionValueExtractorCandidates = findContainerDetectionValueExtractorCandidates( valueExtractorManager ); if ( !containerDetectionValueExtractorCandidates.isEmpty() ) { if ( containerDetectionValueExtractorCandidates.size() > 1 ) { throw LOG.getUnableToGetMostSpecificValueExtractorDueToSeveralMaximallySpecificValueExtractorsDeclaredException( @@ -233,17 +226,20 @@ public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Obje ); } - return ContainerCascadingMetaData.of( - valueExtractorManager, - new CascadingMetaDataBuilder( + return toContainerCascadingMetaData( + new SimpleBeanCascadingMetaDataBuilder( enclosingType, typeParameter, cascading, - addCascadingMetaDataBasedOnContainerDetection( enclosingType, containerElementTypesCascadingMetaData, groupConversions, - containerDetectionValueExtractorCandidates.iterator().next() ), + addCascadingMetaDataBasedOnContainerDetection( + enclosingType, + containerElementTypesCascadingMetaData, + groupConversions, + containerDetectionValueExtractorCandidates.iterator().next() + ), groupConversions ), - context + valueExtractorManager ); } @@ -254,18 +250,31 @@ public CascadingMetaData build(ValueExtractorManager valueExtractorManager, Obje // extends ContainerWithoutRegisteredVE) // so we are looking for VEs such that ValueExtractorDescriptor#getContainerType() is assignable to the declared // type under inspection. - Set potentialValueExtractorCandidates = valueExtractorManager.getResolver() - .getPotentialValueExtractorCandidatesForCascadedValidation( enclosingType ); + Set potentialValueExtractorCandidates = findPotentialValueExtractorCandidates( valueExtractorManager ); // if such VEs were found we return an instance of PotentiallyContainerCascadingMetaData that will store those potential VEs // and they will be used at runtime to check if any of those could be applied to a runtime type and if PotentiallyContainerCascadingMetaData // should be promoted to ContainerCascadingMetaData or not. if ( !potentialValueExtractorCandidates.isEmpty() ) { - return PotentiallyContainerCascadingMetaData.of( this, potentialValueExtractorCandidates, context ); + return PotentiallyContainerCascadingMetaData.of( groupConversions, potentialValueExtractorCandidates ); } // if cascading == false, or none of the above cases matched we just return a non container metadata - return NonContainerCascadingMetaData.of( this, context ); + return nonContainerCascadingMetaData(); + } + + protected NonContainerCascadingMetaData nonContainerCascadingMetaData() { + return NonContainerCascadingMetaData.of( cascading, groupConversions ); + } + + protected Set findContainerDetectionValueExtractorCandidates(ValueExtractorManager valueExtractorManager) { + return valueExtractorManager.getResolver() + .getValueExtractorCandidatesForContainerDetectionOfGlobalCascadedValidation( enclosingType ); + } + + protected Set findPotentialValueExtractorCandidates(ValueExtractorManager valueExtractorManager) { + return valueExtractorManager.getResolver() + .getPotentialValueExtractorCandidatesForCascadedValidation( enclosingType ); } private void validateGroupConversions(Object context) { @@ -282,7 +291,9 @@ private void validateGroupConversions(Object context) { } for ( CascadingMetaDataBuilder containerElementCascadingTypeParameter : containerElementTypesCascadingMetaData.values() ) { - containerElementCascadingTypeParameter.validateGroupConversions( context ); + if ( containerElementCascadingTypeParameter instanceof SimpleBeanCascadingMetaDataBuilder ) { + ( (SimpleBeanCascadingMetaDataBuilder) containerElementCascadingTypeParameter ).validateGroupConversions( context ); + } } } @@ -326,7 +337,7 @@ public boolean equals(Object obj) { if ( getClass() != obj.getClass() ) { return false; } - CascadingMetaDataBuilder other = (CascadingMetaDataBuilder) obj; + SimpleBeanCascadingMetaDataBuilder other = (SimpleBeanCascadingMetaDataBuilder) obj; if ( !typeParameter.equals( other.typeParameter ) ) { return false; } @@ -342,7 +353,26 @@ public boolean equals(Object obj) { return true; } - private static Map, Class> mergeGroupConversion(Map, Class> groupConversions, Map, Class> otherGroupConversions) { + private static Map, CascadingMetaDataBuilder> convertContainerElementTypesCascadingMetaData(List containerElementTypesCascadingMetaData) { + Map, CascadingMetaDataBuilder> converted = CollectionHelper.newHashMap( containerElementTypesCascadingMetaData.size() ); + for ( CascadingMetaDataBuilder containerElement : containerElementTypesCascadingMetaData ) { + Contracts.assertTrue( containerElement instanceof SimpleBeanCascadingMetaDataBuilder, "Supports only SimpleBeanCascadingMetaDataBuilder." ); + + converted.put( ( (SimpleBeanCascadingMetaDataBuilder) containerElement ).typeParameter, containerElement ); + } + return converted; + } + + private boolean hasContainerElementsMarkedForCascading(Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) { + boolean tmpHasContainerElementsMarkedForCascading = false; + for ( CascadingMetaDataBuilder nestedCascadingTypeParameter : containerElementTypesCascadingMetaData.values() ) { + tmpHasContainerElementsMarkedForCascading = tmpHasContainerElementsMarkedForCascading + || nestedCascadingTypeParameter.isCascading() || nestedCascadingTypeParameter.hasContainerElementsMarkedForCascading(); + } + return tmpHasContainerElementsMarkedForCascading; + } + + protected static Map, Class> mergeGroupConversion(Map, Class> groupConversions, Map, Class> otherGroupConversions) { if ( groupConversions.isEmpty() && otherGroupConversions.isEmpty() ) { // this is a rather common case so let's optimize it return Collections.emptyMap(); @@ -368,7 +398,7 @@ private static Map, Class> mergeGroupConversion(Map, Class< private static Map, CascadingMetaDataBuilder> addCascadingMetaDataBasedOnContainerDetection(Type cascadableType, Map, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData, Map, Class> groupConversions, - ValueExtractorDescriptor possibleValueExtractor) { + ValueExtractorDescriptor possibleValueExtractor) { Class cascadableClass = ReflectionHelper.getClassFromType( cascadableType ); if ( cascadableClass.isArray() ) { // for arrays, we need to add an ArrayElement cascading metadata: it's the only way arrays support cascading at the moment. @@ -376,13 +406,13 @@ private static Map, CascadingMetaDataBuilder> addCascadingMetaDa } else { Map, CascadingMetaDataBuilder> cascadingMetaData = containerElementTypesCascadingMetaData; - cascadingMetaData = addCascadingMetaData( - cascadableClass, - possibleValueExtractor.getContainerType(), - possibleValueExtractor.getExtractedTypeParameter(), - cascadingMetaData, - groupConversions - ); + cascadingMetaData = addCascadingMetaData( + cascadableClass, + possibleValueExtractor.getContainerType(), + possibleValueExtractor.getExtractedTypeParameter(), + cascadingMetaData, + groupConversions + ); return cascadingMetaData; } } @@ -413,12 +443,14 @@ private static Map, CascadingMetaDataBuilder> addCascadingMetaDa amendedCascadingMetadata.putAll( containerElementTypesCascadingMetaData ); if ( containerElementTypesCascadingMetaData.containsKey( cascadableTypeParameter ) ) { - amendedCascadingMetadata.put( cascadableTypeParameter, - makeCascading( containerElementTypesCascadingMetaData.get( cascadableTypeParameter ), groupConversions ) ); + amendedCascadingMetadata.put( + cascadableTypeParameter, + makeCascading( (SimpleBeanCascadingMetaDataBuilder) containerElementTypesCascadingMetaData.get( cascadableTypeParameter ), groupConversions ) ); } else { - amendedCascadingMetadata.put( cascadableTypeParameter, - new CascadingMetaDataBuilder( cascadableClass, cascadableTypeParameter, enclosingType, correspondingTypeParameter, true, + amendedCascadingMetadata.put( + cascadableTypeParameter, + new SimpleBeanCascadingMetaDataBuilder( cascadableClass, cascadableTypeParameter, enclosingType, correspondingTypeParameter, true, Collections.emptyMap(), groupConversions ) ); } @@ -433,15 +465,37 @@ private static Map, CascadingMetaDataBuilder> addArrayElementCas TypeVariable cascadableTypeParameter = new ArrayElement( enclosingType ); - amendedCascadingMetadata.put( cascadableTypeParameter, - new CascadingMetaDataBuilder( enclosingType, cascadableTypeParameter, true, Collections.emptyMap(), groupConversions ) ); + amendedCascadingMetadata.put( + cascadableTypeParameter, + new SimpleBeanCascadingMetaDataBuilder( enclosingType, cascadableTypeParameter, true, Collections.emptyMap(), groupConversions ) ); return amendedCascadingMetadata; } - private static CascadingMetaDataBuilder makeCascading(CascadingMetaDataBuilder cascadingTypeParameter, Map, Class> groupConversions) { - return new CascadingMetaDataBuilder( cascadingTypeParameter.enclosingType, cascadingTypeParameter.typeParameter, true, + private static SimpleBeanCascadingMetaDataBuilder makeCascading(SimpleBeanCascadingMetaDataBuilder cascadingTypeParameter, Map, Class> groupConversions) { + return new SimpleBeanCascadingMetaDataBuilder( cascadingTypeParameter.enclosingType, cascadingTypeParameter.typeParameter, true, cascadingTypeParameter.containerElementTypesCascadingMetaData, cascadingTypeParameter.groupConversions.isEmpty() ? groupConversions : cascadingTypeParameter.groupConversions ); } + + protected ContainerCascadingMetaData toContainerCascadingMetaData(ValueExtractorManager valueExtractorManager) { + return new ContainerCascadingMetaData( + valueExtractorManager, + enclosingType, + typeParameter, + declaredContainerClass, + declaredTypeParameter, + containerElementTypesCascadingMetaData.entrySet().stream() + .map( entry -> toContainerCascadingMetaData( entry.getValue(), valueExtractorManager ) ) + .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) ), + cascading, + GroupConversionHelper.of( groupConversions ), + isMarkedForCascadingOnAnnotatedObjectOrContainerElements() + ); + } + + protected static ContainerCascadingMetaData toContainerCascadingMetaData(CascadingMetaDataBuilder builder, ValueExtractorManager valueExtractorManager) { + Contracts.assertTrue( builder instanceof SimpleBeanCascadingMetaDataBuilder, "Only instances of SimpleBeanCascadingMetaDataBuilder type are supported here." ); + return ( (SimpleBeanCascadingMetaDataBuilder) builder ).toContainerCascadingMetaData( valueExtractorManager ); + } } diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraintBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraintBuilder.java new file mode 100644 index 0000000000..821cb61a40 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/core/MetaConstraintBuilder.java @@ -0,0 +1,88 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.core; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.validation.ValidationException; + +import org.hibernate.validator.cfg.AnnotationDef; +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; +import org.hibernate.validator.internal.metadata.location.ConstraintLocation; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.internal.util.annotation.AnnotationDescriptor; +import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; +import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethodHandle; + +/** + * @author Marko Bekhta + */ +public class MetaConstraintBuilder { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private static final MethodHandle CREATE_ANNOTATION_DESCRIPTOR_METHOD_HANDLE = + run( GetDeclaredMethodHandle.andMakeAccessible( MethodHandles.lookup(), AnnotationDef.class, "createAnnotationDescriptor" ) ); + + private final ConstraintAnnotationDescriptor annotationDescriptor; + + public MetaConstraintBuilder(ConstraintAnnotationDescriptor annotationDescriptor) { + this.annotationDescriptor = annotationDescriptor; + } + + public MetaConstraintBuilder(ConstraintDef constraintDef) { + this( createAnnotationDescriptor( constraintDef ) ); + } + + public MetaConstraint build( + TypeResolutionHelper typeResolutionHelper, + ConstraintHelper constraintHelper, + ValueExtractorManager valueExtractorManager, + ConstraintLocation constraintLocation) { + return MetaConstraints.create( + typeResolutionHelper, + valueExtractorManager, + new ConstraintDescriptorImpl<>( + constraintHelper, + constraintLocation.getConstrainable(), + annotationDescriptor, + constraintLocation.getKind() + ), + constraintLocation ); + } + + private static ConstraintAnnotationDescriptor createAnnotationDescriptor(ConstraintDef constraint) { + try { + @SuppressWarnings("unchecked") + AnnotationDescriptor annotationDescriptor = (AnnotationDescriptor) CREATE_ANNOTATION_DESCRIPTOR_METHOD_HANDLE.invoke( constraint ); + return new ConstraintAnnotationDescriptor<>( annotationDescriptor ); + } + catch (Throwable e) { + if ( e instanceof ValidationException ) { + throw (ValidationException) e; + } + throw LOG.getUnableToCreateAnnotationDescriptor( constraint.getClass(), e ); + } + } + + /** + * Runs the given privileged action, using a privileged block if required. + * NOTE: This must never be changed into a publicly available method to avoid execution of arbitrary + * privileged actions within HV's protection domain. + */ + private static V run(PrivilegedAction action) { + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java index fd5864a3d2..d7f6a82aed 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/facets/Cascadable.java @@ -10,7 +10,7 @@ import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaData; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.location.ConstraintLocation.ConstraintLocationKind; /** diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/BeanMetaDataProvider.java similarity index 87% rename from engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManager.java rename to engine/src/main/java/org/hibernate/validator/internal/metadata/manager/BeanMetaDataProvider.java index d1908005e3..cb24506efe 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManager.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/BeanMetaDataProvider.java @@ -4,15 +4,12 @@ * License: Apache License, Version 2.0 * See the license.txt file in the root directory or . */ -package org.hibernate.validator.internal.metadata; +package org.hibernate.validator.internal.metadata.manager; import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList; -import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.Option.IDENTITY_COMPARISONS; -import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.ReferenceType.SOFT; import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES; import java.util.ArrayList; -import java.util.EnumSet; import java.util.List; import javax.validation.valueextraction.ValueExtractor; @@ -22,7 +19,7 @@ import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; -import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl.BeanMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -31,7 +28,6 @@ import org.hibernate.validator.internal.metadata.raw.BeanConfiguration; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; import org.hibernate.validator.internal.util.CollectionHelper; -import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap; import org.hibernate.validator.internal.util.Contracts; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; @@ -57,23 +53,9 @@ * @author Gunnar Morling * @author Chris Beckey <cbeckey@paypal.com> * @author Guillaume Smet -*/ -public class BeanMetaDataManager { - /** - * The default initial capacity for this cache. - */ - private static final int DEFAULT_INITIAL_CAPACITY = 16; - - /** - * The default load factor for this cache. - */ - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - - /** - * The default concurrency level for this cache. - */ - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - + * @author Marko Bekhta + */ +public class BeanMetaDataProvider { /** * Additional metadata providers used for meta data retrieval if * the XML and/or programmatic configuration is used. @@ -98,11 +80,6 @@ public class BeanMetaDataManager { private final ExecutableParameterNameProvider parameterNameProvider; - /** - * Used to cache the constraint meta data for validated entities - */ - private final ConcurrentReferenceHashMap, BeanMetaData> beanMetaDataCache; - /** * Used for resolving type parameters. Thread-safe. */ @@ -117,7 +94,9 @@ public class BeanMetaDataManager { */ private final MethodValidationConfiguration methodValidationConfiguration; - public BeanMetaDataManager(ConstraintHelper constraintHelper, + private final MetaDataCache> beanMetaDataCache; + + public BeanMetaDataProvider(ConstraintHelper constraintHelper, ExecutableHelper executableHelper, TypeResolutionHelper typeResolutionHelper, ExecutableParameterNameProvider parameterNameProvider, @@ -135,14 +114,8 @@ public BeanMetaDataManager(ConstraintHelper constraintHelper, this.methodValidationConfiguration = methodValidationConfiguration; - this.beanMetaDataCache = new ConcurrentReferenceHashMap<>( - DEFAULT_INITIAL_CAPACITY, - DEFAULT_LOAD_FACTOR, - DEFAULT_CONCURRENCY_LEVEL, - SOFT, - SOFT, - EnumSet.of( IDENTITY_COMPARISONS ) - ); + this.beanMetaDataCache = new MetaDataCache<>(); + AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders ); AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider( @@ -167,7 +140,8 @@ public BeanMetaDataManager(ConstraintHelper constraintHelper, public BeanMetaData getBeanMetaData(Class beanClass) { Contracts.assertNotNull( beanClass, MESSAGES.beanTypeCannotBeNull() ); - BeanMetaData beanMetaData = (BeanMetaData) beanMetaDataCache.computeIfAbsent( beanClass, + BeanMetaData beanMetaData = (BeanMetaData) beanMetaDataCache.computeIfAbsent( + beanClass, bc -> createBeanMetaData( bc ) ); return beanMetaData; @@ -221,6 +195,7 @@ private AnnotationProcessingOptions getAnnotationProcessingOptionsFromNonDefault * * @param beanClass The type of interest. * @param The type of the class to get the configurations for. + * * @return A set with the configurations for the complete hierarchy of the given type. May be empty, but never * {@code null}. */ diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/ConstraintMetaDataManager.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/ConstraintMetaDataManager.java new file mode 100644 index 0000000000..38f7173ec7 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/ConstraintMetaDataManager.java @@ -0,0 +1,98 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.manager; + +import java.util.List; + +import org.hibernate.validator.internal.engine.MethodValidationConfiguration; +import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.provider.proeprtyholder.PropertyHolderMetaDataProvider; +import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.util.ExecutableHelper; +import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; +import org.hibernate.validator.internal.util.TypeResolutionHelper; + +/** + * TODO: provide correct description. + *

+ * This manager is in charge of providing all constraint related meta data + * required by the validation engine. + *

+ * Actual retrieval of meta data is delegated to {@link MetaDataProvider} + * implementations which load meta-data based e.g. based on annotations or XML. + *

+ * For performance reasons a cache is used which stores all meta data once + * loaded for repeated retrieval. Upon initialization this cache is populated + * with meta data provided by the given eager providers. If the cache + * doesn't contain the meta data for a requested type it will be retrieved on + * demand using the annotation based provider. + * + * @author Gunnar Morling + * @author Chris Beckey <cbeckey@paypal.com> + * @author Guillaume Smet + * @author Marko Bekhta + */ +public class ConstraintMetaDataManager { + + private final BeanMetaDataProvider beanMetaDataProvider; + + private final PropertyHolderBeanMetaDataProvider propertyHolderBeanMetaDataProvider; + + public ConstraintMetaDataManager(ConstraintHelper constraintHelper, + ExecutableHelper executableHelper, + TypeResolutionHelper typeResolutionHelper, + ExecutableParameterNameProvider parameterNameProvider, + ValueExtractorManager valueExtractorManager, + PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, + JavaBeanHelper javaBeanHelper, + ValidationOrderGenerator validationOrderGenerator, + MethodValidationConfiguration methodValidationConfiguration, + List optionalMetaDataProviders, + List propertyHolderMetaDataProviders) { + this.beanMetaDataProvider = new BeanMetaDataProvider( + constraintHelper, + executableHelper, + typeResolutionHelper, + parameterNameProvider, + valueExtractorManager, + javaBeanHelper, + validationOrderGenerator, + optionalMetaDataProviders, + methodValidationConfiguration + ); + this.propertyHolderBeanMetaDataProvider = new PropertyHolderBeanMetaDataProvider( + propertyHolderMetaDataProviders, + constraintHelper, + typeResolutionHelper, + valueExtractorManager, + propertyAccessorCreatorProvider, + validationOrderGenerator + ); + } + + public BeanMetaData getBeanMetaData(Class beanClass) { + return beanMetaDataProvider.getBeanMetaData( beanClass ); + } + + public BeanMetaData getPropertyHolderBeanMetaData(Class propertyHolderClass, String mapping) { + return propertyHolderBeanMetaDataProvider.getBeanMetaData( propertyHolderClass, mapping ); + } + + public void clear() { + beanMetaDataProvider.clear(); + propertyHolderBeanMetaDataProvider.clear(); + } + + public int numberOfCachedBeanMetaDataInstances() { + return beanMetaDataProvider.numberOfCachedBeanMetaDataInstances() + propertyHolderBeanMetaDataProvider.numberOfCachedBeanMetaDataInstances(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/MetaDataCache.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/MetaDataCache.java new file mode 100644 index 0000000000..12df0af801 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/MetaDataCache.java @@ -0,0 +1,71 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.manager; + +import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.Option.IDENTITY_COMPARISONS; +import static org.hibernate.validator.internal.util.ConcurrentReferenceHashMap.ReferenceType.SOFT; + +import java.util.EnumSet; +import java.util.function.Function; + +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap; + +/** + * This is a preconfigured cache for storing bean metadata + * + * @author Marko Bekhta + */ +public final class MetaDataCache { + /** + * The default initial capacity for this cache. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 16; + + /** + * The default load factor for this cache. + */ + private static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * The default concurrency level for this cache. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * Used to cache the constraint meta data for validated entities + */ + private final ConcurrentReferenceHashMap> beanMetaDataCache; + + public MetaDataCache() { + this.beanMetaDataCache = new ConcurrentReferenceHashMap<>( + DEFAULT_INITIAL_CAPACITY, + DEFAULT_LOAD_FACTOR, + DEFAULT_CONCURRENCY_LEVEL, + SOFT, + SOFT, + EnumSet.of( IDENTITY_COMPARISONS ) + ); + } + + + public void clear() { + beanMetaDataCache.clear(); + } + + public int numberOfCachedBeanMetaDataInstances() { + return beanMetaDataCache.size(); + } + + public int size() { + return beanMetaDataCache.size(); + } + + public BeanMetaData computeIfAbsent(K key, Function> mappingFunction) { + return beanMetaDataCache.computeIfAbsent( key, mappingFunction ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/PropertyHolderBeanMetaDataProvider.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/PropertyHolderBeanMetaDataProvider.java new file mode 100644 index 0000000000..8566b50c1c --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/PropertyHolderBeanMetaDataProvider.java @@ -0,0 +1,140 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.manager; + +import java.util.List; +import java.util.Optional; + +import javax.validation.valueextraction.ValueExtractor; + +import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; +import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; +import org.hibernate.validator.internal.metadata.aggregated.PropertyHolderBeanMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.provider.proeprtyholder.PropertyHolderMetaDataProvider; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.internal.util.stereotypes.Immutable; + +/** + * @author Marko Bekhta + */ +public class PropertyHolderBeanMetaDataProvider { + + @Immutable + private final List propertyHolderMetaDataProviderList; + + /** + * Helper for builtin constraints and their validator implementations + */ + private final ConstraintHelper constraintHelper; + + /** + * Used for resolving generic type information. + */ + private final TypeResolutionHelper typeResolutionHelper; + + /** + * The {@link ValueExtractor} manager. + */ + private final ValueExtractorManager valueExtractorManager; + + private final PropertyAccessorCreatorProvider propertyAccessorCreatorProvider; + + private final ValidationOrderGenerator validationOrderGenerator; + + private final MetaDataCache metaDataCache; + + public PropertyHolderBeanMetaDataProvider(List propertyHolderMetaDataProviderList, ConstraintHelper constraintHelper, TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager, PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, ValidationOrderGenerator validationOrderGenerator) { + this.propertyHolderMetaDataProviderList = propertyHolderMetaDataProviderList; + this.constraintHelper = constraintHelper; + this.typeResolutionHelper = typeResolutionHelper; + this.valueExtractorManager = valueExtractorManager; + this.propertyAccessorCreatorProvider = propertyAccessorCreatorProvider; + this.validationOrderGenerator = validationOrderGenerator; + + this.metaDataCache = new MetaDataCache<>(); + } + + @SuppressWarnings("unchecked") + public BeanMetaData getBeanMetaData(Class propertyHolderClass, String mapping) { + return (BeanMetaData) metaDataCache.computeIfAbsent( + new PropertyHolderMetadataKey( propertyHolderClass, mapping ), + key -> createBeanMetaData( key ) + ); + } + + private BeanMetaDataImpl createBeanMetaData(PropertyHolderMetadataKey metadataKey) { + PropertyHolderBeanMetaDataBuilder builder = PropertyHolderBeanMetaDataBuilder.getInstance( + constraintHelper, typeResolutionHelper, valueExtractorManager, propertyAccessorCreatorProvider, validationOrderGenerator, metadataKey.propertyHolderClass + ); + + for ( PropertyHolderMetaDataProvider metaDataProvider : propertyHolderMetaDataProviderList ) { + Optional beanConfiguration = metaDataProvider.getBeanConfiguration( metadataKey.mapping ); + if ( beanConfiguration.isPresent() ) { + builder.add( beanConfiguration.get() ); + } + } + return builder.build(); + } + + public void clear() { + metaDataCache.clear(); + } + + public int numberOfCachedBeanMetaDataInstances() { + return metaDataCache.size(); + } + + private static class PropertyHolderMetadataKey { + private String mapping; + private Class propertyHolderClass; + private int hashCode; + + public PropertyHolderMetadataKey(Class propertyHolderClass, String mapping) { + this.mapping = mapping; + this.propertyHolderClass = propertyHolderClass; + + this.hashCode = buildHashCode(); + } + + private int buildHashCode() { + int result = mapping.hashCode(); + result = 31 * result + propertyHolderClass.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PropertyHolderMetadataKey that = (PropertyHolderMetadataKey) o; + + if ( !mapping.equals( that.mapping ) ) { + return false; + } + if ( !propertyHolderClass.equals( that.propertyHolderClass ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/package-info.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/package-info.java new file mode 100644 index 0000000000..cdb0a7ca23 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/manager/package-info.java @@ -0,0 +1,10 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +/** + * Various metadata manager related interfaces and their implementations. + */ +package org.hibernate.validator.internal.metadata.manager; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/AnnotationMetaDataProvider.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/AnnotationMetaDataProvider.java index 0f68ebf22c..69903adfb4 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/AnnotationMetaDataProvider.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/AnnotationMetaDataProvider.java @@ -41,7 +41,8 @@ import org.hibernate.validator.group.GroupSequenceProvider; import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.SimpleBeanCascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; @@ -666,7 +667,7 @@ private Map, CascadingMetaDataBuilder> getTypeParametersCascadin Map, CascadingMetaDataBuilder> nestedTypeParametersCascadingMetadata = getTypeParametersCascadingMetaDataForAnnotatedType( annotatedTypeArgument ); - typeParametersCascadingMetadata.put( typeParameters[i], new CascadingMetaDataBuilder( annotatedParameterizedType.getType(), typeParameters[i], + typeParametersCascadingMetadata.put( typeParameters[i], new SimpleBeanCascadingMetaDataBuilder( annotatedParameterizedType.getType(), typeParameters[i], annotatedTypeArgument.isAnnotationPresent( Valid.class ), nestedTypeParametersCascadingMetadata, getGroupConversions( annotatedTypeArgument ) ) ); i++; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/DummyPropertyHolderMetaDataProvider.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/DummyPropertyHolderMetaDataProvider.java new file mode 100644 index 0000000000..1b797b8826 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/DummyPropertyHolderMetaDataProvider.java @@ -0,0 +1,205 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.provider.proeprtyholder; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import javax.validation.ValidationException; + +import org.hibernate.validator.cfg.AnnotationDef; +import org.hibernate.validator.cfg.ConstraintDef; +import org.hibernate.validator.cfg.defs.EmailDef; +import org.hibernate.validator.cfg.defs.MinDef; +import org.hibernate.validator.cfg.defs.NotNullDef; +import org.hibernate.validator.cfg.defs.SizeDef; +import org.hibernate.validator.internal.metadata.aggregated.cascading.PropertyHolderCascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.propertyholder.CascadingConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.metadata.raw.propertyholder.SimpleConstrainedPropertyHolderElementBuilder; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.annotation.AnnotationDescriptor; +import org.hibernate.validator.internal.util.annotation.ConstraintAnnotationDescriptor; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; +import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethodHandle; + +/** + * A dummy metadata provider just for testing purposes. To be removed compeletely later. + * + * @author Marko Bekhta + */ +public class DummyPropertyHolderMetaDataProvider implements PropertyHolderMetaDataProvider { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private static final MethodHandle CREATE_ANNOTATION_DESCRIPTOR_METHOD_HANDLE = + run( GetDeclaredMethodHandle.andMakeAccessible( MethodHandles.lookup(), AnnotationDef.class, "createAnnotationDescriptor" ) ); + + public static final String USER_MAPPING_NAME = "user"; + public static final String ADDRESS_MAPPING_NAME = "address"; + + @Override + public Optional getBeanConfiguration(String mappingName) { + switch ( mappingName ) { + case USER_MAPPING_NAME: + return Optional.of( user() ); + case ADDRESS_MAPPING_NAME: + return Optional.of( address() ); + default: + return Optional.empty(); + } + } + + private static PropertyHolderConfiguration user() { + return new PropertyHolderConfiguration( + ConfigurationSource.API, + USER_MAPPING_NAME, + CollectionHelper.asSet( + new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "name", + String.class, + CollectionHelper.asSet( + new MetaConstraintBuilder( + createAnnotationDescriptor( new NotNullDef() ) + ), + new MetaConstraintBuilder( + createAnnotationDescriptor( new SizeDef().min( 5 ).max( 10 ) ) + ) + ), + Collections.emptySet() + ), + new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "email", + String.class, + CollectionHelper.asSet( + new MetaConstraintBuilder( + createAnnotationDescriptor( new NotNullDef() ) + ), + new MetaConstraintBuilder( + createAnnotationDescriptor( new EmailDef() ) + ) + ), + Collections.emptySet() + ), + new CascadingConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "address", + Collections.emptySet(), + Collections.emptySet(), + PropertyHolderCascadingMetaDataBuilder.simplePropertyHolder( + ADDRESS_MAPPING_NAME, + true, + Collections.emptyMap() + ) + ), + new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "secondaryAddresses", + List.class, + CollectionHelper.asSet( + new MetaConstraintBuilder( + createAnnotationDescriptor( new NotNullDef() ) + ), + new MetaConstraintBuilder( + createAnnotationDescriptor( new SizeDef().max( 2 ) ) + ) + ), + Collections.emptySet(), + PropertyHolderCascadingMetaDataBuilder.propertyHolderContainer( + false, //TODO: need to throw exception if cascading is true here or maybe completely remove the ability to set it ? + List.class, + Collections.singletonMap( + List.class.getTypeParameters()[0], + PropertyHolderCascadingMetaDataBuilder.propertyHolderContainer( + ADDRESS_MAPPING_NAME, + true, + List.class, + List.class.getTypeParameters()[0], + Collections.emptyMap(), + Collections.emptyMap() + ) + ), + Collections.emptyMap() + ) + ) + ), + Collections.emptyList() + ); + } + + private static PropertyHolderConfiguration address() { + return new PropertyHolderConfiguration( + ConfigurationSource.API, + USER_MAPPING_NAME, + CollectionHelper.asSet( + new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "street", + String.class, + CollectionHelper.asSet( + new MetaConstraintBuilder( + createAnnotationDescriptor( new NotNullDef() ) + ), + new MetaConstraintBuilder( + createAnnotationDescriptor( new SizeDef().min( 5 ).max( 10 ) ) + ) + ), + Collections.emptySet() + ), + new SimpleConstrainedPropertyHolderElementBuilder( + ConfigurationSource.API, + "buildingNumber", + Long.class, + CollectionHelper.asSet( + new MetaConstraintBuilder( + createAnnotationDescriptor( new NotNullDef() ) + ), + new MetaConstraintBuilder( + createAnnotationDescriptor( new MinDef().value( 0 ) ) + ) + ), + Collections.emptySet() + ) + ), + Collections.emptyList() + ); + } + + private static ConstraintAnnotationDescriptor createAnnotationDescriptor(ConstraintDef constraint) { + try { + @SuppressWarnings("unchecked") + AnnotationDescriptor annotationDescriptor = (AnnotationDescriptor) CREATE_ANNOTATION_DESCRIPTOR_METHOD_HANDLE.invoke( constraint ); + return new ConstraintAnnotationDescriptor<>( annotationDescriptor ); + } + catch (Throwable e) { + if ( e instanceof ValidationException ) { + throw (ValidationException) e; + } + throw LOG.getUnableToCreateAnnotationDescriptor( constraint.getClass(), e ); + } + } + + /** + * Runs the given privileged action, using a privileged block if required. + * NOTE: This must never be changed into a publicly available method to avoid execution of arbitrary + * privileged actions within HV's protection domain. + */ + private static V run(PrivilegedAction action) { + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/ProgrammaticPropertyHolderMetaDataProvider.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/ProgrammaticPropertyHolderMetaDataProvider.java new file mode 100644 index 0000000000..38c18a6121 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/ProgrammaticPropertyHolderMetaDataProvider.java @@ -0,0 +1,88 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.provider.proeprtyholder; + +import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet; + +import java.lang.invoke.MethodHandles; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.hibernate.validator.internal.cfg.propertyholder.PropertyHolderConstraintMappingImpl; +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.Contracts; +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.internal.util.stereotypes.Immutable; + +/** + * A property holder {@link MetaDataProvider} based on the programmatic constraint API. + * + * @author Gunnar Morling + * @author Marko Bekhta + */ +public class ProgrammaticPropertyHolderMetaDataProvider implements PropertyHolderMetaDataProvider { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + @Immutable + private final Map configuredBeans; + + public ProgrammaticPropertyHolderMetaDataProvider(ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, + ValueExtractorManager valueExtractorManager, + Set constraintMappings) { + Contracts.assertNotNull( constraintMappings ); + + configuredBeans = CollectionHelper.toImmutableMap( + createBeanConfigurations( constraintMappings, constraintHelper, typeResolutionHelper, valueExtractorManager ) + ); + + assertUniquenessOfConfiguredTypes( constraintMappings ); + } + + private static void assertUniquenessOfConfiguredTypes(Set mappings) { + Set allConfiguredTypes = newHashSet(); + + for ( PropertyHolderConstraintMappingImpl constraintMapping : mappings ) { + for ( String propertyHolderMappingName : constraintMapping.getConfiguredMappingNames() ) { + if ( allConfiguredTypes.contains( propertyHolderMappingName ) ) { + throw LOG.getPropertyHolderMappingHasAlreadyBeenConfiguredViaProgrammaticApiException( propertyHolderMappingName ); + } + } + + allConfiguredTypes.addAll( constraintMapping.getConfiguredMappingNames() ); + } + } + + private static Map createBeanConfigurations(Set mappings, ConstraintHelper constraintHelper, + TypeResolutionHelper typeResolutionHelper, ValueExtractorManager valueExtractorManager) { + final Map configuredBeans = new HashMap<>(); + for ( PropertyHolderConstraintMappingImpl mapping : mappings ) { + Set beanConfigurations = mapping.getPropertyHolderConfigurations( constraintHelper, typeResolutionHelper, + valueExtractorManager + ); + + for ( PropertyHolderConfiguration beanConfiguration : beanConfigurations ) { + configuredBeans.put( beanConfiguration.getMappingName(), beanConfiguration ); + } + } + return configuredBeans; + } + + @Override + public Optional getBeanConfiguration(String mappingName) { + return Optional.ofNullable( configuredBeans.get( mappingName ) ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/PropertyHolderMetaDataProvider.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/PropertyHolderMetaDataProvider.java new file mode 100644 index 0000000000..2b3c47f389 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/provider/proeprtyholder/PropertyHolderMetaDataProvider.java @@ -0,0 +1,19 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.provider.proeprtyholder; + +import java.util.Optional; + +import org.hibernate.validator.internal.metadata.raw.propertyholder.PropertyHolderConfiguration; + +/** + * @author Marko Bekhta + */ +public interface PropertyHolderMetaDataProvider { + + Optional getBeanConfiguration(String mappingName); +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/AbstractConstrainedElement.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/AbstractConstrainedElement.java index 4ac6c1676f..6bbc4abe50 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/AbstractConstrainedElement.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/AbstractConstrainedElement.java @@ -10,7 +10,7 @@ import java.util.Iterator; import java.util.Set; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.stereotypes.Immutable; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedElement.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedElement.java index a1b1c456fd..47d6b93bcc 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedElement.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedElement.java @@ -11,7 +11,7 @@ import javax.validation.Valid; import javax.validation.groups.ConvertGroup; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; /** diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedExecutable.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedExecutable.java index 2d9a612929..9b5c518ed1 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedExecutable.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedExecutable.java @@ -17,7 +17,7 @@ import javax.validation.metadata.ConstraintDescriptor; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.properties.Callable; import org.hibernate.validator.internal.util.CollectionHelper; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedField.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedField.java index 143ad0fcc0..66f5aa1b02 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedField.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedField.java @@ -8,7 +8,7 @@ import java.util.Set; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.properties.Field; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedParameter.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedParameter.java index dab9ac417c..63f304906b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedParameter.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedParameter.java @@ -13,7 +13,7 @@ import java.util.HashSet; import java.util.Set; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.properties.Callable; diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedType.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedType.java index a5fe71148b..d237afdc28 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedType.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/ConstrainedType.java @@ -9,7 +9,7 @@ import java.util.Collections; import java.util.Set; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; /** diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/CascadingConstrainedPropertyHolderElementBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/CascadingConstrainedPropertyHolderElementBuilder.java new file mode 100644 index 0000000000..be2a902783 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/CascadingConstrainedPropertyHolderElementBuilder.java @@ -0,0 +1,38 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.raw.propertyholder; + +import java.util.Set; + +import org.hibernate.validator.internal.metadata.aggregated.cascading.PropertyHolderCascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.properties.PropertyAccessor; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.properties.propertyholder.PropertyHolderProperty; +import org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator; + +/** + * @author Marko Bekhta + */ +public class CascadingConstrainedPropertyHolderElementBuilder extends ConstrainedPropertyHolderElementBuilder { + + public CascadingConstrainedPropertyHolderElementBuilder(ConfigurationSource source, + String name, Set> constraints, + Set> typeArgumentConstraints, + PropertyHolderCascadingMetaDataBuilder cascadingMetaDataBuilder) { + super( source, name, constraints, typeArgumentConstraints, cascadingMetaDataBuilder ); + } + + @Override + protected PropertyHolderProperty createPropertyHolderProperty(PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, Class propertyHolderType) { + PropertyAccessorCreator propertyAccessorCreator = propertyAccessorCreatorProvider.getPropertyAccessorCreatorFor( propertyHolderType ); + PropertyAccessor propertyAccessor = propertyAccessorCreator.create( name, propertyHolderType ); + + return new PropertyHolderProperty( propertyHolderType, propertyAccessor, name, propertyHolderType ); + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElement.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElement.java new file mode 100644 index 0000000000..b8ac53515c --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElement.java @@ -0,0 +1,119 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.raw.propertyholder; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.MetaConstraint; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.ConstrainedElement; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.stereotypes.Immutable; + +/** + * Base implementation of with functionality common to all {@link ConstrainedElement} implementations. + * + * @author Gunnar Morling + * @author Hardy Ferentschik + * @author Marko Bekhta + */ +public abstract class ConstrainedPropertyHolderElement implements ConstrainedElement { + private final ConstrainedElementKind kind; + protected final ConfigurationSource source; + @Immutable + protected final Set> constraints; + protected final CascadingMetaDataBuilder cascadingMetaDataBuilder; + @Immutable + protected final Set> typeArgumentConstraints; + + public ConstrainedPropertyHolderElement(ConfigurationSource source, + ConstrainedElementKind kind, + Set> constraints, + Set> typeArgumentConstraints, + CascadingMetaDataBuilder cascadingMetaDataBuilder) { + this.kind = kind; + this.source = source; + this.constraints = constraints != null ? CollectionHelper.toImmutableSet( constraints ) : Collections.>emptySet(); + this.typeArgumentConstraints = typeArgumentConstraints != null ? CollectionHelper.toImmutableSet( typeArgumentConstraints ) : Collections.>emptySet(); + this.cascadingMetaDataBuilder = cascadingMetaDataBuilder; + } + + @Override + public ConstrainedElementKind getKind() { + return kind; + } + + @Override + public Iterator> iterator() { + return constraints.iterator(); + } + + @Override + public Set> getConstraints() { + return constraints; + } + + @Override + public Set> getTypeArgumentConstraints() { + return typeArgumentConstraints; + } + + @Override + public CascadingMetaDataBuilder getCascadingMetaDataBuilder() { + return cascadingMetaDataBuilder; + } + + @Override + public boolean isConstrained() { + return cascadingMetaDataBuilder.isMarkedForCascadingOnAnnotatedObjectOrContainerElements() + || cascadingMetaDataBuilder.hasGroupConversionsOnAnnotatedObjectOrContainerElements() + || !constraints.isEmpty() + || !typeArgumentConstraints.isEmpty(); + } + + @Override + public ConfigurationSource getSource() { + return source; + } + + @Override + public String toString() { + return "AbstractConstrainedElement [kind=" + kind + ", source=" + + source + ", constraints=" + + constraints + ", cascadingMetaDataBuilder=" + + cascadingMetaDataBuilder + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( source == null ) ? 0 : source.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + ConstrainedPropertyHolderElement other = (ConstrainedPropertyHolderElement) obj; + if ( source != other.source ) { + return false; + } + return true; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElementBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElementBuilder.java new file mode 100644 index 0000000000..df9a20a9c9 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/ConstrainedPropertyHolderElementBuilder.java @@ -0,0 +1,125 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.raw.propertyholder; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.ConstraintHelper; +import org.hibernate.validator.internal.metadata.core.MetaConstraint; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.location.ConstraintLocation; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.metadata.raw.ConstrainedElement; +import org.hibernate.validator.internal.metadata.raw.ConstrainedField; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.properties.propertyholder.PropertyHolderProperty; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.TypeResolutionHelper; +import org.hibernate.validator.internal.util.stereotypes.Immutable; + +/** + * @author Marko Bekhta + */ +public abstract class ConstrainedPropertyHolderElementBuilder { + + protected final ConfigurationSource source; + + protected final String name; + + @Immutable + protected final Set> constraints; + protected final CascadingMetaDataBuilder cascadingMetaDataBuilder; + @Immutable + protected final Set> typeArgumentConstraints; + + public ConstrainedPropertyHolderElementBuilder(ConfigurationSource source, + String name, Set> constraints, + Set> typeArgumentConstraints, + CascadingMetaDataBuilder cascadingMetaDataBuilder) { + this.source = source; + this.name = name; + this.constraints = CollectionHelper.toImmutableSetOfNullable( constraints ); + this.typeArgumentConstraints = CollectionHelper.toImmutableSetOfNullable( typeArgumentConstraints ); + this.cascadingMetaDataBuilder = cascadingMetaDataBuilder; + } + + public boolean isConstrained() { + return cascadingMetaDataBuilder.isMarkedForCascadingOnAnnotatedObjectOrContainerElements() + || cascadingMetaDataBuilder.hasGroupConversionsOnAnnotatedObjectOrContainerElements() + || !constraints.isEmpty() + || !typeArgumentConstraints.isEmpty(); + } + + public ConstrainedElement build(TypeResolutionHelper typeResolutionHelper, ConstraintHelper constraintHelper, ValueExtractorManager valueExtractorManager, PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, Class propertyHolderType) { + PropertyHolderProperty property = createPropertyHolderProperty( propertyAccessorCreatorProvider, propertyHolderType ); + + return new ConstrainedField( + source, + property, + toMetaConstraints( typeResolutionHelper, constraintHelper, valueExtractorManager, property, constraints ), + toMetaConstraints( typeResolutionHelper, constraintHelper, valueExtractorManager, property, typeArgumentConstraints ), + cascadingMetaDataBuilder + ); + } + + protected abstract PropertyHolderProperty createPropertyHolderProperty(PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, Class propertyHolderType); + + protected Set> toMetaConstraints(TypeResolutionHelper typeResolutionHelper, ConstraintHelper constraintHelper, ValueExtractorManager valueExtractorManager, PropertyHolderProperty property, Collection> collection) { + Set> builtConstraints = new HashSet<>( constraints.size() ); + ConstraintLocation constraintLocation = ConstraintLocation.forField( property ); + for ( MetaConstraintBuilder builder : constraints ) { + builtConstraints.add( builder.build( typeResolutionHelper, constraintHelper, valueExtractorManager, constraintLocation ) ); + } + return builtConstraints; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder( getClass().getSimpleName() ); + sb.append( "{" ); + sb.append( "source=" ).append( source ); + sb.append( ", name='" ).append( name ).append( '\'' ); + sb.append( ", constraints=" ).append( constraints ); + sb.append( ", cascadingMetaDataBuilder=" ).append( cascadingMetaDataBuilder ); + sb.append( ", typeArgumentConstraints=" ).append( typeArgumentConstraints ); + sb.append( '}' ); + return sb.toString(); + } + + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + ConstrainedPropertyHolderElementBuilder that = (ConstrainedPropertyHolderElementBuilder) o; + + if ( source != that.source ) { + return false; + } + if ( !name.equals( that.name ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = source.hashCode(); + result = 31 * result + name.hashCode(); + return result; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/PropertyHolderConfiguration.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/PropertyHolderConfiguration.java new file mode 100644 index 0000000000..f0f76cd690 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/PropertyHolderConfiguration.java @@ -0,0 +1,93 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.raw.propertyholder; + +import java.util.List; +import java.util.Set; + +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; + +/** + * @author Marko Bekhta + */ +public class PropertyHolderConfiguration { + + private final ConfigurationSource source; + + private final String mappingName; + + private final Set constrainedElements; + + private final List> defaultGroupSequence; + + public PropertyHolderConfiguration( + ConfigurationSource source, + String mappingName, + Set constrainedElements, + List> defaultGroupSequence) { + + this.source = source; + this.mappingName = mappingName; + this.constrainedElements = constrainedElements; + this.defaultGroupSequence = defaultGroupSequence; + } + + public ConfigurationSource getSource() { + return source; + } + + public String getMappingName() { + return mappingName; + } + + public Set getConstrainedElements() { + return constrainedElements; + } + + public List> getDefaultGroupSequence() { + return defaultGroupSequence; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder( "PropertyHolderConfiguration{" ); + sb.append( "source=" ).append( source ); + sb.append( ", mappingName='" ).append( mappingName ).append( '\'' ); + sb.append( ", constrainedElements=" ).append( constrainedElements ); + sb.append( ", defaultGroupSequence=" ).append( defaultGroupSequence ); + sb.append( '}' ); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PropertyHolderConfiguration that = (PropertyHolderConfiguration) o; + + if ( source != that.source ) { + return false; + } + if ( !mappingName.equals( that.mappingName ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = source.hashCode(); + result = 31 * result + mappingName.hashCode(); + return result; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/SimpleConstrainedPropertyHolderElementBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/SimpleConstrainedPropertyHolderElementBuilder.java new file mode 100644 index 0000000000..331f069608 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/raw/propertyholder/SimpleConstrainedPropertyHolderElementBuilder.java @@ -0,0 +1,71 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.metadata.raw.propertyholder; + +import java.util.Set; + +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.core.MetaConstraintBuilder; +import org.hibernate.validator.internal.metadata.raw.ConfigurationSource; +import org.hibernate.validator.internal.properties.PropertyAccessor; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; +import org.hibernate.validator.internal.properties.propertyholder.PropertyHolderProperty; +import org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator; + +/** + * @author Marko Bekhta + */ +public class SimpleConstrainedPropertyHolderElementBuilder extends ConstrainedPropertyHolderElementBuilder { + + private final Class type; + + public SimpleConstrainedPropertyHolderElementBuilder(ConfigurationSource source, + String name, Class type, Set> constraints, + Set> typeArgumentConstraints, CascadingMetaDataBuilder cascadingMetaDataBuilder) { + super( source, name, constraints, typeArgumentConstraints, cascadingMetaDataBuilder ); + this.type = type; + } + + public SimpleConstrainedPropertyHolderElementBuilder(ConfigurationSource source, + String name, Class type, Set> constraints, + Set> typeArgumentConstraints) { + this( source, name, type, constraints, typeArgumentConstraints, CascadingMetaDataBuilder.nonCascading() ); + } + + @Override + protected PropertyHolderProperty createPropertyHolderProperty(PropertyAccessorCreatorProvider propertyAccessorCreatorProvider, Class propertyHolderType) { + PropertyAccessorCreator propertyAccessorCreator = propertyAccessorCreatorProvider.getPropertyAccessorCreatorFor( propertyHolderType ); + PropertyAccessor propertyAccessor = propertyAccessorCreator.create( name, type ); + + return new PropertyHolderProperty( propertyHolderType, propertyAccessor, name, type ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + SimpleConstrainedPropertyHolderElementBuilder that = (SimpleConstrainedPropertyHolderElementBuilder) o; + + if ( !type.equals( that.type ) ) { + return false; + } + + return super.equals( o ); + } + + @Override + public int hashCode() { + int result = type.hashCode(); + result = 31 * result + super.hashCode(); + return result; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyAccessorCreatorProvider.java b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyAccessorCreatorProvider.java new file mode 100644 index 0000000000..c1dae8294b --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyAccessorCreatorProvider.java @@ -0,0 +1,113 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.properties.propertyholder; + +import java.lang.invoke.MethodHandles; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.hibernate.validator.internal.properties.propertyholder.map.MapPropertyAccessorCreator; +import org.hibernate.validator.internal.util.CollectionHelper; +import org.hibernate.validator.internal.util.TypeHelper; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; +import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader; +import org.hibernate.validator.internal.util.privilegedactions.GetInstancesFromServiceLoader; +import org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator; + +/** + * @author Marko Bekhta + */ +public class PropertyAccessorCreatorProvider { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + private final Set> configuredPropertyCreators = new HashSet<>(); + + public PropertyAccessorCreatorProvider() { + //add default property creator for a Map + configuredPropertyCreators.add( new MapPropertyAccessorCreator() ); + + List propertyAccessorCreators = run( GetInstancesFromServiceLoader.action( + run( GetClassLoader.fromContext() ), + PropertyAccessorCreator.class + ) ); + for ( PropertyAccessorCreator propertyAccessorCreator : propertyAccessorCreators ) { + configuredPropertyCreators.add( propertyAccessorCreator ); + } + } + + @SuppressWarnings("unchecked") + public PropertyAccessorCreator getPropertyAccessorCreatorFor(Class propertyHolderType) { + + Set possibleCreators = configuredPropertyCreators + .stream() + .filter( el -> TypeHelper.isAssignable( el.getPropertyHolderType(), propertyHolderType ) ) + .collect( Collectors.toSet() ); + + Set creators = getMaximallySpecificPropertyAccessorCreators( possibleCreators ); + + if ( creators.isEmpty() ) { + throw LOG.getUnableToFindPropertyCreatorException( propertyHolderType ); + } + else if ( creators.size() > 1 ) { + throw LOG.getUnableToFinUniquedPropertyCreatorException( propertyHolderType ); + } + else { + return creators.iterator().next(); + } + } + + private Set getMaximallySpecificPropertyAccessorCreators(Set possiblePropertyAccessorCreators) { + Set propertyAccessorCreators = CollectionHelper.newHashSet( possiblePropertyAccessorCreators.size() ); + + for ( PropertyAccessorCreator creator : possiblePropertyAccessorCreators ) { + if ( propertyAccessorCreators.isEmpty() ) { + propertyAccessorCreators.add( creator ); + continue; + } + Iterator candidatesIterator = propertyAccessorCreators.iterator(); + boolean isNewRoot = true; + while ( candidatesIterator.hasNext() ) { + PropertyAccessorCreator candidate = candidatesIterator.next(); + + // we consider the strictly more specific value extractor so 2 value extractors for the same container + // type should throw an error in the end if no other more specific value extractor is found. + if ( candidate.getPropertyHolderType().equals( creator.getPropertyHolderType() ) ) { + continue; + } + + if ( TypeHelper.isAssignable( candidate.getPropertyHolderType(), creator.getPropertyHolderType() ) ) { + candidatesIterator.remove(); + } + else if ( TypeHelper.isAssignable( creator.getPropertyHolderType(), candidate.getPropertyHolderType() ) ) { + isNewRoot = false; + } + } + if ( isNewRoot ) { + propertyAccessorCreators.add( creator ); + } + } + return propertyAccessorCreators; + } + + /** + * Runs the given privileged action, using a privileged block if required. + * + * NOTE: This must never be changed into a publicly available method to avoid execution of arbitrary + * privileged actions within HV's protection domain. + */ + private T run(PrivilegedAction action) { + return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); + } + +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyHolderProperty.java b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyHolderProperty.java new file mode 100644 index 0000000000..4ba2087d33 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/PropertyHolderProperty.java @@ -0,0 +1,94 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.properties.propertyholder; + +import java.lang.reflect.Type; + +import org.hibernate.validator.internal.properties.Field; +import org.hibernate.validator.internal.properties.Property; +import org.hibernate.validator.internal.properties.PropertyAccessor; + +/** + * @author Marko Bekhta + */ +public class PropertyHolderProperty implements Property, Field { + + private final Class propertyHolderType; + private final PropertyAccessor propertyAccessor; + + private final String name; + private final Class type; + + public PropertyHolderProperty(Class propertyHolderType, PropertyAccessor propertyAccessor, String name, Class type) { + this.propertyHolderType = propertyHolderType; + this.propertyAccessor = propertyAccessor; + this.name = name; + this.type = type; + } + + @Override + public String getPropertyName() { + return getName(); + } + + @Override + public PropertyAccessor createAccessor() { + return propertyAccessor; + } + + @Override + public String getName() { + return name; + } + + @Override + public Class getDeclaringClass() { + return propertyHolderType; + } + + @Override + public Type getTypeForValidatorResolution() { + return getType(); + } + + @Override + public Type getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + PropertyHolderProperty that = (PropertyHolderProperty) o; + + if ( !propertyHolderType.equals( that.propertyHolderType ) ) { + return false; + } + if ( !name.equals( that.name ) ) { + return false; + } + if ( !type.equals( that.type ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = propertyHolderType.hashCode(); + result = 31 * result + name.hashCode(); + result = 31 * result + type.hashCode(); + return result; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/MapPropertyAccessorCreator.java b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/MapPropertyAccessorCreator.java new file mode 100644 index 0000000000..b34e5044ad --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/MapPropertyAccessorCreator.java @@ -0,0 +1,58 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.internal.properties.propertyholder.map; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Type; +import java.util.Map; + +import org.hibernate.validator.internal.properties.PropertyAccessor; +import org.hibernate.validator.internal.util.TypeHelper; +import org.hibernate.validator.internal.util.logging.Log; +import org.hibernate.validator.internal.util.logging.LoggerFactory; +import org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator; + +/** + * @author Marko Bekhta + */ +public class MapPropertyAccessorCreator implements PropertyAccessorCreator { + + private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() ); + + @Override + public Class getPropertyHolderType() { + return Map.class; + } + + @Override + public PropertyAccessor create(String propertyName, Type propertyType) { + return new MapPropertyAccessor( propertyName, propertyType ); + } + + private static class MapPropertyAccessor implements PropertyAccessor { + + private final String name; + private final Type type; + + private MapPropertyAccessor(String name, Type type) { + this.name = name; + this.type = type; + } + + @Override + public Object getValueFrom(Object bean) { + if ( !( bean instanceof Map ) ) { + throw LOG.getUnexpextedPropertyHolderTypeException( Map.class, bean.getClass() ); + } + Object value = ( (Map) bean ).get( name ); + if ( value != null && !TypeHelper.isAssignable( type, value.getClass() ) ) { + throw LOG.getUnexpextedPropertyTypeInPropertyHolderException( type, value.getClass(), name ); + } + return value; + } + } +} diff --git a/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/package-info.java b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/package-info.java new file mode 100644 index 0000000000..08fa45c3f6 --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/map/package-info.java @@ -0,0 +1,11 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +/** + * Contains default {@link org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator} implementaion for {@link java.util.Map}, + * and related classes. + */ +package org.hibernate.validator.internal.properties.propertyholder.map; diff --git a/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/package-info.java b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/package-info.java new file mode 100644 index 0000000000..7e074b01dd --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/internal/properties/propertyholder/package-info.java @@ -0,0 +1,12 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +/** + * Contains {@link org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider} + * and all related classes, as well as default {@link org.hibernate.validator.spi.propertyholder.PropertyAccessorCreator} + * implementaion for {@link java.util.Map}. + */ +package org.hibernate.validator.internal.properties.propertyholder; diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java b/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java index cb678c0c88..406adb7a9a 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/CollectionHelper.java @@ -126,6 +126,15 @@ public static Map toImmutableMap(Map map) { } } + public static Set toImmutableSetOfNullable(Set set) { + if ( set == null ) { + return Collections.emptySet(); + } + else { + return toImmutableSet( set ); + } + } + /** * As the default loadFactor is of 0.75, we need to calculate the initial capacity from the expected size to avoid * resizing the collection when we populate the collection with all the initial elements. We use a calculation diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java index c79e7ca4ef..bae671c137 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Log.java @@ -875,4 +875,25 @@ ConstraintDefinitionException getConstraintValidatorDefinitionConstraintMismatch @Message(id = 248, value = "Unable to get an XML schema named %s.") ValidationException unableToGetXmlSchema(String schemaResourceName); + + @Message(id = 249, value = "Unable to find property creator for property holder of %1$s type.") + ValidationException getUnableToFindPropertyCreatorException(@FormatWith(ClassObjectFormatter.class) Class clazz); + + @Message(id = 250, value = "Unable to find single unique property creator for property holder of %1$s type.") + ValidationException getUnableToFinUniquedPropertyCreatorException(@FormatWith(ClassObjectFormatter.class) Class clazz); + + @Message(id = 251, value = "Unexpected property holder type received. Expected %1$s type, but instead got %2$s.") + ValidationException getUnexpextedPropertyHolderTypeException(@FormatWith(ClassObjectFormatter.class) Class expecetedHolderType, @FormatWith(ClassObjectFormatter.class) Class realHolderType); + + @Message(id = 252, value = "Unexpected property type in a given property holder. Expected that '%3$s' will be of %1$s type, but instead it is %2$s.") + ValidationException getUnexpextedPropertyTypeInPropertyHolderException(Type expecetedType, @FormatWith(ClassObjectFormatter.class) Class realType, String propertyName); + + @Message(id = 253, value = "%s is configured more than once via the programmatic constraint declaration API.") + ValidationException getPropertyHolderMappingHasAlreadyBeenConfiguredViaProgrammaticApiException(String mappingName); + + @Message(id = 254, value = "Property \"%2$s\" in mapping %1$s is configured more than once via the programmatic constraint declaration API.") + ValidationException getPropertyHolderMappingPropertyHasAlreadyBeenConfiguredViaProgrammaticApiException(String mappingName, String propertyName); + + @Message(id = 255, value = "Object of type \"%1$s\" is not a property holder, and cannot be validated as one.") + ValidationException getCannotConvertToPropertyHolderException(@FormatWith(ClassObjectFormatter.class) Class clazz); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Messages.java b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Messages.java index f03e6102ee..9bd56a5ae9 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/util/logging/Messages.java +++ b/engine/src/main/java/org/hibernate/validator/internal/util/logging/Messages.java @@ -97,4 +97,8 @@ public interface Messages { @Message(value = "The annotation type must be annotated with @javax.validation.Constraint when creating a constraint definition.", format = Message.Format.NO_FORMAT) String annotationTypeMustBeAnnotatedWithConstraint(); + + @Message(value = "Mapping name must not be null when creating a constraint mapping.", + format = Message.Format.NO_FORMAT) + String mappingNameMustNotBeNull(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/AbstractConstrainedElementStaxBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/AbstractConstrainedElementStaxBuilder.java index 4791436852..ba900e74e7 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/AbstractConstrainedElementStaxBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/AbstractConstrainedElementStaxBuilder.java @@ -19,7 +19,7 @@ import javax.xml.stream.events.XMLEvent; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedConstructorStaxBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedConstructorStaxBuilder.java index d7f56ab36f..9fbdaf508f 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedConstructorStaxBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedConstructorStaxBuilder.java @@ -16,7 +16,7 @@ import javax.xml.namespace.QName; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedMethodStaxBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedMethodStaxBuilder.java index 658ea6d07b..a0e2c1a261 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedMethodStaxBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedMethodStaxBuilder.java @@ -16,7 +16,7 @@ import javax.xml.namespace.QName; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeConfigurationBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeConfigurationBuilder.java index fd424cd664..511084002b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeConfigurationBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeConfigurationBuilder.java @@ -17,7 +17,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.MetaConstraint; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeStaxBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeStaxBuilder.java index eeb2d16a6d..d354059d5b 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeStaxBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ContainerElementTypeStaxBuilder.java @@ -25,7 +25,8 @@ import org.hibernate.validator.internal.engine.valueextraction.ArrayElement; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.SimpleBeanCascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.location.ConstraintLocation; import org.hibernate.validator.internal.util.CollectionHelper; @@ -151,7 +152,7 @@ public ContainerElementTypeConfiguration build(Set con boolean isCascaded = validStaxBuilder.build(); - containerElementTypesCascadingMetaDataBuilder.put( typeParameter, new CascadingMetaDataBuilder( enclosingType, typeParameter, isCascaded, + containerElementTypesCascadingMetaDataBuilder.put( typeParameter, new SimpleBeanCascadingMetaDataBuilder( enclosingType, typeParameter, isCascaded, nestedContainerElementTypeConfiguration.getTypeParametersCascadingMetaData(), groupConversionBuilder.build() ) diff --git a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ReturnValueStaxBuilder.java b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ReturnValueStaxBuilder.java index 8157a2ba6d..495b2fdaed 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ReturnValueStaxBuilder.java +++ b/engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ReturnValueStaxBuilder.java @@ -13,7 +13,7 @@ import javax.xml.namespace.QName; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.aggregated.CascadingMetaDataBuilder; +import org.hibernate.validator.internal.metadata.aggregated.cascading.CascadingMetaDataBuilder; import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.core.MetaConstraint; diff --git a/engine/src/main/java/org/hibernate/validator/spi/propertyholder/PropertyAccessorCreator.java b/engine/src/main/java/org/hibernate/validator/spi/propertyholder/PropertyAccessorCreator.java new file mode 100644 index 0000000000..48198cdc2b --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/spi/propertyholder/PropertyAccessorCreator.java @@ -0,0 +1,35 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.spi.propertyholder; + +import java.lang.reflect.Type; + +import org.hibernate.validator.internal.properties.PropertyAccessor; + +/** + * This interface expose the functionality of creating the property accessor based on + * a string representation of a property. + * + * @author Marko Bekhta + */ +public interface PropertyAccessorCreator { + + /** + * @return the type of the property holder this creator can be applied to. + */ + Class getPropertyHolderType(); + + /** + * Creates property accessor for a given name and type. + * + * @param propertyName property name + * @param propertyType property type + * + * @return created property + */ + PropertyAccessor create(String propertyName, Type propertyType); +} diff --git a/engine/src/main/java/org/hibernate/validator/spi/propertyholder/package-info.java b/engine/src/main/java/org/hibernate/validator/spi/propertyholder/package-info.java new file mode 100644 index 0000000000..e8018bd99e --- /dev/null +++ b/engine/src/main/java/org/hibernate/validator/spi/propertyholder/package-info.java @@ -0,0 +1,15 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ + +/** + *

+ * This package provides support for customization of the property holder validation by + * exposing a set of interfaces that helps creating properties for custom property holders. + *

+ *

This package is part of the public Hibernate Validator API.

+ */ +package org.hibernate.validator.spi.propertyholder; diff --git a/engine/src/test/java/org/hibernate/validator/test/cfg/ConfigurationFilePropertiesTest.java b/engine/src/test/java/org/hibernate/validator/test/cfg/ConfigurationFilePropertiesTest.java index 8cdc4f3d01..10f5f834af 100644 --- a/engine/src/test/java/org/hibernate/validator/test/cfg/ConfigurationFilePropertiesTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/cfg/ConfigurationFilePropertiesTest.java @@ -18,7 +18,8 @@ import org.hibernate.validator.internal.IgnoreForbiddenApisErrors; import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.ValidatorImpl; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; +import org.hibernate.validator.internal.metadata.manager.BeanMetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.testutil.ValidationXmlTestHelper; import org.testng.Assert; import org.testng.annotations.Test; @@ -59,9 +60,7 @@ public void run() { ValidatorFactory factory = hibernateConfig.buildValidatorFactory(); Validator validator = factory.getValidator(); - ValidatorImpl hibernateValidatorImpl = (ValidatorImpl) validator; - BeanMetaDataManager bmdm = findPropertyOfType( hibernateValidatorImpl, BeanMetaDataManager.class ); - MethodValidationConfiguration methodConfig = findPropertyOfType( bmdm, MethodValidationConfiguration.class ); + MethodValidationConfiguration methodConfig = getMethodValidationConfiguration( (ValidatorImpl) validator ); Assert.assertTrue( methodConfig.isAllowMultipleCascadedValidationOnReturnValues() ); } @@ -88,9 +87,7 @@ public void run() { ValidatorFactory factory = hibernateConfig.buildValidatorFactory(); Validator validator = factory.getValidator(); - ValidatorImpl hibernateValidatorImpl = (ValidatorImpl) validator; - BeanMetaDataManager bmdm = findPropertyOfType( hibernateValidatorImpl, BeanMetaDataManager.class ); - MethodValidationConfiguration methodConfig = findPropertyOfType( bmdm, MethodValidationConfiguration.class ); + MethodValidationConfiguration methodConfig = getMethodValidationConfiguration( (ValidatorImpl) validator ); Assert.assertTrue( methodConfig.isAllowOverridingMethodAlterParameterConstraint() ); } @@ -117,9 +114,7 @@ public void run() { ValidatorFactory factory = hibernateConfig.buildValidatorFactory(); Validator validator = factory.getValidator(); - ValidatorImpl hibernateValidatorImpl = (ValidatorImpl) validator; - BeanMetaDataManager bmdm = findPropertyOfType( hibernateValidatorImpl, BeanMetaDataManager.class ); - MethodValidationConfiguration methodConfig = findPropertyOfType( bmdm, MethodValidationConfiguration.class ); + MethodValidationConfiguration methodConfig = getMethodValidationConfiguration( (ValidatorImpl) validator ); Assert.assertTrue( methodConfig.isAllowParallelMethodsDefineParameterConstraints() ); } @@ -158,6 +153,12 @@ private T findPropertyOfType(Object subject, Class clazz) return null; } + private MethodValidationConfiguration getMethodValidationConfiguration(ValidatorImpl hibernateValidatorImpl) { + ConstraintMetaDataManager cmdm = findPropertyOfType( hibernateValidatorImpl, ConstraintMetaDataManager.class ); + BeanMetaDataProvider bmdp = findPropertyOfType( cmdm, BeanMetaDataProvider.class ); + return findPropertyOfType( bmdp, MethodValidationConfiguration.class ); + } + private void runWithCustomValidationXml(String validationXmlName, Runnable runnable) { new ValidationXmlTestHelper( ConfigurationFilePropertiesTest.class ). runWithCustomValidationXml( validationXmlName, runnable ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java index 3f982953bc..0daaf25ef4 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java @@ -32,12 +32,12 @@ import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; -import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; @@ -193,19 +193,21 @@ public void testNonStringMapKey() { public void testCreationOfExecutablePath() throws Exception { Method executable = Container.class.getMethod( "addItem", Key.class, Item.class ); - BeanMetaDataManager beanMetaDataManager = new BeanMetaDataManager( + ConstraintMetaDataManager constraintMetaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); - ExecutableMetaData executableMetaData = beanMetaDataManager.getBeanMetaData( Container.class ) + ExecutableMetaData executableMetaData = constraintMetaDataManager.getBeanMetaData( Container.class ) .getMetaDataFor( executable ).get(); PathImpl methodParameterPath = PathImpl.createPathForExecutable( executableMetaData ); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/ConstraintMetaDataManagerTest.java similarity index 93% rename from engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java rename to engine/src/test/java/org/hibernate/validator/test/internal/metadata/ConstraintMetaDataManagerTest.java index e66c711745..d84d62ccdd 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/ConstraintMetaDataManagerTest.java @@ -24,44 +24,47 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; -import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; 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.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** * @author Hardy Ferentschik */ -public class BeanMetaDataManagerTest { +public class ConstraintMetaDataManagerTest { private static final Log log = LoggerFactory.make( MethodHandles.lookup() ); private static final int LOOP_COUNT = 100000; private static final int ARRAY_ALLOCATION_SIZE = 100000; - private BeanMetaDataManager metaDataManager; + private ConstraintMetaDataManager metaDataManager; @BeforeMethod public void setUpBeanMetaDataManager() { - metaDataManager = new BeanMetaDataManager( + metaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java index 67d2e47713..02ff0f859e 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java @@ -26,14 +26,14 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; -import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; @@ -42,6 +42,7 @@ import org.hibernate.validator.test.internal.metadata.CustomerRepository.ValidationGroup; import org.hibernate.validator.test.internal.metadata.CustomerRepositoryExt; import org.hibernate.validator.testutil.TestForIssue; + import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -52,24 +53,26 @@ */ public class ExecutableMetaDataTest { - private BeanMetaDataManager beanMetaDataManager; + private ConstraintMetaDataManager constraintMetaDataManager; private BeanMetaData beanMetaData; @BeforeMethod public void setupBeanMetaData() { - beanMetaDataManager = new BeanMetaDataManager( + constraintMetaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); - beanMetaData = beanMetaDataManager.getBeanMetaData( CustomerRepositoryExt.class ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( CustomerRepositoryExt.class ); } @Test @@ -175,7 +178,7 @@ public void getIdentifierForConstructor() throws Exception { @TestForIssue(jiraKey = "HV-1011") public void getIdentifierForOverridingGenericMethod() throws Exception { Method method = JobRepositoryImpl.class.getMethod( "createJob", UUID.class ); - BeanMetaData beanMetaData = beanMetaDataManager.getBeanMetaData( JobRepositoryImpl.class ); + BeanMetaData beanMetaData = constraintMetaDataManager.getBeanMetaData( JobRepositoryImpl.class ); ExecutableMetaData methodMetaData = beanMetaData.getMetaDataFor( method ).get(); assertThat( methodMetaData.getSignatures() ) @@ -183,7 +186,7 @@ public void getIdentifierForOverridingGenericMethod() throws Exception { .containsOnly( "createJob(java.lang.Object)", "createJob(java.util.UUID)" ); method = JobRepository.class.getMethod( "createJob", Object.class ); - beanMetaData = beanMetaDataManager.getBeanMetaData( JobRepository.class ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( JobRepository.class ); methodMetaData = beanMetaData.getMetaDataFor( method ).get(); assertThat( methodMetaData.getSignatures() ) @@ -191,7 +194,7 @@ public void getIdentifierForOverridingGenericMethod() throws Exception { .containsOnly( "createJob(java.lang.Object)" ); method = SpecialJobRepositoryImpl.class.getMethod( "createJob", Object.class ); - beanMetaData = beanMetaDataManager.getBeanMetaData( SpecialJobRepositoryImpl.class ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( SpecialJobRepositoryImpl.class ); methodMetaData = beanMetaData.getMetaDataFor( method ).get(); assertThat( methodMetaData.getSignatures() ) @@ -203,7 +206,7 @@ public void getIdentifierForOverridingGenericMethod() throws Exception { @TestForIssue(jiraKey = "HV-1011") public void getIdentifierForOverloadedMethod() throws Exception { Method method = SpecialJobRepositoryImpl.class.getMethod( "createJob", String.class ); - BeanMetaData beanMetaData = beanMetaDataManager.getBeanMetaData( SpecialJobRepositoryImpl.class ); + BeanMetaData beanMetaData = constraintMetaDataManager.getBeanMetaData( SpecialJobRepositoryImpl.class ); ExecutableMetaData methodMetaData = beanMetaData.getMetaDataFor( method ).get(); assertThat( methodMetaData.getSignatures() ) diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java index adf433af8c..10e7f4cc6b 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java @@ -27,14 +27,14 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; -import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; @@ -42,6 +42,7 @@ import org.hibernate.validator.test.internal.metadata.CustomerRepository; import org.hibernate.validator.test.internal.metadata.CustomerRepository.ValidationGroup; import org.hibernate.validator.testutil.TestForIssue; + import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -57,19 +58,21 @@ public class ParameterMetaDataTest { @BeforeMethod public void setupBeanMetaData() { - BeanMetaDataManager beanMetaDataManager = new BeanMetaDataManager( + ConstraintMetaDataManager constraintMetaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); - beanMetaData = beanMetaDataManager.getBeanMetaData( CustomerRepository.class ); + beanMetaData = constraintMetaDataManager.getBeanMetaData( CustomerRepository.class ); } @Test @@ -128,18 +131,20 @@ public void parameterNameInInheritanceHierarchy() throws Exception { // // The failure rate on my current VM before fixing the bug is 50%. // Running it in a loop does not improve the odds of failure: all tests will pass or fail for all loop run. - BeanMetaDataManager beanMetaDataManager = new BeanMetaDataManager( + ConstraintMetaDataManager constraintMetaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new SkewedParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); - BeanMetaData localBeanMetaData = beanMetaDataManager.getBeanMetaData( ServiceImpl.class ); + BeanMetaData localBeanMetaData = constraintMetaDataManager.getBeanMetaData( ServiceImpl.class ); Method method = Service.class.getMethod( "sayHello", String.class ); ExecutableMetaData methodMetaData = localBeanMetaData.getMetaDataFor( method ).get(); diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java index 4f3c2616aa..aa26b82cf3 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java @@ -20,15 +20,16 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorManager; -import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData; import org.hibernate.validator.internal.metadata.core.ConstraintHelper; -import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; +import org.hibernate.validator.internal.metadata.manager.ConstraintMetaDataManager; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper; +import org.hibernate.validator.internal.properties.propertyholder.PropertyAccessorCreatorProvider; import org.hibernate.validator.internal.util.ExecutableHelper; import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.TypeResolutionHelper; + import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; @@ -37,40 +38,42 @@ */ public class PropertyMetaDataTest { - private BeanMetaDataManager beanMetaDataManager; + private ConstraintMetaDataManager constraintMetaDataManager; @BeforeMethod public void setupBeanMetaDataManager() { - beanMetaDataManager = new BeanMetaDataManager( + constraintMetaDataManager = new ConstraintMetaDataManager( new ConstraintHelper(), new ExecutableHelper( new TypeResolutionHelper() ), new TypeResolutionHelper(), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new ValueExtractorManager( Collections.emptySet() ), + new PropertyAccessorCreatorProvider(), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy() ), new ValidationOrderGenerator(), - Collections.emptyList(), - new MethodValidationConfiguration.Builder().build() + new MethodValidationConfiguration.Builder().build(), + Collections.emptyList(), + Collections.emptyList() ); } @Test public void locallyDefinedGroupConversion() { - PropertyMetaData property = beanMetaDataManager.getBeanMetaData( User1.class ).getMetaDataFor( "addresses" ); + PropertyMetaData property = constraintMetaDataManager.getBeanMetaData( User1.class ).getMetaDataFor( "addresses" ); assertThat( property.getCascadables().iterator().next().getCascadingMetaData().convertGroup( Default.class ) ).isEqualTo( BasicPostal.class ); } @Test public void groupConversionDefinedInHierarchy() { - PropertyMetaData property = beanMetaDataManager.getBeanMetaData( User2.class ).getMetaDataFor( "addresses" ); + PropertyMetaData property = constraintMetaDataManager.getBeanMetaData( User2.class ).getMetaDataFor( "addresses" ); assertThat( property.getCascadables().iterator().next().getCascadingMetaData().convertGroup( Default.class ) ).isEqualTo( BasicPostal.class ); } @Test(expectedExceptions = ConstraintDeclarationException.class, expectedExceptionsMessageRegExp = "HV000124.*") public void groupConversionInHierarchyWithSameFrom() { - beanMetaDataManager.getBeanMetaData( User3.class ).getMetaDataFor( "addresses" ); + constraintMetaDataManager.getBeanMetaData( User3.class ).getMetaDataFor( "addresses" ); } public interface Complete extends Default { diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/propertyholder/ValidatorTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/propertyholder/ValidatorTest.java new file mode 100644 index 0000000000..88b959a485 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/propertyholder/ValidatorTest.java @@ -0,0 +1,169 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.internal.propertyholder; + +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.pathWith; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf; +import static org.hibernate.validator.testutils.ValidatorUtil.getValidator; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.constraints.Email; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cfg.defs.EmailDef; +import org.hibernate.validator.cfg.defs.MinDef; +import org.hibernate.validator.cfg.defs.NotNullDef; +import org.hibernate.validator.cfg.defs.SizeDef; +import org.hibernate.validator.cfg.propertyholder.PropertyHolderConstraintMapping; +import org.hibernate.validator.internal.engine.ValidatorImpl; + +import org.testng.annotations.Test; + +/** + * @author Marko Bekhta + */ +public class ValidatorTest { + @Test(enabled = false) + public void testSimplePropertyHolder() { + ValidatorImpl validator = (ValidatorImpl) getValidator(); + + Map address = new HashMap<>(); + address.put( "street", "str" ); + address.put( "buildingNumber", -1L ); + + Map user = new HashMap<>(); + user.put( "name", "jhon" ); + user.put( "email", "not a mail" ); + user.put( "address", address ); + + Set> constraintViolations = validator.validatePropertyHolder( user, "user" ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( Size.class ).withProperty( "name" ), + violationOf( Email.class ).withProperty( "email" ), + violationOf( Min.class ) + .withPropertyPath( pathWith() + .property( "address" ) + .property( "buildingNumber" ) + ), + violationOf( Size.class ) + .withPropertyPath( pathWith() + .property( "address" ) + .property( "street" ) + ), + violationOf( NotNull.class ).withProperty( "secondaryAddresses" ) + ); + } + + @Test(enabled = false) + public void testPropertyHolderContainerElements() { + ValidatorImpl validator = (ValidatorImpl) getValidator(); + + Map address = new HashMap<>(); + address.put( "street", "street" ); + address.put( "buildingNumber", 10L ); + + Map invaildAddress = new HashMap<>(); + invaildAddress.put( "street", "str" ); + invaildAddress.put( "buildingNumber", -1L ); + + Map user = new HashMap<>(); + user.put( "name", "jhon doe" ); + user.put( "email", "kinda@valid.mail" ); + user.put( "address", address ); + user.put( "secondaryAddresses", Collections.singletonList( invaildAddress ) ); + + Set> constraintViolations = validator.validatePropertyHolder( user, "user" ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( Min.class ) + .withPropertyPath( pathWith() + .property( "secondaryAddresses" ) + .property( "buildingNumber", true, null, 0, List.class, 0 ) + ), + violationOf( Size.class ) + .withPropertyPath( pathWith() + .property( "secondaryAddresses" ) + .property( "street", true, null, 0, List.class, 0 ) + ) + ); + } + + @Test + public void testProgrammaticMapping() { + HibernateValidatorConfiguration configuration = Validation.byProvider( HibernateValidator.class ) + .configure(); + PropertyHolderConstraintMapping mapping = configuration.createPropertyHolderConstraintMapping(); + + mapping.type( "user" ) + .property( "name", String.class ) + .constraint( new NotNullDef() ) + .constraint( new SizeDef().min( 5 ) ) + + .property( "email", String.class ) + .constraint( new NotNullDef() ) + .constraint( new EmailDef() ) + + .propertyHolder( "address" ) + .valid( "address" ) + + .property( "secondaryAddresses", List.class ) + .constraint( new SizeDef().max( 2 ) ) + .constraint( new NotNullDef() ) +// .containerElementType() +// .valid( "addess" ) + ; + mapping.type( "address" ) + .property( "street", String.class ) + .constraint( new NotNullDef() ) + .constraint( new SizeDef().min( 5 ).max( 10 ) ) + .property( "buildingNumber", Long.class ) + .constraint( new NotNullDef() ) + .constraint( new MinDef().value( 0L ) ); + + configuration.addPropertyHolderMapping( mapping ); + + ValidatorImpl validator = (ValidatorImpl) configuration.buildValidatorFactory().getValidator(); + + Map address = new HashMap<>(); + address.put( "street", "str" ); + address.put( "buildingNumber", -1L ); + + Map user = new HashMap<>(); + user.put( "name", "jhon" ); + user.put( "email", "not a mail" ); + user.put( "address", address ); + + Set> constraintViolations = validator.validatePropertyHolder( user, "user" ); + assertThat( constraintViolations ).containsOnlyViolations( + violationOf( Size.class ).withProperty( "name" ), + violationOf( Email.class ).withProperty( "email" ), + violationOf( Min.class ) + .withPropertyPath( pathWith() + .property( "address" ) + .property( "buildingNumber" ) + ), + violationOf( Size.class ) + .withPropertyPath( pathWith() + .property( "address" ) + .property( "street" ) + ), + violationOf( NotNull.class ).withProperty( "secondaryAddresses" ) + ); + + } +}