Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

HV-421: Implemented checking of parameter constraints in type hierarc…

…hies
  • Loading branch information...
commit be407914826c4e91efe1f651596490025d3e0e9e 1 parent 3474fdd
Gunnar Morling authored
113 hibernate-validator/src/main/java/org/hibernate/validator/metadata/BeanMetaDataImpl.java
@@ -32,6 +32,7 @@
32 32 import java.util.Map;
33 33 import java.util.Map.Entry;
34 34 import java.util.Set;
  35 +import javax.validation.ConstraintDefinitionException;
35 36 import javax.validation.GroupDefinitionException;
36 37 import javax.validation.GroupSequence;
37 38 import javax.validation.Valid;
@@ -48,6 +49,7 @@
48 49
49 50 import static org.hibernate.validator.util.CollectionHelper.newArrayList;
50 51 import static org.hibernate.validator.util.CollectionHelper.newHashMap;
  52 +import static org.hibernate.validator.util.CollectionHelper.newHashSet;
51 53 import static org.hibernate.validator.util.ReflectionHelper.newInstance;
52 54
53 55 /**
@@ -85,6 +87,14 @@
85 87 private Map<Class<?>, Map<Method, MethodMetaData>> methodMetaConstraints = new HashMap<Class<?>, Map<Method, MethodMetaData>>();
86 88
87 89 /**
  90 + * Contains meta data for all method's of this type (including the method's
  91 + * from its super types). Used only at construction time to determine whether
  92 + * there are any illegal parameter constraints for overridden methods in a
  93 + * inheritance tree.
  94 + */
  95 + private final Set<MethodMetaData> allMethods = newHashSet();
  96 +
  97 + /**
88 98 * List of cascaded members.
89 99 */
90 100 private List<Member> cascadedMembers = new ArrayList<Member>();
@@ -199,6 +209,58 @@ public BeanMetaDataImpl(Class<T> beanClass,
199 209 addCascadedMember( member );
200 210 }
201 211 }
  212 +
  213 + checkParameterConstraints();
  214 + }
  215 +
  216 + /**
  217 + * Checks that there are no invalid parameter constraints defined at this
  218 + * type's methods. The following rules apply:
  219 + * <ul>
  220 + * <li>Only the root method of an overridden method in an inheritance
  221 + * hierarchy may be annotated with parameter constraints in order to avoid
  222 + * the strengthening of a method's preconditions by additional parameter
  223 + * constraints defined at sub-types. If the root method itself has no
  224 + * parameter constraints, also no parameter constraints may be added in
  225 + * sub-types.</li>
  226 + * <li>If there are multiple root methods for an method in an inheritance
  227 + * hierarchy (e.g. by implementing two interfaces defining the same method)
  228 + * no parameter constraints for this method are allowed at all in order to
  229 + * avoid a strengthening of a method's preconditions in parallel types.</li>
  230 + * </ul>
  231 + */
  232 + private void checkParameterConstraints() {
  233 +
  234 + for ( MethodMetaData oneMethod : getMethodsWithParameterConstraints( allMethods ) ) {
  235 +
  236 + Set<MethodMetaData> methodsWithSameSignature = getMethodsWithSameSignature( allMethods, oneMethod );
  237 + Set<MethodMetaData> methodsWithSameSignatureAndParameterConstraints = getMethodsWithParameterConstraints(
  238 + methodsWithSameSignature
  239 + );
  240 +
  241 + if ( methodsWithSameSignatureAndParameterConstraints.size() > 1 ) {
  242 + throw new ConstraintDefinitionException(
  243 + "Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints, " +
  244 + "but there are parameter constraints defined at all of the following overridden methods: " +
  245 + methodsWithSameSignatureAndParameterConstraints
  246 + );
  247 + }
  248 +
  249 + for ( MethodMetaData oneMethodWithSameSignature : methodsWithSameSignature ) {
  250 + if ( !oneMethod.getMethod().getDeclaringClass()
  251 + .isAssignableFrom( oneMethodWithSameSignature.getMethod().getDeclaringClass() ) ) {
  252 + throw new ConstraintDefinitionException(
  253 + "Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints. " +
  254 + "The following method itself has no parameter constraints but it is not defined one a sub-type of " +
  255 + oneMethod.getMethod().getDeclaringClass() +
  256 + ": " + oneMethodWithSameSignature
  257 + );
  258 + }
  259 + }
  260 +
  261 + }
  262 +
  263 +
202 264 }
203 265
204 266 public Class<T> getBeanClass() {
@@ -336,6 +398,8 @@ private void addMetaConstraint(Class<?> clazz, BeanMetaConstraint<T, ? extends A
336 398
337 399 private void addMethodMetaConstraint(Class<?> clazz, MethodMetaData methodMetaData) {
338 400
  401 + allMethods.add( methodMetaData );
  402 +
339 403 Map<Method, MethodMetaData> constraintsOfClass = methodMetaConstraints.get( clazz );
340 404
341 405 if ( constraintsOfClass == null ) {
@@ -654,7 +718,8 @@ else if ( constraintHelper.isMultiValueConstraint( annotationType ) ) {
654 718 *
655 719 * @param method The method to check for constraints annotations.
656 720 *
657   - * @return A list of constraint descriptors for all constraint specified for the given field or method.
  721 + * @return A meta data object describing the constraints specified for the
  722 + * given method.
658 723 */
659 724 private MethodMetaData getMethodMetaData(Method method) {
660 725
@@ -735,6 +800,52 @@ private ConstraintOrigin determineOrigin(Class<?> clazz) {
735 800 }
736 801 }
737 802
  803 + /**
  804 + * Returns a set with those methods from the given pile of methods that have
  805 + * the same method as the specified one. If the given method itself is part
  806 + * of the specified pile of methods, it also will be contained in the result
  807 + * set.
  808 + *
  809 + * @param methods The methods to search in.
  810 + * @param methodToCheck The method to compare against.
  811 + *
  812 + * @return A set with methods with the same signature as the given one. May
  813 + * be empty, but never null.
  814 + */
  815 + private Set<MethodMetaData> getMethodsWithSameSignature(Iterable<MethodMetaData> methods, MethodMetaData methodToCheck) {
  816 +
  817 + Set<MethodMetaData> theValue = newHashSet();
  818 +
  819 + for ( MethodMetaData oneMethod : methods ) {
  820 + if ( ReflectionHelper.haveSameSignature( oneMethod.getMethod(), methodToCheck.getMethod() ) ) {
  821 + theValue.add( oneMethod );
  822 + }
  823 + }
  824 + return theValue;
  825 + }
  826 +
  827 + /**
  828 + * Returns a set with those methods from the given pile of methods that have
  829 + * at least one constrained parameter or at least one parameter annotated
  830 + * with {@link Valid}.
  831 + *
  832 + * @param methods The methods to search in.
  833 + *
  834 + * @return A set with constrained methods. May be empty, but never null.
  835 + */
  836 + private Set<MethodMetaData> getMethodsWithParameterConstraints(Iterable<MethodMetaData> methods) {
  837 +
  838 + Set<MethodMetaData> theValue = newHashSet();
  839 +
  840 + for ( MethodMetaData oneMethod : methods ) {
  841 + if ( oneMethod.hasParameterConstraints() ) {
  842 + theValue.add( oneMethod );
  843 + }
  844 + }
  845 +
  846 + return theValue;
  847 + }
  848 +
738 849 @Override
739 850 public String toString() {
740 851 final StringBuilder sb = new StringBuilder();
69 hibernate-validator/src/main/java/org/hibernate/validator/metadata/MethodMetaData.java
@@ -22,6 +22,7 @@
22 22 import java.util.Iterator;
23 23 import java.util.List;
24 24 import java.util.Map;
  25 +import java.util.Map.Entry;
25 26
26 27 /**
27 28 * Represents a method of a Java type and all its associated meta-data relevant
@@ -40,6 +41,8 @@
40 41
41 42 private final boolean isCascading;
42 43
  44 + private final boolean hasParameterConstraints;
  45 +
43 46 public MethodMetaData(
44 47 Method method,
45 48 List<BeanMetaConstraint<?, ? extends Annotation>> constraints,
@@ -58,6 +61,17 @@ public MethodMetaData(
58 61 this.parameterMetaData = parameterMetaData;
59 62 this.constraints = constraints;
60 63 this.isCascading = isCascading;
  64 +
  65 + boolean foundParameterConstraint = false;
  66 +
  67 + for ( Entry<Integer, ParameterMetaData> oneParameter : parameterMetaData.entrySet() ) {
  68 + if ( oneParameter.getValue().isCascading() || oneParameter.getValue().iterator().hasNext() ) {
  69 + foundParameterConstraint = true;
  70 + break;
  71 + }
  72 + }
  73 +
  74 + this.hasParameterConstraints = foundParameterConstraint;
61 75 }
62 76
63 77 /**
@@ -99,11 +113,64 @@ public boolean isCascading() {
99 113 return isCascading;
100 114 }
101 115
  116 + /**
  117 + * Whether this method has at least one cascaded parameter or at least one
  118 + * parameter with constraints.
  119 + *
  120 + * @return <code>True</code>, if this method has at least one cascading or
  121 + * constrained parameter, <code>false</code> otherwise.
  122 + */
  123 + public boolean hasParameterConstraints() {
  124 + return hasParameterConstraints;
  125 + }
  126 +
102 127 @Override
103 128 public String toString() {
104 129 return "MethodMetaData [method=" + method + ", parameterMetaData="
105 130 + parameterMetaData + ", constraints=" + constraints
106   - + ", isCascading=" + isCascading + "]";
  131 + + ", isCascading=" + isCascading + ", hasParameterConstraints="
  132 + + hasParameterConstraints + "]";
  133 + }
  134 +
  135 + /**
  136 + * It is expected that there is exactly one instance of this type for a
  137 + * given method in a type system. This method is therefore only based on the
  138 + * hash code defined by the represented {@link Method}.
  139 + */
  140 + @Override
  141 + public int hashCode() {
  142 + final int prime = 31;
  143 + int result = 1;
  144 + result = prime * result + ( ( method == null ) ? 0 : method.hashCode() );
  145 + return result;
  146 + }
  147 +
  148 + /**
  149 + * It is expected that there is exactly one instance of this type for a
  150 + * given method in a type system. This method is therefore only based on
  151 + * the equality as defined by the represented {@link Method}.
  152 + */
  153 + @Override
  154 + public boolean equals(Object obj) {
  155 + if ( this == obj ) {
  156 + return true;
  157 + }
  158 + if ( obj == null ) {
  159 + return false;
  160 + }
  161 + if ( getClass() != obj.getClass() ) {
  162 + return false;
  163 + }
  164 + MethodMetaData other = (MethodMetaData) obj;
  165 + if ( method == null ) {
  166 + if ( other.method != null ) {
  167 + return false;
  168 + }
  169 + }
  170 + else if ( !method.equals( other.method ) ) {
  171 + return false;
  172 + }
  173 + return true;
107 174 }
108 175
109 176 }
22 ...e-validator/src/test/java/org/hibernate/validator/test/engine/methodlevel/MethodLevelValidationTest.java
@@ -379,28 +379,6 @@ public void validFromOverriddenMethodIsEvaluated() {
379 379 }
380 380
381 381 @Test
382   - public void constraintsAtOverridingMethodAreEvaluated() {
383   -
384   - try {
385   -
386   - customerRepository.foo( Long.valueOf( 0 ) );
387   - fail( "Expected MethodConstraintViolationException wasn't thrown." );
388   - }
389   - catch ( MethodConstraintViolationException e ) {
390   -
391   - assertEquals( e.getConstraintViolations().size(), 1 );
392   -
393   - MethodConstraintViolation<?> constraintViolation = e.getConstraintViolations().iterator().next();
394   - assertEquals( constraintViolation.getMessage(), "must be greater than or equal to 1" );
395   - assertEquals( constraintViolation.getMethod().getName(), "foo" );
396   - assertEquals( constraintViolation.getMethod().getDeclaringClass(), CustomerRepository.class );
397   - assertEquals( constraintViolation.getParameterIndex(), Integer.valueOf( 0 ) );
398   - assertEquals( constraintViolation.getKind(), Kind.PARAMETER );
399   - assertEquals( constraintViolation.getRootBeanClass(), CustomerRepositoryImpl.class );
400   - }
401   - }
402   -
403   - @Test
404 382 public void parameterValidationOfParameterlessMethod() {
405 383 customerRepository.boz();
406 384 }
2  ...-validator/src/test/java/org/hibernate/validator/test/engine/methodlevel/service/CustomerRepository.java
@@ -45,7 +45,7 @@
45 45
46 46 void cascadingParameter(@NotNull @Valid Customer param1, @NotNull @Valid Customer param2);
47 47
48   - void foo(@Min(1) Long id);
  48 + void foo(Long id);
49 49
50 50 void bar(Customer customer);
51 51
87 hibernate-validator/src/test/java/org/hibernate/validator/test/metadata/BeanMetaDataImplTest.java
@@ -18,7 +18,10 @@
18 18
19 19 import java.lang.annotation.Annotation;
20 20 import java.lang.reflect.Method;
  21 +import javax.validation.ConstraintDefinitionException;
21 22 import javax.validation.constraints.Min;
  23 +import javax.validation.constraints.NotNull;
  24 +import javax.validation.constraints.Size;
22 25
23 26 import org.testng.annotations.Test;
24 27
@@ -95,6 +98,33 @@ public void cascadingConstraintAtMethodReturnValue() throws Exception {
95 98 return new BeanMetaDataImpl<T>( clazz, new ConstraintHelper(), new BeanMetaDataCache() );
96 99 }
97 100
  101 + @Test(
  102 + expectedExceptions = ConstraintDefinitionException.class,
  103 + expectedExceptionsMessageRegExp = "Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints\\. The following.*"
  104 + )
  105 + public void parameterConstraintsAddedInSubTypeCausesDefinitionException() {
  106 +
  107 + setupBeanMetaData( FooExtImpl.class );
  108 + }
  109 +
  110 + @Test(
  111 + expectedExceptions = ConstraintDefinitionException.class,
  112 + expectedExceptionsMessageRegExp = "Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints, but there are.*"
  113 + )
  114 + public void constraintStrengtheningInSubTypeCausesDefinitionException() {
  115 +
  116 + setupBeanMetaData( BarExtImpl.class );
  117 + }
  118 +
  119 + @Test(
  120 + expectedExceptions = ConstraintDefinitionException.class,
  121 + expectedExceptionsMessageRegExp = "Only the root method of an overridden method in an inheritance hierarchy may be annotated with parameter constraints\\. The following.*"
  122 + )
  123 + public void parameterConstraintsInHierarchyWithMultipleRootMethodsCausesDefinitionException() {
  124 +
  125 + setupBeanMetaData( BazImpl.class );
  126 + }
  127 +
98 128 private void assertSize(Iterable<?> iterable, int expectedCount) {
99 129
100 130 int i = 0;
@@ -105,4 +135,61 @@ private void assertSize(Iterable<?> iterable, int expectedCount) {
105 135
106 136 assertEquals( i, expectedCount, "Actual size of iterable [" + iterable + "] differed from expected size." );
107 137 }
  138 +
  139 + public static interface Foo {
  140 +
  141 + void foo(String s);
  142 + }
  143 +
  144 + public static interface FooExt extends Foo {
  145 +
  146 + /**
  147 + * Adds constraints to an un-constrained method from a super-type, which is not allowed.
  148 + */
  149 + void foo(@NotNull String s);
  150 + }
  151 +
  152 + public static class FooExtImpl implements FooExt {
  153 +
  154 + public void foo(String s) {
  155 + }
  156 + }
  157 +
  158 + public static interface Bar {
  159 +
  160 + void bar(@NotNull String s);
  161 + }
  162 +
  163 + public static interface BarExt extends Bar {
  164 +
  165 + /**
  166 + * Adds constraints to a constrained method from a super-type, which is not allowed.
  167 + */
  168 + void bar(@Size(min = 3) String s);
  169 + }
  170 +
  171 + public static class BarExtImpl implements BarExt {
  172 +
  173 + public void bar(String s) {
  174 + }
  175 + }
  176 +
  177 + public static interface Baz1 {
  178 +
  179 + void baz(String s);
  180 + }
  181 +
  182 + public static interface Baz2 {
  183 +
  184 + void baz(@Size(min = 3) String s);
  185 + }
  186 +
  187 + public static class BazImpl implements Baz1, Baz2 {
  188 +
  189 + /**
  190 + * Implements a method defined by two interfaces, with di a super-type, which is not allowed.
  191 + */
  192 + public void baz(String s) {
  193 + }
  194 + }
108 195 }

0 comments on commit be40791

Please sign in to comment.
Something went wrong with that request. Please try again.