/* * Hibernate Validator, declare and validate application constraints * * License: Apache License, Version 2.0 * See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>. */ package org.hibernate.validator.test.internal.engine.valuehandling; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertCorrectConstraintTypes; import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertCorrectPropertyPaths; import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertNumberOfViolations; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.Set; import javax.validation.Constraint; import javax.validation.ConstraintDeclarationException; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintViolation; import javax.validation.Payload; import javax.validation.UnexpectedTypeException; import javax.validation.Validator; import javax.validation.constraints.Future; import javax.validation.constraints.Min; import javax.validation.constraints.Null; import javax.validation.valueextraction.ExtractedValue; import javax.validation.valueextraction.UnwrapByDefault; import javax.validation.valueextraction.Unwrapping; import javax.validation.valueextraction.ValueExtractor; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.testutil.TestForIssue; import org.hibernate.validator.testutils.ValidatorUtil; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; /** * Test the various scenarios for explicit and implicit unwrapping of values. * * @author Hardy Ferentschik * @author Guillaume Smet */ @TestForIssue(jiraKey = "HV-925") @SuppressWarnings("unused") public class UnwrappingTest { private Validator validatorWithValueExtractor; private Validator validatorWithoutValueExtractor; @BeforeClass public void setupValidator() { validatorWithoutValueExtractor = ValidatorUtil.getValidator(); validatorWithValueExtractor = ValidatorUtil.getConfiguration() .addValueExtractor( new ValueHolderExtractor() ) .addValueExtractor( new UnwrapByDefaultWrapperValueExtractor() ) .addValueExtractor( new WrapperWithTwoTypeArgumentsValueExtractor() ) .buildValidatorFactory() .getValidator(); } @Test(expectedExceptions = UnexpectedTypeException.class, expectedExceptionsMessageRegExp = "HV000030.*") public void no_constraint_validator_for_wrapped_value_throws_exception() { validatorWithoutValueExtractor.validate( new Foo() ); } @Test(expectedExceptions = UnexpectedTypeException.class, expectedExceptionsMessageRegExp = "HV000030.*") public void no_constraint_validator_for_unwrapped_value_throws_exception() { validatorWithValueExtractor.validate( new Fubar() ); } @Test(expectedExceptions = ConstraintDeclarationException.class, expectedExceptionsMessageRegExp = "HV000198.*") public void missing_value_extractor_throws_exception() { validatorWithoutValueExtractor.validate( new Foobar() ); } @Test public void validate_wrapper_itself_if_there_is_no_value_extractor() { Set<ConstraintViolation<Qux>> constraintViolations = validatorWithoutValueExtractor.validate( new Qux() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerHolder" ); assertCorrectConstraintTypes( constraintViolations, ValueHolderConstraint.class ); } @Test public void validate_wrapper_itself_even_if_there_is_a_value_extractor() { Set<ConstraintViolation<Qux>> constraintViolations = validatorWithValueExtractor.validate( new Qux() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerHolder" ); assertCorrectConstraintTypes( constraintViolations, ValueHolderConstraint.class ); // execute validation twice to ensure that the handling for this case is not subjective to caching (see HV-976) constraintViolations = validatorWithValueExtractor.validate( new Qux() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerHolder" ); assertCorrectConstraintTypes( constraintViolations, ValueHolderConstraint.class ); } @Test public void validate_wrapper_itself_if_there_is_no_value_extractor_even_if_constraint_could_be_applied_to_unwrapped_value() { Set<ConstraintViolation<Baz>> constraintViolations = validatorWithoutValueExtractor.validate( new Baz() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerHolder" ); assertCorrectConstraintTypes( constraintViolations, Null.class ); } @Test(enabled = false, expectedExceptions = UnexpectedTypeException.class, expectedExceptionsMessageRegExp = "HV000186.*") // TODO implicit unwrapping not supported for now public void constraint_declaration_exception_if_there_are_validators_for_wrapper_and_wrapped_value() { validatorWithValueExtractor.validate( new Baz() ); } @Test public void validate_wrapped_value_if_value_extractor_unwraps_by_default() { Set<ConstraintViolation<WrapperWithImplicitUnwrapping>> constraintViolations = validatorWithValueExtractor.validate( new WrapperWithImplicitUnwrapping() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerWrapper" ); assertCorrectConstraintTypes( constraintViolations, Min.class ); } @Test(expectedExceptions = UnexpectedTypeException.class, expectedExceptionsMessageRegExp = "HV000030.*") public void validation_exception_if_unwrapping_disabled_per_constraint() { validatorWithValueExtractor.validate( new WrapperWithDisabledUnwrapping() ); } @Test public void validate_wrapped_value_if_value_extractor_unwraps_by_default_and_unwrapping_enabled_per_constraint() { Set<ConstraintViolation<WrapperWithForcedUnwrapping>> constraintViolations = validatorWithValueExtractor.validate( new WrapperWithForcedUnwrapping() ); assertNumberOfViolations( constraintViolations, 1 ); assertCorrectPropertyPaths( constraintViolations, "integerWrapper" ); assertCorrectConstraintTypes( constraintViolations, Min.class ); } @Test(expectedExceptions = ConstraintDeclarationException.class, expectedExceptionsMessageRegExp = "HV000199.*") public void validate_wrapped_value_while_wrapper_has_two_type_parameters_raises_exception() { validatorWithValueExtractor.validate( new BeanWithWrapperWithTwoTypeArguments() ); } private class Foo { // no constraint validator defined for @DummyConstraint @DummyConstraint private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); } private class Fubar { // no constraint validator for the wrapped value @Future(payload = { Unwrapping.Unwrap.class }) private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); } private class Foobar { @Min(value = 10, payload = { Unwrapping.Unwrap.class }) private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); } private class Bar { @Min(10) private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); private final ValueHolder<@NotBlank String> stringHolder = new ValueHolder<>( "" ); } private class Baz { @Null private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); } private class Qux { @ValueHolderConstraint private final ValueHolder<Integer> integerHolder = new ValueHolder<>( 5 ); } private class WrapperWithImplicitUnwrapping { @Min(10) private final Wrapper<Integer> integerWrapper = new Wrapper<>( 5 ); } private class WrapperWithDisabledUnwrapping { @Min(value = 10, payload = { Unwrapping.Skip.class }) private final Wrapper<Integer> integerWrapper = new Wrapper<>( 5 ); } private class WrapperWithForcedUnwrapping { @Min(value = 10, payload = { Unwrapping.Unwrap.class }) private final Wrapper<Integer> integerWrapper = new Wrapper<>( 5 ); } private class BeanWithWrapperWithTwoTypeArguments { @Min(value = 10, payload = { Unwrapping.Unwrap.class }) private final WrapperWithTwoTypeArguments<Long, String> wrapper = new WrapperWithTwoTypeArguments<>( 5L, "value" ); } private class ValueHolder<T> { private final T value; private ValueHolder(T value) { this.value = value; } public T getValue() { return value; } } private class Wrapper<T> { private final T value; private Wrapper(T value) { this.value = value; } public T getValue() { return value; } } private class WrapperWithTwoTypeArguments<T, U> { private final T value1; private final U value2; private WrapperWithTwoTypeArguments(T value1, U value2) { this.value1 = value1; this.value2 = value2; } } @Documented @Constraint(validatedBy = {}) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface DummyConstraint { String message() default "dummy constraint"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } @Documented @Constraint(validatedBy = { ValueHandlerConstraintValidator.class }) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) public @interface ValueHolderConstraint { String message() default "value holder constraint"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } public static class ValueHandlerConstraintValidator implements ConstraintValidator<ValueHolderConstraint, ValueHolder> { @Override public boolean isValid(ValueHolder value, ConstraintValidatorContext context) { return false; } } private class ValueHolderExtractor implements ValueExtractor<ValueHolder<@ExtractedValue ?>> { @Override public void extractValues(ValueHolder<@ExtractedValue ?> originalValue, ValueExtractor.ValueReceiver receiver) { receiver.value( null, originalValue.value ); } } @UnwrapByDefault private class UnwrapByDefaultWrapperValueExtractor implements ValueExtractor<Wrapper<@ExtractedValue ?>> { @Override public void extractValues(Wrapper<@ExtractedValue ?> originalValue, ValueExtractor.ValueReceiver receiver) { receiver.value( null, originalValue.value ); } } private class WrapperWithTwoTypeArgumentsValueExtractor implements ValueExtractor<WrapperWithTwoTypeArguments<@ExtractedValue ?, ?>> { @Override public void extractValues(WrapperWithTwoTypeArguments<?, ?> originalValue, ValueExtractor.ValueReceiver receiver) { receiver.value( null, originalValue.value1 ); } } }