Skip to content

Commit e343f17

Browse files
committed
HV-1831 Initialize processed groups lazily
Signed-off-by: marko-bekhta <[email protected]>
1 parent d6ab8cc commit e343f17

File tree

8 files changed

+123
-143
lines changed

8 files changed

+123
-143
lines changed

engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorImpl.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... grou
160160
BaseBeanValidationContext<T> validationContext = getValidationContextBuilder().forValidate( rootBeanClass, rootBeanMetaData, object );
161161

162162
ValidationOrder validationOrder = determineGroupValidationOrder( groups );
163-
BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForBean(
163+
BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForRootBean(
164164
validatorScopedContext.getParameterNameProvider(),
165165
object,
166166
validationContext.getRootBeanMetaData(),
@@ -648,7 +648,7 @@ private void validateCascadedAnnotatedObjectForCurrentGroup(Object value, BaseBe
648648
Class<?> originalGroup = valueContext.getCurrentGroup();
649649
Class<?> currentGroup = cascadingMetaData.convertGroup( originalGroup );
650650

651-
if ( validationContext.isBeanAlreadyValidated( value, currentGroup, valueContext.getPropertyPath() )
651+
if ( validationContext.isBeanAlreadyValidated( value, currentGroup, valueContext )
652652
|| shouldFailFast( validationContext ) ) {
653653
return;
654654
}
@@ -728,7 +728,7 @@ private void doValidate(Object value, String nodeName) {
728728
Class<?> currentGroup = cascadingMetaData.convertGroup( originalGroup );
729729

730730
if ( value == null
731-
|| validationContext.isBeanAlreadyValidated( value, currentGroup, valueContext.getPropertyPath() )
731+
|| validationContext.isBeanAlreadyValidated( value, currentGroup, valueContext )
732732
|| shouldFailFast( validationContext ) ) {
733733
return;
734734
}
@@ -803,6 +803,7 @@ private BeanValueContext<?, Object> buildNewLocalExecutionContext(ValueContext<?
803803
Contracts.assertNotNull( value, "value cannot be null" );
804804
BeanMetaData<?> beanMetaData = beanMetaDataManager.getBeanMetaData( value.getClass() );
805805
newValueContext = ValueContexts.getLocalExecutionContextForBean(
806+
valueContext,
806807
validatorScopedContext.getParameterNameProvider(),
807808
value,
808809
beanMetaData,
@@ -1246,7 +1247,7 @@ else if ( propertyPathNode.getKey() != null ) {
12461247

12471248
propertyPath.removeLeafNode();
12481249

1249-
return ValueContexts.getLocalExecutionContextForBean( validatorScopedContext.getParameterNameProvider(), value, beanMetaData, propertyPath );
1250+
return ValueContexts.getLocalExecutionContextForRootBean( validatorScopedContext.getParameterNameProvider(), value, beanMetaData, propertyPath );
12501251
}
12511252

12521253
/**

engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/AbstractValidationContext.java

Lines changed: 3 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import java.lang.invoke.MethodHandles;
88
import java.util.Collections;
99
import java.util.HashSet;
10-
import java.util.IdentityHashMap;
1110
import java.util.Map;
1211
import java.util.Set;
1312

@@ -103,18 +102,6 @@ abstract class AbstractValidationContext<T> implements BaseBeanValidationContext
103102
@Lazy
104103
private Set<BeanPathMetaConstraintProcessedUnit> processedPathUnits;
105104

106-
/**
107-
* The set of already processed groups per bean ({@link BeanGroupProcessedUnit}).
108-
*/
109-
@Lazy
110-
private Set<BeanGroupProcessedUnit> processedGroupUnits;
111-
112-
/**
113-
* Maps an object to a list of paths (represented by leaf nodes) in which it has been validated. The objects are the bean instances.
114-
*/
115-
@Lazy
116-
private Map<Object, Set<NodeImpl>> processedPathsPerBean;
117-
118105
/**
119106
* Contains all failing constraints so far.
120107
*/
@@ -196,28 +183,19 @@ public ConstraintValidatorFactory getConstraintValidatorFactory() {
196183
}
197184

198185
@Override
199-
public boolean isBeanAlreadyValidated(Object value, Class<?> group, ModifiablePath path) {
186+
public boolean isBeanAlreadyValidated(Object value, Class<?> group, ValueContext<?, ?> valueContext) {
200187
if ( !processedBeanTrackingEnabled ) {
201188
return false;
202189
}
203-
204-
boolean alreadyValidated = isAlreadyValidatedForCurrentGroup( value, group );
205-
206-
if ( alreadyValidated ) {
207-
alreadyValidated = isAlreadyValidatedForPath( value, path.getLeafNode() );
208-
}
209-
210-
return alreadyValidated;
190+
return valueContext.isBeanAlreadyValidated( value, group );
211191
}
212192

213193
@Override
214194
public void markCurrentBeanAsProcessed(ValueContext<?, ?> valueContext) {
215195
if ( !processedBeanTrackingEnabled ) {
216196
return;
217197
}
218-
219-
markCurrentBeanAsProcessedForCurrentGroup( valueContext.getCurrentBean(), valueContext.getCurrentGroup() );
220-
markCurrentBeanAsProcessedForCurrentPath( valueContext.getCurrentBean(), valueContext.getPropertyPath() );
198+
valueContext.markCurrentGroupAsProcessed();
221199
}
222200

223201
@Override
@@ -340,70 +318,13 @@ private String interpolate(
340318
}
341319
}
342320

343-
private boolean isAlreadyValidatedForPath(Object value, NodeImpl path) {
344-
Set<NodeImpl> pathSet = getInitializedProcessedPathsPerBean().get( value );
345-
if ( pathSet == null ) {
346-
return false;
347-
}
348-
349-
if ( path.isRootPath() ) {
350-
return true;
351-
}
352-
353-
// Since this isAlreadyValidatedForPath(..) is only applicable for an object that is about to be cascaded into,
354-
// it means that the new path we are testing cannot be a root path; also since we are cascading into inner
355-
// objects, i.e. going further from the object tree root, it means that the new path cannot be shorter than
356-
// the ones we've already encountered.
357-
for ( NodeImpl p : pathSet ) {
358-
if ( p.isSubPathOrContains( path ) ) {
359-
return true;
360-
}
361-
}
362-
return false;
363-
}
364-
365-
private boolean isAlreadyValidatedForCurrentGroup(Object value, Class<?> group) {
366-
return getInitializedProcessedGroupUnits().contains( new BeanGroupProcessedUnit( value, group ) );
367-
}
368-
369-
private void markCurrentBeanAsProcessedForCurrentPath(Object bean, ModifiablePath path) {
370-
Map<Object, Set<NodeImpl>> processedPathsPerBean = getInitializedProcessedPathsPerBean();
371-
372-
Set<NodeImpl> processedPaths = processedPathsPerBean.get( bean );
373-
if ( processedPaths == null ) {
374-
processedPaths = new HashSet<>();
375-
processedPathsPerBean.put( bean, processedPaths );
376-
}
377-
378-
// HV-1031 The path object is mutated as we traverse the object tree, hence we use the node which is not (for the most part):
379-
processedPaths.add( path.getLeafNode() );
380-
}
381-
382-
private void markCurrentBeanAsProcessedForCurrentGroup(Object bean, Class<?> group) {
383-
getInitializedProcessedGroupUnits().add( new BeanGroupProcessedUnit( bean, group ) );
384-
}
385-
386321
private Set<BeanPathMetaConstraintProcessedUnit> getInitializedProcessedPathUnits() {
387322
if ( processedPathUnits == null ) {
388323
processedPathUnits = new HashSet<>();
389324
}
390325
return processedPathUnits;
391326
}
392327

393-
private Set<BeanGroupProcessedUnit> getInitializedProcessedGroupUnits() {
394-
if ( processedGroupUnits == null ) {
395-
processedGroupUnits = new HashSet<>();
396-
}
397-
return processedGroupUnits;
398-
}
399-
400-
private Map<Object, Set<NodeImpl>> getInitializedProcessedPathsPerBean() {
401-
if ( processedPathsPerBean == null ) {
402-
processedPathsPerBean = new IdentityHashMap<>();
403-
}
404-
return processedPathsPerBean;
405-
}
406-
407328
private Set<ConstraintViolation<T>> getInitializedFailingConstraintViolations() {
408329
if ( failingConstraintViolations == null ) {
409330
failingConstraintViolations = new HashSet<>();
@@ -462,48 +383,4 @@ private int createHashCode() {
462383
}
463384
}
464385

465-
private static final class BeanGroupProcessedUnit {
466-
467-
// these fields are final but we don't mark them as final as an optimization
468-
private Object bean;
469-
private Class<?> group;
470-
private int hashCode;
471-
472-
BeanGroupProcessedUnit(Object bean, Class<?> group) {
473-
this.bean = bean;
474-
this.group = group;
475-
this.hashCode = createHashCode();
476-
}
477-
478-
@Override
479-
public boolean equals(Object o) {
480-
// null check intentionally left out
481-
if ( this == o ) {
482-
return true;
483-
}
484-
485-
// No need to check if the class matches because of how this class is used in the set.
486-
BeanGroupProcessedUnit that = (BeanGroupProcessedUnit) o;
487-
488-
if ( bean != that.bean ) { // instance equality
489-
return false;
490-
}
491-
if ( !group.equals( that.group ) ) {
492-
return false;
493-
}
494-
495-
return true;
496-
}
497-
498-
@Override
499-
public int hashCode() {
500-
return hashCode;
501-
}
502-
503-
private int createHashCode() {
504-
int result = System.identityHashCode( bean );
505-
result = 31 * result + group.hashCode();
506-
return result;
507-
}
508-
}
509386
}

engine/src/main/java/org/hibernate/validator/internal/engine/validationcontext/BaseBeanValidationContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public interface BaseBeanValidationContext<T> extends ValidationContext<T> {
3434

3535
TraversableResolver getTraversableResolver();
3636

37-
boolean isBeanAlreadyValidated(Object value, Class<?> group, ModifiablePath path);
37+
boolean isBeanAlreadyValidated(Object value, Class<?> group, ValueContext<?, ?> valueContext);
3838

3939
void markCurrentBeanAsProcessed(ValueContext<?, ?> valueContext);
4040

engine/src/main/java/org/hibernate/validator/internal/engine/valuecontext/BeanValueContext.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,67 @@
44
*/
55
package org.hibernate.validator.internal.engine.valuecontext;
66

7+
import java.util.HashSet;
8+
import java.util.Set;
9+
710
import org.hibernate.validator.internal.engine.path.ModifiablePath;
811
import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData;
912
import org.hibernate.validator.internal.util.ExecutableParameterNameProvider;
13+
import org.hibernate.validator.internal.util.stereotypes.Lazy;
1014

1115
/**
1216
* @author Marko Bekhta
1317
*/
14-
public class BeanValueContext<T, V> extends ValueContext<T, V> {
18+
public final class BeanValueContext<T, V> extends ValueContext<T, V> {
1519

1620
/**
1721
* The metadata of the current bean.
1822
*/
1923
private final BeanMetaData<T> currentBeanMetaData;
2024

21-
BeanValueContext(ExecutableParameterNameProvider parameterNameProvider, T currentBean, BeanMetaData<T> currentBeanMetaData, ModifiablePath propertyPath) {
22-
super( parameterNameProvider, currentBean, currentBeanMetaData, propertyPath );
25+
/**
26+
* When we check whether the bean was validated we need to check that it was validated for the requested group.
27+
* This set tracks the groups we've already processed this bean for.
28+
*/
29+
@Lazy
30+
private Set<Class<?>> alreadyProcessedGroups;
31+
32+
BeanValueContext(ValueContext<?, ?> parentContext, ExecutableParameterNameProvider parameterNameProvider, T currentBean, BeanMetaData<T> currentBeanMetaData, ModifiablePath propertyPath) {
33+
super( parentContext, parameterNameProvider, currentBean, currentBeanMetaData, propertyPath );
2334
this.currentBeanMetaData = currentBeanMetaData;
35+
this.alreadyProcessedGroups = new HashSet<>();
2436
}
2537

26-
public final BeanMetaData<T> getCurrentBeanMetaData() {
38+
public BeanMetaData<T> getCurrentBeanMetaData() {
2739
return currentBeanMetaData;
2840
}
41+
42+
@Override
43+
public boolean isBeanAlreadyValidated(Object value, Class<?> group) {
44+
ValueContext<?, ?> curr = this;
45+
while ( curr != null ) {
46+
if ( curr.currentBean == value ) {
47+
return curr.isProcessedForGroup( group );
48+
}
49+
curr = curr.parentContext;
50+
}
51+
return false;
52+
}
53+
54+
@Override
55+
public void markCurrentGroupAsProcessed() {
56+
// if we just validate the default/single group it doesn't make sense to track it beyond the "current group" value
57+
if ( this.previousGroup != null && this.previousGroup != this.currentGroup ) {
58+
if ( this.alreadyProcessedGroups == null ) {
59+
this.alreadyProcessedGroups = new HashSet<>();
60+
this.alreadyProcessedGroups.add( this.previousGroup );
61+
}
62+
this.alreadyProcessedGroups.add( this.currentGroup );
63+
}
64+
}
65+
66+
@Override
67+
protected boolean isProcessedForGroup(Class<?> group) {
68+
return group == this.currentGroup || ( this.alreadyProcessedGroups != null && alreadyProcessedGroups.contains( group ) );
69+
}
2970
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.validator.internal.engine.valuecontext;
6+
7+
import org.hibernate.validator.internal.engine.path.ModifiablePath;
8+
import org.hibernate.validator.internal.metadata.facets.Validatable;
9+
import org.hibernate.validator.internal.util.ExecutableParameterNameProvider;
10+
11+
/**
12+
* @author Marko Bekhta
13+
*/
14+
public final class ExecutableValueContext<T, V> extends ValueContext<T, V> {
15+
16+
ExecutableValueContext(ValueContext<?, ?> parentContext, ExecutableParameterNameProvider parameterNameProvider, T currentBean, Validatable validatable, ModifiablePath propertyPath) {
17+
super( parentContext, parameterNameProvider, currentBean, validatable, propertyPath );
18+
}
19+
20+
@Override
21+
public boolean isBeanAlreadyValidated(Object value, Class<?> group) {
22+
// executables start with the root bean as a value of an object that that executable is "called" on,
23+
// but we haven't really validated that root, so we need to ignore it:
24+
return false;
25+
}
26+
27+
@Override
28+
public void markCurrentGroupAsProcessed() {
29+
// do nothing
30+
}
31+
32+
@Override
33+
protected boolean isProcessedForGroup(Class<?> group) {
34+
return false;
35+
}
36+
}

0 commit comments

Comments
 (0)