1
7 package org.hibernate.validator.internal.engine.constraintvalidation;
8
9 import static org.hibernate.validator.constraints.CompositionType.ALL_FALSE;
10 import static org.hibernate.validator.constraints.CompositionType.AND;
11 import static org.hibernate.validator.constraints.CompositionType.OR;
12
13 import java.lang.annotation.Annotation;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.reflect.Type;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.List;
19 import java.util.Optional;
20 import java.util.stream.Collectors;
21
22 import javax.validation.ConstraintValidator;
23
24 import org.hibernate.validator.constraints.CompositionType;
25 import org.hibernate.validator.internal.engine.validationcontext.ValidationContext;
26 import org.hibernate.validator.internal.engine.valuecontext.ValueContext;
27 import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl;
28 import org.hibernate.validator.internal.util.CollectionHelper;
29 import org.hibernate.validator.internal.util.logging.Log;
30 import org.hibernate.validator.internal.util.logging.LoggerFactory;
31 import org.hibernate.validator.internal.util.stereotypes.Immutable;
32
33
43 class ComposingConstraintTree<B extends Annotation> extends ConstraintTree<B> {
44
45 private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
46
47 @Immutable
48 private final List<ConstraintTree<?>> children;
49
50 public ComposingConstraintTree(ConstraintValidatorManager constraintValidatorManager, ConstraintDescriptorImpl<B> descriptor, Type validatedValueType) {
51 super( constraintValidatorManager, descriptor, validatedValueType );
52 this.children = descriptor.getComposingConstraintImpls().stream()
53 .map( desc -> createConstraintTree( constraintValidatorManager, desc ) )
54 .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
55 }
56
57 private <U extends Annotation> ConstraintTree<U> createConstraintTree(ConstraintValidatorManager constraintValidatorManager, ConstraintDescriptorImpl<U> composingDescriptor) {
58 if ( composingDescriptor.getComposingConstraintImpls().isEmpty() ) {
59 return new SimpleConstraintTree<>( constraintValidatorManager, composingDescriptor, getValidatedValueType() );
60 }
61 else {
62 return new ComposingConstraintTree<>( constraintValidatorManager, composingDescriptor, getValidatedValueType() );
63 }
64 }
65
66 @Override
67 protected void validateConstraints(ValidationContext<?> validationContext,
68 ValueContext<?, ?> valueContext,
69 Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
70 CompositionResult compositionResult = validateComposingConstraints(
71 validationContext, valueContext, violatedConstraintValidatorContexts
72 );
73
74 Optional<ConstraintValidatorContextImpl> violatedLocalConstraintValidatorContext;
75
76
77 if ( mainConstraintNeedsEvaluation( validationContext, violatedConstraintValidatorContexts ) ) {
78
79 if ( LOG.isTraceEnabled() ) {
80 LOG.tracef(
81 "Validating value %s against constraint defined by %s.",
82 valueContext.getCurrentValidatedValue(),
83 descriptor
84 );
85 }
86
87
88 ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
89
90
91 ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(
92 descriptor, valueContext.getPropertyPath()
93 );
94
95
96 violatedLocalConstraintValidatorContext = validateSingleConstraint(
97 valueContext,
98 constraintValidatorContext,
99 validator
100 );
101
102
103
104 if ( !violatedLocalConstraintValidatorContext.isPresent() ) {
105 compositionResult.setAtLeastOneTrue( true );
106 }
107 else {
108 compositionResult.setAllTrue( false );
109 }
110 }
111 else {
112 violatedLocalConstraintValidatorContext = Optional.empty();
113 }
114
115 if ( !passesCompositionTypeRequirement( violatedConstraintValidatorContexts, compositionResult ) ) {
116 prepareFinalConstraintViolations(
117 validationContext, valueContext, violatedConstraintValidatorContexts, violatedLocalConstraintValidatorContext
118 );
119 }
120 }
121
122 private boolean mainConstraintNeedsEvaluation(ValidationContext<?> validationContext,
123 Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
124
125 if ( !descriptor.getComposingConstraints().isEmpty() && descriptor.getMatchingConstraintValidatorDescriptors().isEmpty() ) {
126 return false;
127 }
128
129 if ( violatedConstraintValidatorContexts.isEmpty() ) {
130 return true;
131 }
132
133
134 if ( descriptor.isReportAsSingleViolation() && descriptor.getCompositionType() == AND ) {
135 return false;
136 }
137
138
139 if ( validationContext.isFailFastModeEnabled() ) {
140 return false;
141 }
142
143 return true;
144 }
145
146
155 private void prepareFinalConstraintViolations(ValidationContext<?> validationContext,
156 ValueContext<?, ?> valueContext,
157 Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts,
158 Optional<ConstraintValidatorContextImpl> localConstraintValidatorContext) {
159 if ( reportAsSingleViolation() ) {
160
161 violatedConstraintValidatorContexts.clear();
162
163
164
165
166
167 if ( !localConstraintValidatorContext.isPresent() ) {
168 violatedConstraintValidatorContexts.add(
169 validationContext.createConstraintValidatorContextFor(
170 descriptor, valueContext.getPropertyPath()
171 )
172 );
173 }
174 }
175
176
177
178
179
180
181
182
183
184 if ( localConstraintValidatorContext.isPresent() ) {
185 violatedConstraintValidatorContexts.add( localConstraintValidatorContext.get() );
186 }
187 }
188
189
198 private CompositionResult validateComposingConstraints(ValidationContext<?> validationContext,
199 ValueContext<?, ?> valueContext,
200 Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {
201 CompositionResult compositionResult = new CompositionResult( true, false );
202 for ( ConstraintTree<?> tree : children ) {
203 List<ConstraintValidatorContextImpl> tmpConstraintValidatorContexts = new ArrayList<>( 5 );
204 tree.validateConstraints( validationContext, valueContext, tmpConstraintValidatorContexts );
205 violatedConstraintValidatorContexts.addAll( tmpConstraintValidatorContexts );
206
207 if ( tmpConstraintValidatorContexts.isEmpty() ) {
208 compositionResult.setAtLeastOneTrue( true );
209
210 if ( descriptor.getCompositionType() == OR ) {
211 break;
212 }
213 }
214 else {
215 compositionResult.setAllTrue( false );
216 if ( descriptor.getCompositionType() == AND
217 && ( validationContext.isFailFastModeEnabled() || descriptor.isReportAsSingleViolation() ) ) {
218 break;
219 }
220 }
221 }
222 return compositionResult;
223 }
224
225 private boolean passesCompositionTypeRequirement(Collection<?> constraintViolations, CompositionResult compositionResult) {
226 CompositionType compositionType = getDescriptor().getCompositionType();
227 boolean passedValidation = false;
228 switch ( compositionType ) {
229 case OR:
230 passedValidation = compositionResult.isAtLeastOneTrue();
231 break;
232 case AND:
233 passedValidation = compositionResult.isAllTrue();
234 break;
235 case ALL_FALSE:
236 passedValidation = !compositionResult.isAtLeastOneTrue();
237 break;
238 }
239 assert ( !passedValidation || !( compositionType == AND ) || constraintViolations.isEmpty() );
240 if ( passedValidation ) {
241 constraintViolations.clear();
242 }
243 return passedValidation;
244 }
245
246
251 private boolean reportAsSingleViolation() {
252 return getDescriptor().isReportAsSingleViolation()
253 || getDescriptor().getCompositionType() == ALL_FALSE;
254 }
255
256 private static final class CompositionResult {
257
258 private boolean allTrue;
259 private boolean atLeastOneTrue;
260
261 CompositionResult(boolean allTrue, boolean atLeastOneTrue) {
262 this.allTrue = allTrue;
263 this.atLeastOneTrue = atLeastOneTrue;
264 }
265
266 public boolean isAllTrue() {
267 return allTrue;
268 }
269
270 public boolean isAtLeastOneTrue() {
271 return atLeastOneTrue;
272 }
273
274 public void setAllTrue(boolean allTrue) {
275 this.allTrue = allTrue;
276 }
277
278 public void setAtLeastOneTrue(boolean atLeastOneTrue) {
279 this.atLeastOneTrue = atLeastOneTrue;
280 }
281 }
282 }
283