Skip to content

Commit

Permalink
HV-1055 Expanding re-defined default group sequences for validate(), …
Browse files Browse the repository at this point in the history
…validateProperty() and validateValue()
  • Loading branch information
gunnarmorling committed Feb 17, 2016
1 parent 689b0df commit cf0e695
Show file tree
Hide file tree
Showing 14 changed files with 469 additions and 84 deletions.
Expand Up @@ -37,8 +37,8 @@

import org.hibernate.validator.internal.engine.ValidationContext.ValidationContextBuilder;
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
import org.hibernate.validator.internal.engine.groups.GroupWithInheritance;
import org.hibernate.validator.internal.engine.groups.Group;
import org.hibernate.validator.internal.engine.groups.GroupWithInheritance;
import org.hibernate.validator.internal.engine.groups.Sequence;
import org.hibernate.validator.internal.engine.groups.ValidationOrder;
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
Expand Down Expand Up @@ -466,7 +466,8 @@ private <U> void validateConstraintsForDefaultGroup(ValidationContext<?> validat
for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.defaultGroupSequenceIsRedefined();
List<Class<?>> defaultGroupSequence = hostingBeanMetaData.getDefaultGroupSequence( valueContext.getCurrentBean() );
Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );

Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();

// if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
Expand All @@ -475,31 +476,37 @@ private <U> void validateConstraintsForDefaultGroup(ValidationContext<?> validat
}

PathImpl currentPath = valueContext.getPropertyPath();
for ( Class<?> defaultSequenceMember : defaultGroupSequence ) {
valueContext.setCurrentGroup( defaultSequenceMember );
boolean validationSuccessful = true;
for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
// HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
// time. An interface can define more than one constraint, we have to check the class we are validating.
final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
if ( declaringClass.isInterface() ) {
Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
continue;

while ( defaultGroupSequence.hasNext() ) {
for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
boolean validationSuccessful = true;

for ( Group defaultSequenceMember : groupOfGroups ) {
valueContext.setCurrentGroup( defaultSequenceMember.getDefiningClass() );
for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
// HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
// time. An interface can define more than one constraint, we have to check the class we are validating.
final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
if ( declaringClass.isInterface() ) {
Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
continue;
}
validatedInterfaces.put( declaringClass, clazz );
}

boolean tmp = validateConstraint( validationContext, valueContext, false, metaConstraint );
if ( shouldFailFast( validationContext ) ) {
return;
}
validationSuccessful = validationSuccessful && tmp;
// reset property path
valueContext.setPropertyPath( currentPath );
}
validatedInterfaces.put( declaringClass, clazz );
}

boolean tmp = validateConstraint( validationContext, valueContext, false, metaConstraint );
if ( shouldFailFast( validationContext ) ) {
return;
if ( !validationSuccessful ) {
break;
}
validationSuccessful = validationSuccessful && tmp;
// reset property path
valueContext.setPropertyPath( currentPath );
}
if ( !validationSuccessful ) {
break;
}
}
validationContext.markCurrentBeanAsProcessed( valueContext );
Expand Down Expand Up @@ -994,55 +1001,61 @@ private <U> int validatePropertyForDefaultGroup(ValueContext<U, Object> valueCon
BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.defaultGroupSequenceIsRedefined();
Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
List<Class<?>> defaultGroupSequence = hostingBeanMetaData.getDefaultGroupSequence( valueContext.getCurrentBean() );
Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );

if ( defaultGroupSequenceIsRedefined ) {
metaConstraints = hostingBeanMetaData.getMetaConstraints();
}

for ( Class<?> groupClass : defaultGroupSequence ) {
boolean validationSuccessful = true;
valueContext.setCurrentGroup( groupClass );
for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
// HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
// time. An interface can define more than one constraint, we have to check the class we are validating.
final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
if ( declaringClass.isInterface() ) {
Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
continue;
while ( defaultGroupSequence.hasNext() ) {
for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
boolean validationSuccessful = true;

for ( Group groupClass : groupOfGroups ) {
valueContext.setCurrentGroup( groupClass.getDefiningClass() );
for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
// HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
// time. An interface can define more than one constraint, we have to check the class we are validating.
final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
if ( declaringClass.isInterface() ) {
Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
continue;
}
validatedInterfaces.put( declaringClass, clazz );
}

if ( constraintList.contains( metaConstraint ) ) {
boolean tmp = validateConstraint( validationContext, valueContext, true, metaConstraint );

validationSuccessful = validationSuccessful && tmp;
if ( shouldFailFast( validationContext ) ) {
return validationContext.getFailingConstraints()
.size() - numberOfConstraintViolationsBefore;
}
}

if ( typeUseConstraints.contains( metaConstraint ) ) {
boolean tmp = validatePropertyTypeConstraint(
validationContext,
valueContext,
true,
metaConstraint
);
validationSuccessful = validationSuccessful && tmp;
if ( shouldFailFast( validationContext ) ) {
return validationContext.getFailingConstraints()
.size() - numberOfConstraintViolationsBefore;
}
}
}
validatedInterfaces.put( declaringClass, clazz );
}

if ( constraintList.contains( metaConstraint ) ) {
boolean tmp = validateConstraint( validationContext, valueContext, true, metaConstraint );

validationSuccessful = validationSuccessful && tmp;
if ( shouldFailFast( validationContext ) ) {
return validationContext.getFailingConstraints()
.size() - numberOfConstraintViolationsBefore;
}
}

if ( typeUseConstraints.contains( metaConstraint ) ) {
boolean tmp = validatePropertyTypeConstraint(
validationContext,
valueContext,
true,
metaConstraint
);
validationSuccessful = validationSuccessful && tmp;
if ( shouldFailFast( validationContext ) ) {
return validationContext.getFailingConstraints()
.size() - numberOfConstraintViolationsBefore;
}
if ( !validationSuccessful ) {
break;
}
}

if ( !validationSuccessful ) {
break;
}
}
// all the hierarchy has been validated, stop validation.
if ( defaultGroupSequenceIsRedefined ) {
Expand Down
Expand Up @@ -7,12 +7,14 @@
package org.hibernate.validator.internal.engine.groups;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.validation.GroupSequence;
import javax.validation.groups.Default;

import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
Expand All @@ -23,12 +25,26 @@
* @author Hardy Ferentschik
*/
public class Sequence implements Iterable<GroupWithInheritance> {

/**
* An "anonymous" sequence with just a single contained element, {@code Default.class}.
*/
public static Sequence DEFAULT = new Sequence();

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

private final Class<?> sequence;
private List<Group> groups;
private List<GroupWithInheritance> expandedGroups;

private Sequence() {
this.sequence = Default.class;
this.groups = Collections.singletonList( Group.DEFAULT_GROUP );
this.expandedGroups = Collections.singletonList(
new GroupWithInheritance( Collections.singleton( Group.DEFAULT_GROUP ) )
);
}

public Sequence(Class<?> sequence, List<Group> groups) {
this.groups = groups;
this.sequence = sequence;
Expand Down
Expand Up @@ -6,8 +6,10 @@
*/
package org.hibernate.validator.internal.engine.groups;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.validation.GroupDefinitionException;

/**
Expand All @@ -32,4 +34,34 @@ public interface ValidationOrder {
*/
void assertDefaultGroupSequenceIsExpandable(List<Class<?>> defaultGroupSequence)
throws GroupDefinitionException;

/**
* A {@link org.hibernate.validator.internal.engine.groups.ValidationOrder} which contains a single sequence which
* in turn contains a single group, {@code Default}.
*/
ValidationOrder DEFAULT_SEQUENCE = new DefaultValidationOrder();

static class DefaultValidationOrder implements ValidationOrder {

private final List<Sequence> defaultSequences;

private DefaultValidationOrder() {
defaultSequences = Collections.singletonList( Sequence.DEFAULT );
}

@Override
public Iterator<Group> getGroupIterator() {
// Not using emptyIterator() to stay on 1.6 language level
return Collections.<Group>emptyList().iterator();
}

@Override
public Iterator<Sequence> getSequenceIterator() {
return defaultSequences.iterator();
}

@Override
public void assertDefaultGroupSequenceIsExpandable(List<Class<?>> defaultGroupSequence) throws GroupDefinitionException {
}
}
}
Expand Up @@ -89,7 +89,7 @@ public ValidationOrder getValidationOrder(Collection<Class<?>> groups) {
validationOrder.insertGroup( Group.DEFAULT_GROUP );
}
else if ( isGroupSequence( clazz ) ) {
insertSequence( clazz, validationOrder );
insertSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), true, validationOrder );
}
else {
Group group = new Group( clazz );
Expand All @@ -101,6 +101,12 @@ else if ( isGroupSequence( clazz ) ) {
return validationOrder;
}

public ValidationOrder getDefaultValidationOrder(Class<?> clazz, List<Class<?>> defaultGroupSequence) {
DefaultValidationOrder validationOrder = new DefaultValidationOrder();
insertSequence( clazz, defaultGroupSequence.toArray( new Class<?>[0] ), false, validationOrder );
return validationOrder;
}

private boolean isGroupSequence(Class<?> clazz) {
return clazz.getAnnotation( GroupSequence.class ) != null;
}
Expand All @@ -119,35 +125,35 @@ private void insertInheritedGroups(Class<?> clazz, DefaultValidationOrder chain)
}
}

private void insertSequence(Class<?> sequenceClass, DefaultValidationOrder validationOrder) {
Sequence sequence = resolvedSequences.get( sequenceClass );
private void insertSequence(Class<?> sequenceClass, Class<?>[] sequenceElements, boolean cache, DefaultValidationOrder validationOrder) {
Sequence sequence = cache ? resolvedSequences.get( sequenceClass ) : null;
if ( sequence == null ) {
sequence = resolveSequence( sequenceClass, new ArrayList<Class<?>>() );
sequence = resolveSequence( sequenceClass, sequenceElements, new ArrayList<Class<?>>() );
// we expand the inherited groups only after we determined whether the sequence is expandable
sequence.expandInheritedGroups();

// cache already resolved sequences
final Sequence cachedResolvedSequence = resolvedSequences.putIfAbsent( sequenceClass, sequence );
if ( cachedResolvedSequence != null ) {
sequence = cachedResolvedSequence;
if ( cache ) {
final Sequence cachedResolvedSequence = resolvedSequences.putIfAbsent( sequenceClass, sequence );
if ( cachedResolvedSequence != null ) {
sequence = cachedResolvedSequence;
}
}
}
validationOrder.insertSequence( sequence );
}

private Sequence resolveSequence(Class<?> sequenceClass, List<Class<?>> processedSequences) {
private Sequence resolveSequence(Class<?> sequenceClass, Class<?>[] sequenceElements, List<Class<?>> processedSequences) {
if ( processedSequences.contains( sequenceClass ) ) {
throw log.getCyclicDependencyInGroupsDefinitionException();
}
else {
processedSequences.add( sequenceClass );
}
List<Group> resolvedSequenceGroups = new ArrayList<Group>();
GroupSequence sequenceAnnotation = sequenceClass.getAnnotation( GroupSequence.class );
Class<?>[] sequenceArray = sequenceAnnotation.value();
for ( Class<?> clazz : sequenceArray ) {
for ( Class<?> clazz : sequenceElements ) {
if ( isGroupSequence( clazz ) ) {
Sequence tmpSequence = resolveSequence( clazz, processedSequences );
Sequence tmpSequence = resolveSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), processedSequences );
addGroups( resolvedSequenceGroups, tmpSequence.getComposingGroups() );
}
else {
Expand Down
Expand Up @@ -8,8 +8,10 @@

import java.util.EnumSet;
import java.util.List;

import javax.validation.ParameterNameProvider;

import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
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;
Expand All @@ -18,8 +20,8 @@
import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl;
import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
import org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider;
import org.hibernate.validator.internal.metadata.provider.TypeAnnotationAwareMetaDataProvider;
import org.hibernate.validator.internal.metadata.provider.MetaDataProvider;
import org.hibernate.validator.internal.metadata.provider.TypeAnnotationAwareMetaDataProvider;
import org.hibernate.validator.internal.metadata.raw.BeanConfiguration;
import org.hibernate.validator.internal.util.ConcurrentReferenceHashMap;
import org.hibernate.validator.internal.util.Contracts;
Expand Down Expand Up @@ -85,6 +87,8 @@ public class BeanMetaDataManager {
*/
private final ExecutableHelper executableHelper;

private final ValidationOrderGenerator validationOrderGenerator = new ValidationOrderGenerator();

/**
* Creates a new {@code BeanMetaDataManager}.
*
Expand All @@ -111,7 +115,6 @@ public BeanMetaDataManager(ConstraintHelper constraintHelper,
EnumSet.of( IDENTITY_COMPARISONS )
);


AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders();
AnnotationMetaDataProvider defaultProvider = null;
if ( Version.getJavaRelease() >= 8 ) {
Expand Down Expand Up @@ -157,7 +160,7 @@ public int numberOfCachedBeanMetaDataInstances() {
* @return A bean meta data object for the given type.
*/
private <T> BeanMetaDataImpl<T> createBeanMetaData(Class<T> clazz) {
BeanMetaDataBuilder<T> builder = BeanMetaDataBuilder.getInstance( constraintHelper, executableHelper, clazz );
BeanMetaDataBuilder<T> builder = BeanMetaDataBuilder.getInstance( constraintHelper, executableHelper, validationOrderGenerator, clazz );

for ( MetaDataProvider provider : metaDataProviders ) {
for ( BeanConfiguration<? super T> beanConfiguration : provider.getBeanConfigurationForHierarchy( clazz ) ) {
Expand Down

0 comments on commit cf0e695

Please sign in to comment.