diff --git a/build.gradle b/build.gradle index 92368c52d..eb4056cce 100644 --- a/build.gradle +++ b/build.gradle @@ -73,6 +73,7 @@ subprojects { Project subproject -> implementation "org.apache.poi:poi:$apachePoiVersion" implementation "org.apache.poi:poi-ooxml:$apachePoiVersion" implementation "org.modelmapper:modelmapper:$modelMapperVersion" + implementation "org.reflections:reflections:$reflectionsVersion" } } diff --git a/gradle.properties b/gradle.properties index 0b662da21..b93abbb50 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,6 +4,7 @@ gradleAggregateJavadocPluginVersion=8.3 gradleJgitverPluginVersion=0.10.0-rc03 gradlePublishPluginVersion=1.1.0 modelMapperVersion=3.1.1 +reflectionsVersion=0.10.2 springBootVersion=3.1.3 # POM metadata diff --git a/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/mapping/ConstraintMappingRegistrar.java b/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/mapping/ConstraintMappingRegistrar.java new file mode 100644 index 000000000..bef498a2a --- /dev/null +++ b/nrich-validation-api/src/main/java/net/croz/nrich/validation/api/mapping/ConstraintMappingRegistrar.java @@ -0,0 +1,26 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.api.mapping; + +import jakarta.validation.Configuration; + +public interface ConstraintMappingRegistrar { + + void registerConstraints(Configuration validatorConfiguration); + +} diff --git a/nrich-validation-spring-boot-starter/README.md b/nrich-validation-spring-boot-starter/README.md index 5209a0fd3..3018b5fd0 100644 --- a/nrich-validation-spring-boot-starter/README.md +++ b/nrich-validation-spring-boot-starter/README.md @@ -41,23 +41,45 @@ Note if using `nrich-bom` dependency versions should be omitted. Configuration is done through a property file, available properties and descriptions are given bellow (all properties are prefixed with nrich.validation which is omitted for readability): -| property | description | default value | -|--------------------|------------------------------------------------------------------|---------------| -| register-messages | Whether default validation failure messages should be registered | true | +| property | description | default value | +|------------------------|------------------------------------------------------------------|------------------------------------------------| +| register-messages | Whether default validation failure messages should be registered | true | +| register-validators | Whether default validators should be registered | true | +| validator-package-list | List of packages from which to register validators | net.croz.nrich.validation.constraint.validator | The default configuration values in yaml format for easier modification are given bellow: ```yaml nrich.validation: - register-messages: true + register-messages: true + register-validators: true + validator-package-list: net.croz.nrich.validation.constraint.validator ``` ### Using the module Users should just add the dependency on classpath and then use the provided constraints. If custom messages are required they should be defined in `messages.properties` file -and default ones disabled then through `nrich.validation.register-messages` property set to false. A list of available constraints and descriptions is given bellow: +and default ones disabled then through `nrich.validation.register-messages` property set to false. There are two options for registering the modules validators +first one is by automatic registration which is enabled by default through property `nrich.validation.register-validators`, the other option is by defining a standard `validation.xml` +file in `META-INF` directory and registering `ConstraintMappingContributor` implementation `net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor`. + +i.e + +```xml + + + net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor + + + +``` + +A list of available constraints and descriptions is given bellow: | constraint | description | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -72,7 +94,6 @@ and default ones disabled then through `nrich.validation.register-messages` prop | `@ValidRange` | Validates that the annotated element from property must be less than (or equal to if inclusive is true) to property | | `@ValidSearchProperties ` | Validates that at least one group of annotated element must contain all properties that are not null (i.e. when searching users that either name is not null or first and last name are not null) | - #### File related constraints Difference between `@ValidFile` and `@ValidFileResolvable` is that the former resolves allowed values from environment. Searched properties are given bellow but can be overridden on each constraint diff --git a/nrich-validation-spring-boot-starter/build.gradle b/nrich-validation-spring-boot-starter/build.gradle index a1ab3da0f..0e03c51c3 100644 --- a/nrich-validation-spring-boot-starter/build.gradle +++ b/nrich-validation-spring-boot-starter/build.gradle @@ -7,9 +7,9 @@ dependencies { annotationProcessor "org.projectlombok:lombok" compileOnly "org.projectlombok:lombok" - implementation "org.springframework.boot:spring-boot-autoconfigure" + implementation project(":nrich-validation") - runtimeOnly project(":nrich-validation") + implementation "org.springframework.boot:spring-boot-autoconfigure" testRuntimeOnly "ch.qos.logback:logback-classic" diff --git a/nrich-validation-spring-boot-starter/src/main/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfiguration.java b/nrich-validation-spring-boot-starter/src/main/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfiguration.java index 430094f92..e4c0d5cce 100644 --- a/nrich-validation-spring-boot-starter/src/main/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfiguration.java +++ b/nrich-validation-spring-boot-starter/src/main/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfiguration.java @@ -18,13 +18,19 @@ package net.croz.nrich.validation.starter.configuration; import lombok.RequiredArgsConstructor; +import net.croz.nrich.validation.api.mapping.ConstraintMappingRegistrar; +import net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingRegistrar; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.validation.ValidationConfigurationCustomizer; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.AbstractResourceBasedMessageSource; +import java.util.List; + @Configuration(proxyBeanMethods = false) public class NrichValidationAutoConfiguration { @@ -43,9 +49,21 @@ public static class ValidationMessageSourceRegistrar implements InitializingBean @Override public void afterPropertiesSet() { - if (messageSource instanceof AbstractResourceBasedMessageSource) { - ((AbstractResourceBasedMessageSource) messageSource).addBasenames(VALIDATION_MESSAGES_NAME); + if (messageSource instanceof AbstractResourceBasedMessageSource abstractResourceBasedMessageSource) { + abstractResourceBasedMessageSource.addBasenames(VALIDATION_MESSAGES_NAME); } } } + + @ConditionalOnProperty(name = "nrich.validation.register-validators", havingValue = "true", matchIfMissing = true) + @Bean + ConstraintMappingRegistrar constraintMappingRegistrar(@Value("${nrich.validation.validator-package-list:net.croz.nrich.validation.constraint.validator}") List validatorPacakgeList) { + return new DefaultConstraintMappingRegistrar(validatorPacakgeList); + } + + @ConditionalOnProperty(name = "nrich.validation.register-validators", havingValue = "true", matchIfMissing = true) + @Bean + ValidationConfigurationCustomizer validationConfigurationCustomizer(ConstraintMappingRegistrar constraintMappingRegistrar) { + return constraintMappingRegistrar::registerConstraints; + } } diff --git a/nrich-validation-spring-boot-starter/src/test/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfigurationTest.java b/nrich-validation-spring-boot-starter/src/test/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfigurationTest.java index 4d066c804..b84809ceb 100644 --- a/nrich-validation-spring-boot-starter/src/test/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfigurationTest.java +++ b/nrich-validation-spring-boot-starter/src/test/java/net/croz/nrich/validation/starter/configuration/NrichValidationAutoConfigurationTest.java @@ -17,8 +17,10 @@ package net.croz.nrich.validation.starter.configuration; +import net.croz.nrich.validation.api.mapping.ConstraintMappingRegistrar; import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.validation.ValidationConfigurationCustomizer; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.MessageSource; import org.springframework.context.support.AbstractResourceBasedMessageSource; @@ -34,8 +36,11 @@ class NrichValidationAutoConfigurationTest { @Test void shouldConfigureDefaultConfiguration() { // expect - contextRunner.run(context -> - assertThat(context).hasSingleBean(NrichValidationAutoConfiguration.ValidationMessageSourceRegistrar.class) + contextRunner.run(context -> { + assertThat(context).hasSingleBean(NrichValidationAutoConfiguration.ValidationMessageSourceRegistrar.class); + assertThat(context).hasSingleBean(ConstraintMappingRegistrar.class); + assertThat(context).hasSingleBean(ValidationConfigurationCustomizer.class); + } ); } @@ -47,6 +52,15 @@ void shouldNotRegisterValidationMessagesWhenDisabledViaProperty() { ); } + @Test + void shouldNotRegisterValidatorsWhenDisabledViaProperty() { + // expect + contextRunner.withPropertyValues("nrich.validation.register-validators=false").run(context -> { + assertThat(context).doesNotHaveBean(ConstraintMappingRegistrar.class); + assertThat(context).doesNotHaveBean(ValidationConfigurationCustomizer.class); + }); + } + @Test void shouldRegisterMessagesWhenPossible() { // given diff --git a/nrich-validation/README.md b/nrich-validation/README.md index bf4cce844..2c4e808ab 100644 --- a/nrich-validation/README.md +++ b/nrich-validation/README.md @@ -10,7 +10,7 @@ It also contains a list of messages for standard and additional constraints in e ## Setting up Spring beans -If automatic registration of messages is required then following configuration is required +If automatic registration of messages and validators (supported only for hibernate-validator) is required then following configuration is required (this can also be registered through application.properties by manually including `validationMessages` when using Spring Boot): ```java @@ -37,10 +37,32 @@ public class ApplicationConfiguration { } } } + + @Bean + Validator validator(ConstraintMappingRegistrar constraintMappingRegistrar) { + LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); + + registerConstraintMappingContributors(localValidatorFactoryBean, constraintMappingRegistrar); + + return localValidatorFactoryBean; + } + + @Bean + ConstraintMappingRegistrar constraintMappingRegistrar() { + return new DefaultConstraintMappingRegistrar(List.of("net.croz.nrich.validation.constraint.validator")); + } + + private void registerConstraintMappingContributors(LocalValidatorFactoryBean validator, ConstraintMappingRegistrar constraintMappingRegistrar) { + validator.setConfigurationInitializer(constraintMappingRegistrar::registerConstraints); + } } ``` +If validators don't need to be registered automatically then standard `validation.xml` file in `META-INF` directory should be provided. And inside `ConstraintMappingContributor` +implementation `net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor` should be registered. In that case `ConstraintMappingRegistrar` bean +and `registerConstraintMappingContributors` method are not needed. + ## Usage The module provides following constraints: diff --git a/nrich-validation/build.gradle b/nrich-validation/build.gradle index 29229bf4a..af89df074 100644 --- a/nrich-validation/build.gradle +++ b/nrich-validation/build.gradle @@ -9,6 +9,8 @@ dependencies { compileOnly "org.projectlombok:lombok" implementation "org.hibernate.validator:hibernate-validator" + implementation "org.slf4j:slf4j-api" + implementation "org.reflections:reflections" runtimeOnly "org.apache.tomcat.embed:tomcat-embed-el" diff --git a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java index 6e96b9c65..e7ae8c55d 100644 --- a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributor.java @@ -17,21 +17,21 @@ package net.croz.nrich.validation.constraint.mapping; -import net.croz.nrich.validation.api.constraint.SpelExpression; import net.croz.nrich.validation.api.constraint.InList; import net.croz.nrich.validation.api.constraint.MaxSizeInBytes; import net.croz.nrich.validation.api.constraint.NotNullWhen; import net.croz.nrich.validation.api.constraint.NullWhen; +import net.croz.nrich.validation.api.constraint.SpelExpression; import net.croz.nrich.validation.api.constraint.ValidFile; import net.croz.nrich.validation.api.constraint.ValidFileResolvable; import net.croz.nrich.validation.api.constraint.ValidOib; import net.croz.nrich.validation.api.constraint.ValidRange; import net.croz.nrich.validation.api.constraint.ValidSearchProperties; -import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; import net.croz.nrich.validation.constraint.validator.InListValidator; import net.croz.nrich.validation.constraint.validator.MaxSizeInBytesValidator; import net.croz.nrich.validation.constraint.validator.NotNullWhenValidator; import net.croz.nrich.validation.constraint.validator.NullWhenValidator; +import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; import net.croz.nrich.validation.constraint.validator.ValidFileResolvableValidator; import net.croz.nrich.validation.constraint.validator.ValidFileValidator; import net.croz.nrich.validation.constraint.validator.ValidOibValidator; diff --git a/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrar.java b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrar.java new file mode 100644 index 000000000..3da17bd56 --- /dev/null +++ b/nrich-validation/src/main/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrar.java @@ -0,0 +1,89 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.mapping; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.croz.nrich.validation.api.mapping.ConstraintMappingRegistrar; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cfg.ConstraintMapping; +import org.reflections.Reflections; +import org.reflections.scanners.Scanners; +import org.reflections.util.ConfigurationBuilder; + +import jakarta.validation.Configuration; +import jakarta.validation.ConstraintValidator; +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +@Slf4j +@RequiredArgsConstructor +public class DefaultConstraintMappingRegistrar implements ConstraintMappingRegistrar { + + private static final int INDEX_OF_CONSTRAINT_TYPE = 0; + + private final List constraintPacakgeList; + + @Override + public void registerConstraints(Configuration configuration) { + if (configuration instanceof HibernateValidatorConfiguration hibernateValidatorConfiguration) { + registerConstraintsInternal(hibernateValidatorConfiguration); + } + else { + log.warn("Unable to register validation configuration, automatic registration is only supported for hibernate validator"); + } + } + + protected void registerConstraintsInternal(HibernateValidatorConfiguration configuration) { + org.reflections.Configuration reflectionsConfiguration = new ConfigurationBuilder() + .forPackages(constraintPacakgeList.toArray(new String[0])) + .setScanners(Scanners.SubTypes); + + @SuppressWarnings("rawtypes") + Set> constraintValidators = new Reflections(reflectionsConfiguration).getSubTypesOf(ConstraintValidator.class); + + constraintValidators.forEach(validatorClass -> { + @SuppressWarnings("unchecked") + Class> castedValidatorClass = (Class>) validatorClass; + Class annotationClass = annotationClass(validatorClass); + + registerConstraint(configuration, castedValidatorClass, annotationClass); + }); + } + + @SuppressWarnings("unchecked") + private Class annotationClass(Class type) { + ParameterizedType parameterizedType = (ParameterizedType) Arrays.stream(type.getGenericInterfaces()) + .filter(genericInterface -> ((ParameterizedType) genericInterface).getRawType().getTypeName().equals(ConstraintValidator.class.getName())) + .findFirst() + .orElseThrow(); + + return (Class) parameterizedType.getActualTypeArguments()[INDEX_OF_CONSTRAINT_TYPE]; + } + + private void registerConstraint(HibernateValidatorConfiguration hibernateValidatorConfiguration, Class> validator, Class annotationClass) { + ConstraintMapping constraintMapping = hibernateValidatorConfiguration.createConstraintMapping(); + + constraintMapping.constraintDefinition(annotationClass).validatedBy(validator); + + hibernateValidatorConfiguration.addMapping(constraintMapping); + } +} diff --git a/nrich-validation/src/main/resources/META-INF/validation.xml b/nrich-validation/src/main/resources/META-INF/validation.xml deleted file mode 100644 index 7b14cb6f1..000000000 --- a/nrich-validation/src/main/resources/META-INF/validation.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingContributor - diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/ValidationTestConfiguration.java b/nrich-validation/src/test/java/net/croz/nrich/validation/ValidationTestConfiguration.java index 36777c7a3..0224409e0 100644 --- a/nrich-validation/src/test/java/net/croz/nrich/validation/ValidationTestConfiguration.java +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/ValidationTestConfiguration.java @@ -17,6 +17,8 @@ package net.croz.nrich.validation; +import net.croz.nrich.validation.api.mapping.ConstraintMappingRegistrar; +import net.croz.nrich.validation.constraint.mapping.DefaultConstraintMappingRegistrar; import net.croz.nrich.validation.constraint.stub.NullWhenTestService; import net.croz.nrich.validation.constraint.stub.SpelValidationTestService; import org.springframework.context.annotation.Bean; @@ -24,21 +26,36 @@ import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import java.util.List; + @Configuration(proxyBeanMethods = false) public class ValidationTestConfiguration { @Bean - public Validator validator() { - return new LocalValidatorFactoryBean(); + Validator validator(ConstraintMappingRegistrar constraintMappingRegistrar) { + LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean(); + + registerConstraintMappingContributors(localValidatorFactoryBean, constraintMappingRegistrar); + + return localValidatorFactoryBean; } @Bean - public NullWhenTestService nullWhenTestService() { + NullWhenTestService nullWhenTestService() { return new NullWhenTestService(); } @Bean - public SpelValidationTestService spelValidationTestService() { + SpelValidationTestService spelValidationTestService() { return new SpelValidationTestService(); } + + @Bean + ConstraintMappingRegistrar constraintMappingRegistrar() { + return new DefaultConstraintMappingRegistrar(List.of("net.croz.nrich.validation.constraint.validator")); + } + + private void registerConstraintMappingContributors(LocalValidatorFactoryBean validator, ConstraintMappingRegistrar constraintMappingRegistrar) { + validator.setConfigurationInitializer(constraintMappingRegistrar::registerConstraints); + } } diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributorTest.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributorTest.java new file mode 100644 index 000000000..40cf74da4 --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingContributorTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.mapping; + +import net.croz.nrich.validation.api.constraint.InList; +import net.croz.nrich.validation.api.constraint.MaxSizeInBytes; +import net.croz.nrich.validation.api.constraint.NotNullWhen; +import net.croz.nrich.validation.api.constraint.NullWhen; +import net.croz.nrich.validation.api.constraint.SpelExpression; +import net.croz.nrich.validation.api.constraint.ValidFile; +import net.croz.nrich.validation.api.constraint.ValidFileResolvable; +import net.croz.nrich.validation.api.constraint.ValidOib; +import net.croz.nrich.validation.api.constraint.ValidRange; +import net.croz.nrich.validation.api.constraint.ValidSearchProperties; +import net.croz.nrich.validation.constraint.validator.InListValidator; +import net.croz.nrich.validation.constraint.validator.MaxSizeInBytesValidator; +import net.croz.nrich.validation.constraint.validator.NotNullWhenValidator; +import net.croz.nrich.validation.constraint.validator.NullWhenValidator; +import net.croz.nrich.validation.constraint.validator.SpelExpressionValidator; +import net.croz.nrich.validation.constraint.validator.ValidFileResolvableValidator; +import net.croz.nrich.validation.constraint.validator.ValidFileValidator; +import net.croz.nrich.validation.constraint.validator.ValidOibValidator; +import net.croz.nrich.validation.constraint.validator.ValidRangeValidator; +import net.croz.nrich.validation.constraint.validator.ValidSearchPropertiesValidator; +import org.hibernate.validator.cfg.ConstraintMapping; +import org.hibernate.validator.spi.cfg.ConstraintMappingContributor; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class DefaultConstraintMappingContributorTest { + + @Mock + private ConstraintMappingContributor.ConstraintMappingBuilder constraintMappingBuilder; + + @InjectMocks + private DefaultConstraintMappingContributor defaultConstraintMappingContributor; + + @Test + void shouldRegisterConstraints() { + // given + ConstraintMapping constraintMapping = mock(ConstraintMapping.class, Mockito.RETURNS_DEEP_STUBS); + + doReturn(constraintMapping).when(constraintMappingBuilder).addConstraintMapping(); + + // when + defaultConstraintMappingContributor.createConstraintMappings(constraintMappingBuilder); + + // then + verify(constraintMapping.constraintDefinition(ValidOib.class)).validatedBy(ValidOibValidator.class); + verify(constraintMapping.constraintDefinition(ValidSearchProperties.class)).validatedBy(ValidSearchPropertiesValidator.class); + verify(constraintMapping.constraintDefinition(ValidRange.class)).validatedBy(ValidRangeValidator.class); + verify(constraintMapping.constraintDefinition(MaxSizeInBytes.class)).validatedBy(MaxSizeInBytesValidator.class); + verify(constraintMapping.constraintDefinition(NotNullWhen.class)).validatedBy(NotNullWhenValidator.class); + verify(constraintMapping.constraintDefinition(NullWhen.class)).validatedBy(NullWhenValidator.class); + verify(constraintMapping.constraintDefinition(ValidFile.class)).validatedBy(ValidFileValidator.class); + verify(constraintMapping.constraintDefinition(ValidFileResolvable.class)).validatedBy(ValidFileResolvableValidator.class); + verify(constraintMapping.constraintDefinition(InList.class)).validatedBy(InListValidator.class); + verify(constraintMapping.constraintDefinition(SpelExpression.class)).validatedBy(SpelExpressionValidator.class); + } +} diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrarTest.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrarTest.java new file mode 100644 index 000000000..8c885b13c --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/DefaultConstraintMappingRegistrarTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.mapping; + +import net.croz.nrich.validation.constraint.mapping.stub.DefaultConstraintMappingRegistrarConstraint; +import net.croz.nrich.validation.constraint.mapping.stub.DefaultConstraintMappingRegistrarConstraintValidator; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cfg.ConstraintMapping; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import jakarta.validation.Configuration; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +class DefaultConstraintMappingRegistrarTest { + + private final DefaultConstraintMappingRegistrar defaultConstraintMappingRegistrar = new DefaultConstraintMappingRegistrar(List.of("net.croz.nrich.validation.constraint.mapping.stub")); + + @Test + void shouldNotFailOnNonHibernateConfigurationInstance() { + // given + Configuration configuration = mock(Configuration.class); + + // when + Throwable thrown = catchThrowable(() -> defaultConstraintMappingRegistrar.registerConstraints(configuration)); + + // then + assertThat(thrown).isNull(); + verifyNoInteractions(configuration); + } + + @Test + void shouldRegisterConstraints() { + // given + HibernateValidatorConfiguration configuration = mock(HibernateValidatorConfiguration.class); + ConstraintMapping constraintMapping = mock(ConstraintMapping.class, Mockito.RETURNS_DEEP_STUBS); + + doReturn(constraintMapping).when(configuration).createConstraintMapping(); + + // when + defaultConstraintMappingRegistrar.registerConstraints(configuration); + + // then + verify(constraintMapping.constraintDefinition(DefaultConstraintMappingRegistrarConstraint.class)).validatedBy(DefaultConstraintMappingRegistrarConstraintValidator.class); + } +} diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraint.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraint.java new file mode 100644 index 000000000..3baf86f25 --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraint.java @@ -0,0 +1,47 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.mapping.stub; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +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.ElementType.TYPE; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE, TYPE }) +@Retention(RUNTIME) +@Documented +@Constraint(validatedBy = {}) +public @interface DefaultConstraintMappingRegistrarConstraint { + + String message() default ""; + + Class[] groups() default {}; + + Class[] payload() default {}; + +} diff --git a/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraintValidator.java b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraintValidator.java new file mode 100644 index 000000000..f5a999713 --- /dev/null +++ b/nrich-validation/src/test/java/net/croz/nrich/validation/constraint/mapping/stub/DefaultConstraintMappingRegistrarConstraintValidator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2020-2023 CROZ d.o.o, the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.croz.nrich.validation.constraint.mapping.stub; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class DefaultConstraintMappingRegistrarConstraintValidator implements ConstraintValidator { + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + return false; + } +}