Skip to content

Commit b7d48c3

Browse files
RADickinsongsmet
authored andcommitted
HV-1534 Allow getter constraints to be specified for subclasses in XML configuration
It was possible in Hibernate Validator 4.3 and got somehow broken during the 5 development cycle. It's not exactly something we would support nowadays but as some users were relying on it and it's not complicating the code, for now, we can try to support it, hoping it won't be in the way later.
1 parent ebcd2cd commit b7d48c3

File tree

8 files changed

+189
-6
lines changed

8 files changed

+189
-6
lines changed

engine/src/main/java/org/hibernate/validator/internal/metadata/aggregated/PropertyMetaData.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.hibernate.validator.internal.metadata.descriptor.PropertyDescriptorImpl;
3434
import org.hibernate.validator.internal.metadata.facets.Cascadable;
3535
import org.hibernate.validator.internal.metadata.location.ConstraintLocation;
36+
import org.hibernate.validator.internal.metadata.location.GetterConstraintLocation;
3637
import org.hibernate.validator.internal.metadata.location.TypeArgumentConstraintLocation;
3738
import org.hibernate.validator.internal.metadata.raw.ConstrainedElement;
3839
import org.hibernate.validator.internal.metadata.raw.ConstrainedElement.ConstrainedElementKind;
@@ -262,7 +263,13 @@ private MetaConstraint<?> withGetterLocation(ConstraintLocation getterConstraint
262263

263264
// fast track if it's a regular constraint
264265
if ( !(constraint.getLocation() instanceof TypeArgumentConstraintLocation) ) {
265-
converted = getterConstraintLocation;
266+
// Change the constraint location to a GetterConstraintLocation if it is not already one
267+
if ( constraint.getLocation() instanceof GetterConstraintLocation ) {
268+
converted = constraint.getLocation();
269+
}
270+
else {
271+
converted = getterConstraintLocation;
272+
}
266273
}
267274
else {
268275
Deque<ConstraintLocation> locationStack = new ArrayDeque<>();
@@ -283,7 +290,13 @@ private MetaConstraint<?> withGetterLocation(ConstraintLocation getterConstraint
283290
// 2. beginning at the root, transform each location so it references the transformed delegate
284291
for ( ConstraintLocation location : locationStack ) {
285292
if ( !(location instanceof TypeArgumentConstraintLocation) ) {
286-
converted = getterConstraintLocation;
293+
// Change the constraint location to a GetterConstraintLocation if it is not already one
294+
if ( location instanceof GetterConstraintLocation ) {
295+
converted = location;
296+
}
297+
else {
298+
converted = getterConstraintLocation;
299+
}
287300
}
288301
else {
289302
converted = ConstraintLocation.forTypeArgument(

engine/src/main/java/org/hibernate/validator/internal/metadata/location/ConstraintLocation.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,29 @@ static ConstraintLocation forField(Field field) {
4343
return new FieldConstraintLocation( field );
4444
}
4545

46+
/**
47+
* Create a new {@link GetterConstraintLocation} for the given getter method.
48+
*
49+
* @param getter The getter method being constrained
50+
* @return A new GetterConstraintLocation
51+
*/
4652
static ConstraintLocation forGetter(Method getter) {
47-
return new GetterConstraintLocation( getter );
53+
return new GetterConstraintLocation( getter.getDeclaringClass(), getter );
54+
}
55+
56+
/**
57+
* Create a new {@link GetterConstraintLocation} for the given declaring class and getter method.
58+
* <p>
59+
* This provides an alternative to {@link ConstraintLocation#forGetter(Method)} where the given declaring class is usually a sub-class of the
60+
* actual class on which the getter method is declared. This is provided to support XML mapping configurations used to specify constraints on
61+
* subclasses for inherited getter methods.
62+
*
63+
* @param declaringClass The class on which the constraint is defined.
64+
* @param getter The getter method being constrained.
65+
* @return A new GetterConstraintLocation
66+
*/
67+
static ConstraintLocation forGetter(Class<?> declaringClass, Method getter ) {
68+
return new GetterConstraintLocation( declaringClass, getter );
4869
}
4970

5071
static ConstraintLocation forTypeArgument(ConstraintLocation delegate, TypeVariable<?> typeParameter, Type typeOfAnnotatedElement) {

engine/src/main/java/org/hibernate/validator/internal/metadata/location/GetterConstraintLocation.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,26 @@ public class GetterConstraintLocation implements ConstraintLocation {
4444
*/
4545
private final Type typeForValidatorResolution;
4646

47+
/**
48+
* The class of the method for which the constraint was defined.
49+
* <p>
50+
* It is usually the same as the declaring class of the method itself, except in the XML case when a user could
51+
* declare a constraint for a specific subclass.
52+
*/
53+
private final Class<?> declaringClass;
54+
4755

48-
GetterConstraintLocation(Method method) {
56+
GetterConstraintLocation( Class<?> declaringClass, Method method ) {
4957
this.method = method;
5058
this.accessibleMethod = getAccessible( method );
5159
this.propertyName = ReflectionHelper.getPropertyName( method );
5260
this.typeForValidatorResolution = ReflectionHelper.boxedType( ReflectionHelper.typeOf( method ) );
61+
this.declaringClass = declaringClass;
5362
}
5463

5564
@Override
5665
public Class<?> getDeclaringClass() {
57-
return method.getDeclaringClass();
66+
return declaringClass;
5867
}
5968

6069
@Override

engine/src/main/java/org/hibernate/validator/internal/xml/mapping/ConstrainedGetterStaxBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ ConstrainedExecutable build(Class<?> beanClass, List<String> alreadyProcessedGet
7171
alreadyProcessedGetterNames.add( mainAttributeValue );
7272
}
7373
Method getter = findGetter( beanClass, mainAttributeValue );
74-
ConstraintLocation constraintLocation = ConstraintLocation.forGetter( getter );
74+
ConstraintLocation constraintLocation = ConstraintLocation.forGetter( beanClass, getter );
7575

7676
Set<MetaConstraint<?>> metaConstraints = constraintTypeStaxBuilders.stream()
7777
.map( builder -> builder.build( constraintLocation, java.lang.annotation.ElementType.METHOD, null ) )
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Hibernate Validator, declare and validate application constraints
3+
*
4+
* License: Apache License, Version 2.0
5+
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6+
*/
7+
package org.hibernate.validator.test.internal.xml;
8+
9+
/**
10+
* Test class for HV-1534.
11+
*
12+
* @author Rob Dickinson
13+
*/
14+
public class Child extends Parent {
15+
16+
public Child( String parentAttribute ) {
17+
super( parentAttribute );
18+
}
19+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Hibernate Validator, declare and validate application constraints
3+
*
4+
* License: Apache License, Version 2.0
5+
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6+
*/
7+
package org.hibernate.validator.test.internal.xml;
8+
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
12+
/**
13+
* Test class for HV-1534.
14+
*
15+
* @author Rob Dickinson
16+
*/
17+
public class Parent {
18+
19+
private String parentAttribute = null;
20+
private List<String> parentListAttribute = new ArrayList<>();
21+
22+
public Parent( String parentAttribute ) {
23+
this.parentAttribute = parentAttribute;
24+
}
25+
26+
public final String getParentAttribute() {
27+
return parentAttribute;
28+
}
29+
30+
public void addToListAttribute(String element) {
31+
parentListAttribute.add( element );
32+
}
33+
34+
public final List<String> getParentListAttribute() {
35+
return parentListAttribute;
36+
}
37+
}

engine/src/test/java/org/hibernate/validator/test/internal/xml/XmlMappingTest.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.hibernate.validator.internal.util.CollectionHelper.asSet;
1010
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNoViolations;
1111
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat;
12+
import static org.hibernate.validator.testutil.ConstraintViolationAssert.pathWith;
1213
import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf;
1314
import static org.testng.Assert.assertEquals;
1415

@@ -376,4 +377,59 @@ public void testCascadedValidation() {
376377
violationOf( NotNull.class )
377378
);
378379
}
380+
381+
@Test
382+
@TestForIssue(jiraKey = "HV-1534")
383+
public void test_constraint_is_applied_to_inherited_getter() {
384+
final Configuration<?> configuration = ValidatorUtil.getConfiguration();
385+
configuration.addMapping( XmlMappingTest.class.getResourceAsStream( "hv-1534-mapping.xml" ) );
386+
387+
final ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
388+
final Validator validator = validatorFactory.getValidator();
389+
390+
Parent parent = new Parent( null );
391+
392+
Set<ConstraintViolation<Parent>> parentViolations = validator.validate( parent );
393+
394+
assertNoViolations( parentViolations );
395+
396+
Child child = new Child( null );
397+
398+
Set<ConstraintViolation<Child>> childViolations = validator.validate( child );
399+
400+
assertThat( childViolations ).containsOnlyViolations(
401+
violationOf( NotNull.class ).withProperty( "parentAttribute" )
402+
);
403+
}
404+
405+
@Test
406+
@TestForIssue(jiraKey = "HV-1534")
407+
public void test_constraint_is_applied_to_type_argument_of_inherited_getter() {
408+
final Configuration<?> configuration = ValidatorUtil.getConfiguration();
409+
configuration.addMapping( XmlMappingTest.class.getResourceAsStream( "hv-1534-mapping.xml" ) );
410+
411+
final ValidatorFactory validatorFactory = configuration.buildValidatorFactory();
412+
final Validator validator = validatorFactory.getValidator();
413+
414+
Parent parent = new Parent( "someValue" );
415+
parent.addToListAttribute( null );
416+
417+
Set<ConstraintViolation<Parent>> parentViolations = validator.validate( parent );
418+
419+
assertNoViolations( parentViolations );
420+
421+
Child child = new Child( "someValue" );
422+
child.addToListAttribute( null );
423+
424+
Set<ConstraintViolation<Child>> childViolations = validator.validate( child );
425+
426+
assertThat( childViolations ).containsOnlyViolations(
427+
violationOf( NotNull.class )
428+
.withPropertyPath(
429+
pathWith()
430+
.property( "parentListAttribute" )
431+
.containerElement( "<list element>", true, null, 0, List.class, 0 ) )
432+
);
433+
}
434+
379435
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Hibernate Validator, declare and validate application constraints
4+
~
5+
~ License: Apache License, Version 2.0
6+
~ See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
7+
-->
8+
<constraint-mappings
9+
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
10+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
12+
http://xmlns.jcp.org/xml/ns/validation/mapping/validation-mapping-2.0.xsd"
13+
version="2.0">
14+
15+
<default-package>org.hibernate.validator.internal.xml</default-package>
16+
17+
<bean class="org.hibernate.validator.test.internal.xml.Child" ignore-annotations="false">
18+
<getter name="parentAttribute">
19+
<constraint annotation="javax.validation.constraints.NotNull"/>
20+
</getter>
21+
<getter name="parentListAttribute">
22+
<container-element-type type-argument-index="0">
23+
<constraint annotation="javax.validation.constraints.NotNull"/>
24+
</container-element-type>
25+
</getter>
26+
</bean>
27+
28+
</constraint-mappings>

0 commit comments

Comments
 (0)