Skip to content

Commit 6b24ba8

Browse files
marko-bekhtagsmet
authored andcommitted
HV-1450 Changed which executables are excepted by ExecutableMetaData.Builder
- changed the logic on how executable builder determines if it should accept a constrained element. Used a most specific sub type from the hierarchy under inspection to resolve executables and check if they override each other somewhere in the hierarchy. - changed the order of metadata providers. Made annotation based as a first provider. This should build all possible builder delegates after the first iteration on metadata providers and data from other providers will be just merged in.
1 parent 0f64f72 commit 6b24ba8

File tree

4 files changed

+169
-17
lines changed

4 files changed

+169
-17
lines changed

engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManager.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,6 @@ public BeanMetaDataManager(ConstraintHelper constraintHelper,
127127
this.parameterNameProvider = parameterNameProvider;
128128
this.validationOrderGenerator = validationOrderGenerator;
129129

130-
this.metaDataProviders = newArrayList();
131-
this.metaDataProviders.addAll( optionalMetaDataProviders );
132-
133130
this.methodValidationConfiguration = methodValidationConfiguration;
134131

135132
this.beanMetaDataCache = new ConcurrentReferenceHashMap<>(
@@ -141,15 +138,19 @@ public BeanMetaDataManager(ConstraintHelper constraintHelper,
141138
EnumSet.of( IDENTITY_COMPARISONS )
142139
);
143140

144-
AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders();
141+
AnnotationProcessingOptions annotationProcessingOptions = getAnnotationProcessingOptionsFromNonDefaultProviders( optionalMetaDataProviders );
145142
AnnotationMetaDataProvider defaultProvider = new AnnotationMetaDataProvider(
146143
constraintHelper,
147144
typeResolutionHelper,
148145
valueExtractorManager,
149146
annotationProcessingOptions
150147
);
151-
148+
this.metaDataProviders = newArrayList();
149+
// default annotation based metadata provider should go first in the list of providers to prevent possible issues
150+
// similar to HV-1450. This way after the first run we will have all possible constrained elements in the list and
151+
// should only merge in additional constraint information from other providers.
152152
this.metaDataProviders.add( defaultProvider );
153+
this.metaDataProviders.addAll( optionalMetaDataProviders );
153154
}
154155

155156
@SuppressWarnings("unchecked")
@@ -195,9 +196,9 @@ private <T> BeanMetaDataImpl<T> createBeanMetaData(Class<T> clazz) {
195196
/**
196197
* @return returns the annotation ignores from the non annotation based meta data providers
197198
*/
198-
private AnnotationProcessingOptions getAnnotationProcessingOptionsFromNonDefaultProviders() {
199+
private AnnotationProcessingOptions getAnnotationProcessingOptionsFromNonDefaultProviders(List<MetaDataProvider> optionalMetaDataProviders) {
199200
AnnotationProcessingOptions options = new AnnotationProcessingOptionsImpl();
200-
for ( MetaDataProvider metaDataProvider : metaDataProviders ) {
201+
for ( MetaDataProvider metaDataProvider : optionalMetaDataProviders ) {
201202
options.merge( metaDataProvider.getAnnotationProcessingOptions() );
202203
}
203204

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,15 @@ public boolean accepts(ConstrainedElement constrainedElement) {
310310

311311
//are the locations equal (created by different builders) or
312312
//does one of the executables override the other one?
313-
return executable.equals( candidate ) ||
314-
overrides( executable, candidate ) ||
315-
overrides( candidate, executable );
313+
return resolveToSameMethodInHierarchy( executable, candidate );
314+
}
315+
316+
private boolean resolveToSameMethodInHierarchy(Executable first, Executable other) {
317+
if ( first instanceof Constructor || other instanceof Constructor ) {
318+
return first.equals( other );
319+
}
320+
321+
return executableHelper.resolveToSameMethodInHierarchy( getBeanClass(), (Method) first, (Method) other );
316322
}
317323

318324
private boolean overrides(Executable first, Executable other) {

engine/src/main/java/org/hibernate/validator/internal/util/ExecutableHelper.java

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.stream.Collectors;
1818
import java.util.stream.Stream;
1919

20+
import org.hibernate.validator.internal.util.classhierarchy.Filters;
2021
import org.hibernate.validator.internal.util.logging.Log;
2122
import org.hibernate.validator.internal.util.logging.LoggerFactory;
2223
import org.hibernate.validator.internal.util.privilegedactions.GetResolvedMemberMethods;
@@ -90,14 +91,83 @@ public boolean overrides(Method subTypeMethod, Method superTypeMethod) {
9091
return false;
9192
}
9293

93-
if ( !Modifier.isPublic( superTypeMethod.getModifiers() ) && !Modifier.isProtected( superTypeMethod.getModifiers() )
94-
&& !superTypeMethod.getDeclaringClass().getPackage().equals( subTypeMethod.getDeclaringClass().getPackage() ) ) {
94+
if ( !isOneMethodVisibleToAnother( subTypeMethod, superTypeMethod ) ) {
9595
return false;
9696
}
9797

9898
return instanceMethodParametersResolveToSameTypes( subTypeMethod, superTypeMethod );
9999
}
100100

101+
/**
102+
* Checks if a pair of given methods ({@code left} and {@code right}) are resolved to the same
103+
* method based on the {@code mainSubType} type.
104+
*
105+
* @param mainSubType a type at the bottom of class hierarchy to be used to lookup the methods.
106+
* @param left one of the methods to check
107+
* @param right another of the methods to check
108+
*
109+
* @return {@code true} if a pair of methods are equal {@code left == right}, or one of the methods
110+
* override another one in the class hierarchy with {@code mainSubType} at the bottom,
111+
* {@code false} otherwise.
112+
*/
113+
public boolean resolveToSameMethodInHierarchy(Class<?> mainSubType, Method left, Method right) {
114+
Contracts.assertValueNotNull( mainSubType, "mainSubType" );
115+
Contracts.assertValueNotNull( left, "left" );
116+
Contracts.assertValueNotNull( right, "right" );
117+
118+
if ( left.equals( right ) ) {
119+
return true;
120+
}
121+
122+
if ( !left.getName().equals( right.getName() ) ) {
123+
return false;
124+
}
125+
126+
// methods with same name in the same class should be different
127+
if ( left.getDeclaringClass().equals( right.getDeclaringClass() ) ) {
128+
return false;
129+
}
130+
131+
if ( left.getParameterTypes().length != right.getParameterTypes().length ) {
132+
return false;
133+
}
134+
135+
// if at least one method from a pair is static - they are different methods
136+
if ( Modifier.isStatic( right.getModifiers() ) || Modifier.isStatic( left.getModifiers() ) ) {
137+
return false;
138+
}
139+
140+
// HV-861 Bridge method should be ignored. Classmates type/member resolution will take care of proper
141+
// override detection without considering bridge methods
142+
if ( left.isBridge() || right.isBridge() ) {
143+
return false;
144+
}
145+
146+
// if one of the methods is private - methods are different
147+
if ( Modifier.isPrivate( left.getModifiers() ) || Modifier.isPrivate( right.getModifiers() ) ) {
148+
return false;
149+
}
150+
151+
if ( !isOneMethodVisibleToAnother( left, right ) || !isOneMethodVisibleToAnother( right, left ) ) {
152+
return false;
153+
}
154+
155+
// We need to check if the passed mainSubType is not a Weld proxy. In case of proxy we need to get
156+
// a class that was proxied otherwise we can use the class itself. This is due to the issue that
157+
// call to Class#getGenericInterfaces() on a Weld proxy returns raw types instead of parametrized
158+
// generics and methods will not be resolved correctly.
159+
return instanceMethodParametersResolveToSameTypes(
160+
Filters.excludeProxies().accepts( mainSubType ) ? mainSubType : mainSubType.getSuperclass(),
161+
left,
162+
right
163+
);
164+
}
165+
166+
private static boolean isOneMethodVisibleToAnother(Method left, Method right) {
167+
return Modifier.isPublic( right.getModifiers() ) || Modifier.isProtected( right.getModifiers() )
168+
|| right.getDeclaringClass().getPackage().equals( left.getDeclaringClass().getPackage() );
169+
}
170+
101171
public static String getSimpleName(Executable executable) {
102172
return executable instanceof Constructor ? executable.getDeclaringClass().getSimpleName() : executable.getName();
103173
}
@@ -140,14 +210,18 @@ public static ElementType getElementType(Executable executable) {
140210
* @return {@code true} if the parameters of the two methods resolve to the same types, {@code false otherwise}.
141211
*/
142212
private boolean instanceMethodParametersResolveToSameTypes(Method subTypeMethod, Method superTypeMethod) {
143-
if ( subTypeMethod.getParameterTypes().length == 0 ) {
213+
return instanceMethodParametersResolveToSameTypes( subTypeMethod.getDeclaringClass(), subTypeMethod, superTypeMethod );
214+
}
215+
216+
private boolean instanceMethodParametersResolveToSameTypes(Class<?> mainSubType, Method left, Method right) {
217+
if ( left.getParameterTypes().length == 0 ) {
144218
return true;
145219
}
146220

147-
ResolvedType resolvedSubType = typeResolver.resolve( subTypeMethod.getDeclaringClass() );
221+
ResolvedType resolvedSubType = typeResolver.resolve( mainSubType );
148222

149223
MemberResolver memberResolver = new MemberResolver( typeResolver );
150-
memberResolver.setMethodFilter( new SimpleMethodFilter( subTypeMethod, superTypeMethod ) );
224+
memberResolver.setMethodFilter( new SimpleMethodFilter( left, right ) );
151225
ResolvedTypeWithMembers typeWithMembers = memberResolver.resolve(
152226
resolvedSubType,
153227
null,
@@ -181,9 +255,9 @@ private boolean instanceMethodParametersResolveToSameTypes(Method subTypeMethod,
181255
catch (ArrayIndexOutOfBoundsException e) {
182256
LOG.debug(
183257
"Error in ExecutableHelper#instanceMethodParametersResolveToSameTypes comparing "
184-
+ subTypeMethod
258+
+ left
185259
+ " with "
186-
+ superTypeMethod
260+
+ right
187261
);
188262
}
189263

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.engine.methodvalidation;
8+
9+
import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat;
10+
import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf;
11+
12+
import java.lang.reflect.Method;
13+
import java.util.Set;
14+
15+
import javax.validation.ConstraintViolation;
16+
import javax.validation.Validation;
17+
import javax.validation.Validator;
18+
import javax.validation.constraints.NotNull;
19+
20+
import org.hibernate.validator.testutil.TestForIssue;
21+
22+
import org.testng.annotations.Test;
23+
24+
/**
25+
* @author Marko Bekhta
26+
*/
27+
public class MethodParameterConstraintsInParallelHierarchyTest {
28+
29+
/**
30+
* NOTE: prior to the changes where this test was added it would fail randomly.
31+
*/
32+
@Test
33+
@TestForIssue(jiraKey = "HV-1450")
34+
public void testDeepParallelHierarchyIsProcessedCorrectly() throws Exception {
35+
36+
WebServiceImpl service = new WebServiceImpl();
37+
Method method = WebServiceImpl.class.getMethod( "getEntityVersion", Long.class );
38+
Object[] params = new Object[] { null };
39+
40+
Validator validator = Validation.byDefaultProvider().configure()
41+
.buildValidatorFactory().getValidator();
42+
43+
Set<ConstraintViolation<WebServiceImpl>> violations =
44+
validator.forExecutables().validateParameters( service, method, params );
45+
46+
assertThat( violations ).containsOnlyViolations( violationOf( NotNull.class ) );
47+
}
48+
49+
private class WebServiceImpl extends AbstractWebService implements ExtendedWebService {
50+
51+
}
52+
53+
private abstract class AbstractWebService implements WebService {
54+
55+
@Override
56+
public int getEntityVersion(Long id) {
57+
return id.intValue();
58+
}
59+
}
60+
61+
private interface ExtendedWebService extends WebService {
62+
63+
@Override
64+
int getEntityVersion(Long id);
65+
}
66+
67+
private interface WebService {
68+
69+
int getEntityVersion(@NotNull Long id);
70+
}
71+
}

0 commit comments

Comments
 (0)