From e91c6615c37f14afed9f9af2e214ad5411082e2c Mon Sep 17 00:00:00 2001 From: Denis Stepanov Date: Thu, 2 Mar 2023 10:15:15 -0500 Subject: [PATCH] Migrate to Jakarta and add TCK --- ...naut.build.internal.validation-base.gradle | 10 +- gradle/libs.versions.toml | 7 +- settings.gradle | 4 + .../docs/guide/additionalConstraints.adoc | 2 +- .../docs/guide/compileTimeValidation.adoc | 2 +- .../docs/guide/dataClassesValidation.adoc | 2 +- src/main/docs/guide/methodsValidation.adoc | 4 +- src/main/docs/guide/quickStart.adoc | 6 +- .../micronaut/docs/validation/Person.groovy | 4 +- .../docs/validation/PersonService.groovy | 2 +- .../docs/validation/PersonServiceSpec.groovy | 2 +- .../validation/custom/DurationPattern.groovy | 2 +- .../DurationPatternValidatorSpec.groovy | 2 +- .../validation/custom/HolidayService.groovy | 2 +- .../docs/validation/pojo/PersonService.groovy | 2 +- .../validation/pojo/PersonServiceSpec.groovy | 2 +- .../micronaut/docs/ioc/validation/Person.kt | 6 +- .../docs/ioc/validation/PersonService.kt | 2 +- .../docs/ioc/validation/PersonServiceSpec.kt | 2 +- .../ioc/validation/custom/DurationPattern.kt | 2 +- .../custom/DurationPatternValidatorSpec.kt | 2 +- .../ioc/validation/custom/HolidayService.kt | 2 +- .../docs/ioc/validation/pojo/PersonService.kt | 2 +- .../ioc/validation/pojo/PersonServiceSpec.kt | 2 +- .../io/micronaut/docs/validation/Person.java | 4 +- .../docs/validation/PersonService.java | 2 +- .../docs/validation/PersonServiceSpec.java | 2 +- .../validation/custom/DurationPattern.java | 2 +- .../custom/DurationPatternValidatorSpec.java | 4 +- .../validation/custom/HolidayService.java | 2 +- .../docs/validation/iterable/BookInfo.java | 4 +- .../validation/iterable/BookInfoService.java | 4 +- .../validation/iterable/BookInfoSpec.java | 2 +- .../docs/validation/pojo/PersonService.java | 2 +- .../validation/pojo/PersonServiceSpec.java | 4 +- tests/jakarta-validation-tck/build.gradle | 61 + .../tck/ArchiveCompilationException.java | 31 + .../validation/tck/ArchiveCompiler.java | 222 ++++ .../tck/ArchiveCompilerException.java | 34 + .../validation/tck/DeploymentClassLoader.java | 91 ++ .../validation/tck/DeploymentDir.java | 38 + .../tck/TckContainerConfiguration.java | 27 + .../tck/TckDeployableContainer.java | 203 ++++ .../validation/tck/TckExtension.java | 39 + .../micronaut/validation/tck/TckProtocol.java | 153 +++ .../tck/runtime/TckValidationProvider.java | 44 + .../runtime/TckValidatorConfiguration.java | 130 +++ .../tck/runtime/TestClassVisitor.java | 61 + .../resources/META-INF/cdi-tck.properties | 4 + ...icronaut.inject.visitor.TypeElementVisitor | 1 + ...boss.arquillian.core.spi.LoadableExtension | 1 + .../src/main/resources/arquillian.xml | 15 + .../src/main/resources/logback.xml | 17 + .../src/test/java/test/DummyTest.java | 8 + tests/jakarta-validation-tck/tck-tests.xml | 44 + .../IntrospectedValidationIndexesVisitor.java | 4 +- .../validation/visitor/ValidationVisitor.java | 10 +- .../visitor/ValidatedParseSpec.groovy | 20 +- .../visitor/ValidatedParseSpecGroovy.groovy | 8 +- .../validation/ValidatingInterceptor.java | 10 +- .../ConstraintExceptionHandler.java | 8 +- .../DefaultAnnotatedElementValidator.java | 22 +- .../validator/DefaultClockProvider.java | 2 +- .../DefaultConstraintDescriptor.java | 10 +- .../validator/DefaultValidator.java | 831 ++++++-------- .../DefaultValidatorConfiguration.java | 137 ++- .../validator/DefaultValidatorFactory.java | 35 +- .../validator/ExecutableMethodValidator.java | 6 +- .../validator/IntrospectedBeanDescriptor.java | 6 +- .../validator/ReactiveValidator.java | 4 +- .../validation/validator/Validator.java | 10 +- .../validator/ValidatorConfiguration.java | 12 +- .../constraints/AbstractPatternValidator.java | 4 +- .../constraints/ConstraintValidator.java | 10 +- .../ConstraintValidatorContext.java | 8 +- .../ConstraintValidatorRegistry.java | 2 +- .../constraints/DecimalMaxValidator.java | 4 +- .../constraints/DecimalMinValidator.java | 4 +- .../DefaultConstraintValidators.java | 1009 ++--------------- .../constraints/DigitsValidator.java | 2 +- .../validator/constraints/EmailValidator.java | 2 +- .../InternalConstraintValidators.java | 480 ++++++++ .../constraints/PatternValidator.java | 2 +- .../validator/constraints/SizeValidator.java | 2 +- .../extractors/DefaultValueExtractors.java | 260 +---- .../extractors/InternalValueExtractors.java | 90 ++ .../extractors/SimpleValueReceiver.java | 2 +- .../UnwrapByDefaultValueExtractor.java | 2 +- .../extractors/ValueExtractorRegistry.java | 21 +- .../messages/DefaultValidationMessages.java | 2 +- .../CompositeTraversableResolver.java | 4 +- .../validation/BeanIntrospectionSpec.groovy | 76 +- .../validation/BookmarkController.java | 8 +- .../groovy/io/micronaut/validation/Foo.java | 6 +- ...mmutableConfigurationPropertiesSpec.groovy | 4 +- ...nterfaceConfigurationPropertiesSpec.groovy | 22 +- .../io/micronaut/validation/JavaClient.java | 2 +- .../io/micronaut/validation/MyConfig.java | 2 +- .../io/micronaut/validation/MyEachConfig.java | 2 +- .../validation/PaginationCommand.java | 6 +- .../groovy/io/micronaut/validation/Pojo.java | 4 +- .../validation/PojoNoIntrospection.java | 4 +- .../micronaut/validation/ValidatedConfig.java | 4 +- .../ValidatedConfigurationSpec.groovy | 4 +- .../validation/ValidatedController.java | 8 +- .../validation/ValidatedGetterConfig.java | 4 +- .../micronaut/validation/ValidatedSpec.groovy | 14 +- .../WebSocketClientValidationSpec.groovy | 6 +- .../WebSocketValidationServerHandler.groovy | 4 +- .../properties/WriteOnlyConfigProperties.java | 2 +- .../io/micronaut/validation/records/Test.java | 2 +- .../micronaut/validation/records/Test2.java | 2 +- .../ConstraintValidatorRegistrySpec.groovy | 2 +- .../validator/ValidatorGroupsSpec.groovy | 17 +- .../validation/validator/ValidatorSpec.groovy | 103 +- .../validator/ValidatorSpecClasses.java | 10 +- .../validation/validator/ast/SomeAnn.java | 2 +- .../ast/ValidateAnnotationMetadataSpec.groovy | 3 +- .../constraints/ConstraintMessagesSpec.groovy | 11 +- .../constraints/ConstraintsSpec.groovy | 6 +- .../custom/AlwaysInvalidConstraint.java | 2 +- .../custom/CustomConstraintsSpec.groovy | 2 +- .../custom/CustomMessageConstraint.java | 2 +- .../EmployeeExperienceConstraint.java | 2 +- .../EmployeeValidationsSpec.groovy | 12 +- .../validation/validator/map/Author.java | 2 +- .../validation/validator/map/Book.java | 2 +- .../validator/map/MapValidatorSpec.groovy | 6 +- .../validator/pojo/FavoriteWebsSpec.groovy | 4 +- .../pojo/NameAndLastNameValidator.java | 2 +- .../pojo/PojoBodyParameterSpec.groovy | 10 +- .../validator/pojo/PojoConfigProps.java | 2 +- .../PojoConfigurationPropertiesSpec.groovy | 6 +- .../validator/pojo/PojoValidatorSpec.groovy | 6 +- .../validation/validator/pojo/ValidURLs.java | 2 +- .../validation/validator/reactive/Book.java | 2 +- .../validator/reactive/BookService.java | 4 +- .../reactive/BookServiceRxJava2.java | 4 +- .../ReactiveMethodValidationSpec.groovy | 22 +- .../RxJava2MethodValidationSpec.groovy | 19 +- 140 files changed, 2780 insertions(+), 2049 deletions(-) create mode 100644 tests/jakarta-validation-tck/build.gradle create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilationException.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompiler.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilerException.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentClassLoader.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentDir.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckContainerConfiguration.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckDeployableContainer.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckExtension.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckProtocol.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidationProvider.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidatorConfiguration.java create mode 100644 tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TestClassVisitor.java create mode 100644 tests/jakarta-validation-tck/src/main/resources/META-INF/cdi-tck.properties create mode 100644 tests/jakarta-validation-tck/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor create mode 100644 tests/jakarta-validation-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension create mode 100644 tests/jakarta-validation-tck/src/main/resources/arquillian.xml create mode 100644 tests/jakarta-validation-tck/src/main/resources/logback.xml create mode 100644 tests/jakarta-validation-tck/src/test/java/test/DummyTest.java create mode 100644 tests/jakarta-validation-tck/tck-tests.xml create mode 100644 validation/src/main/java/io/micronaut/validation/validator/constraints/InternalConstraintValidators.java create mode 100644 validation/src/main/java/io/micronaut/validation/validator/extractors/InternalValueExtractors.java diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.validation-base.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.validation-base.gradle index 273de696..c00f8b2f 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.validation-base.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.validation-base.gradle @@ -1,7 +1,9 @@ // If you don't need any common settings/dependencies/... for everything, remove this convention plugin and the reference to it in `io.micronaut.build.internal.project-template-module.gradle` file repositories { - repositories { - mavenCentral() - maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } - } + mavenCentral() + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" } +} + +configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b368a954..41055e4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ kotlin = "1.7.20" kotest = "5.5.5" spotbugs = "4.7.3" -managed-validation = "2.0.1.Final" +managed-validation = "3.0.2" # Gradle plugins @@ -21,6 +21,8 @@ micronaut-gradle-plugin = "3.7.4" kotlin-gradle-plugin = "1.8.10" ksp-gradle-plugin = "1.8.10-1.0.9" +jakarta-validation-tck = "3.0.1" + [libraries] # BOMS @@ -41,7 +43,8 @@ spotbugs = { module = "com.github.spotbugs:spotbugs-annotations", version.ref = # MANAGED DEPENDENCIES -managed-validation = { module = "javax.validation:validation-api", version.ref = "managed-validation" } +managed-validation = { module = "jakarta.validation:jakarta.validation-api", version.ref = "managed-validation" } +jakarta-validation-tck-tests = { module = 'jakarta.validation:beanvalidation-tck-tests', version.ref = "jakarta-validation-tck" } # GRADLE PLUGINS diff --git a/settings.gradle b/settings.gradle index ef004068..486062c6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,8 @@ plugins { id 'io.micronaut.build.shared.settings' version '6.3.1' } +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + dependencyResolutionManagement { repositories { mavenCentral() @@ -25,6 +27,8 @@ include 'test-suite' include 'test-suite-groovy' include 'test-suite-kotlin' +include 'tests:jakarta-validation-tck' + micronautBuild { importMicronautCatalog() importMicronautCatalog("micronaut-reactor") diff --git a/src/main/docs/guide/additionalConstraints.adoc b/src/main/docs/guide/additionalConstraints.adoc index f0ba2599..ec193356 100644 --- a/src/main/docs/guide/additionalConstraints.adoc +++ b/src/main/docs/guide/additionalConstraints.adoc @@ -4,7 +4,7 @@ To define additional constraints, create a new annotation, for example: .Example Constraint Annotation snippet::io.micronaut.docs.validation.custom.DurationPattern[tags="imports,class", indent="0"] -<1> The annotation should be annotated with `javax.validation.Constraint` +<1> The annotation should be annotated with `jakarta.validation.Constraint` <2> A `message` template can be provided in a hard-coded manner as above. If none is specified, Micronaut tries to find a message using `ClassName.message` using the api:context.MessageSource[] interface (optional) <3> To support repeated annotations you can define an inner annotation (optional) diff --git a/src/main/docs/guide/compileTimeValidation.adoc b/src/main/docs/guide/compileTimeValidation.adoc index 562320ab..70e9783e 100644 --- a/src/main/docs/guide/compileTimeValidation.adoc +++ b/src/main/docs/guide/compileTimeValidation.adoc @@ -2,7 +2,7 @@ Micronaut Validation validates annotation elements at compile time with `microna dependency:micronaut-validation-processor[groupId="io.micronaut.validation",scope="annotationProcessor"] -Micronaut Validation will, at compile time, validate annotation values that are themselves annotated with `javax.validation`. +Micronaut Validation will, at compile time, validate annotation values that are themselves annotated with `jakarta.validation`. For example consider the following annotation: .Annotation Validation diff --git a/src/main/docs/guide/dataClassesValidation.adoc b/src/main/docs/guide/dataClassesValidation.adoc index a3e36a4a..e88ab90b 100644 --- a/src/main/docs/guide/dataClassesValidation.adoc +++ b/src/main/docs/guide/dataClassesValidation.adoc @@ -16,7 +16,7 @@ snippet::io.micronaut.docs.validation.pojo.PersonServiceSpec[tags="validator", i <1> The validator validates the person <2> The constraint violations are verified -Alternatively on Bean methods you can use `javax.validation.Valid` to trigger cascading validation: +Alternatively on Bean methods you can use `jakarta.validation.Valid` to trigger cascading validation: .ConstraintViolationException Example snippet::io.micronaut.docs.validation.pojo.PersonService[tags="class",indent="0"] diff --git a/src/main/docs/guide/methodsValidation.adoc b/src/main/docs/guide/methodsValidation.adoc index 726288e2..7e863c3e 100644 --- a/src/main/docs/guide/methodsValidation.adoc +++ b/src/main/docs/guide/methodsValidation.adoc @@ -1,4 +1,4 @@ -You can validate methods of any class declared as a Micronaut bean by applying `javax.validation` annotations to arguments: +You can validate methods of any class declared as a Micronaut bean by applying `jakarta.validation` annotations to arguments: .Validating Methods snippet::io.micronaut.docs.validation.PersonService[tags="imports,class",indent=0] @@ -7,7 +7,7 @@ The above example declares that the `@NotBlank` annotation will be validated whe WARNING: If you use Kotlin, the class and method must be declared `open` so Micronaut can create a compile-time subclass. Alternatively you can annotate the class with ann:validation.Validated[] and configure the Kotlin `all-open` plugin to open classes annotated with this type. See the https://kotlinlang.org/docs/reference/compiler-plugins.html[Compiler plugins] section. -A `javax.validation.ConstraintViolationException` is thrown if a validation error occurs. For example: +A `jakarta.validation.ConstraintViolationException` is thrown if a validation error occurs. For example: .ConstraintViolationException Example snippet::io.micronaut.docs.validation.PersonServiceSpec[tags="imports,test",indent=0] diff --git a/src/main/docs/guide/quickStart.adoc b/src/main/docs/guide/quickStart.adoc index e2062269..96014d50 100644 --- a/src/main/docs/guide/quickStart.adoc +++ b/src/main/docs/guide/quickStart.adoc @@ -4,9 +4,9 @@ dependency:micronaut-validation-processor[scope="annotationProcessor",groupId="i dependency:micronaut-validation[groupId="io.micronaut.validation"] -You can validate types, fields and parameters by applying `javax.validation` annotations to arguments. Include the following dependency for annotations: +You can validate types, fields and parameters by applying `jakarta.validation` annotations to arguments. Include the following dependency for annotations: -dependency:javax.validation:validation-api[] +dependency:jakarta.validation:validation-api[] == Supported Features @@ -16,7 +16,7 @@ The following features are unsupported at this time: * Any interaction with the https://beanvalidation.org/2.0/spec/#constraintmetadata[constraint metadata API], since Micronaut uses compile-time generated metadata. * XML-based configuration -* Instead of using `javax.validation.ConstraintValidator`, use api:validation.validator.constraints.ConstraintValidator[] (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations at compile time. +* Instead of using `jakarta.validation.ConstraintValidator`, use api:validation.validator.constraints.ConstraintValidator[] (io.micronaut.validation.validator.constraints.ConstraintValidator) to define custom constraints, which supports validating annotations at compile time. Micronaut's implementation includes the following benefits: diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/Person.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/Person.groovy index 2224fdb0..8e967624 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/Person.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/Person.groovy @@ -18,8 +18,8 @@ package io.micronaut.docs.validation // tag::class[] import io.micronaut.core.annotation.Introspected -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank @Introspected class Person { diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonService.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonService.groovy index e8fff3cd..b13b8e33 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonService.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonService.groovy @@ -17,7 +17,7 @@ package io.micronaut.docs.validation // tag::imports[] import jakarta.inject.Singleton -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank // end::imports[] // tag::class[] diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonServiceSpec.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonServiceSpec.groovy index d5cce212..aa1a12b7 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonServiceSpec.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/PersonServiceSpec.groovy @@ -5,7 +5,7 @@ import io.micronaut.test.extensions.spock.annotation.MicronautTest import spock.lang.Specification import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException // end::imports[] // tag::test[] diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPattern.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPattern.groovy index b11ad658..f8a6ff83 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPattern.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPattern.groovy @@ -16,7 +16,7 @@ package io.micronaut.docs.validation.custom // tag::imports[] -import javax.validation.Constraint +import jakarta.validation.Constraint import java.lang.annotation.Retention import static java.lang.annotation.RetentionPolicy.RUNTIME diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.groovy index 97ce9fab..2ae7a1e7 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.groovy @@ -4,7 +4,7 @@ import io.micronaut.test.extensions.spock.annotation.MicronautTest import spock.lang.Specification import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException @MicronautTest class DurationPatternValidatorSpec extends Specification { diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/HolidayService.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/HolidayService.groovy index 2bef82ce..165078cf 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/HolidayService.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/custom/HolidayService.groovy @@ -16,7 +16,7 @@ package io.micronaut.docs.validation.custom import jakarta.inject.Singleton -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank import java.time.Duration // tag::class[] diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonService.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonService.groovy index 845a5923..b448d3a8 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonService.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonService.groovy @@ -19,7 +19,7 @@ package io.micronaut.docs.validation.pojo import io.micronaut.docs.validation.Person import jakarta.inject.Singleton -import javax.validation.Valid +import jakarta.validation.Valid // end::imports[] // tag::class[] diff --git a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonServiceSpec.groovy b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonServiceSpec.groovy index 90f6aa23..29f56e0f 100644 --- a/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonServiceSpec.groovy +++ b/test-suite-groovy/src/test/groovy/io/micronaut/docs/validation/pojo/PersonServiceSpec.groovy @@ -8,7 +8,7 @@ import io.micronaut.validation.validator.Validator import spock.lang.Specification import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException // end::imports[] // tag::test[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/Person.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/Person.kt index b869cf98..6af4f23b 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/Person.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/Person.kt @@ -17,12 +17,12 @@ package io.micronaut.docs.ioc.validation // tag::class[] import io.micronaut.core.annotation.Introspected -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank @Introspected data class Person( @field:NotBlank var name: String, @field:Min(18) var age: Int ) -// end::class[] \ No newline at end of file +// end::class[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonService.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonService.kt index dd1e2e9c..c767e1d3 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonService.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonService.kt @@ -17,7 +17,7 @@ package io.micronaut.docs.ioc.validation // tag::imports[] import jakarta.inject.Singleton -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank // end::imports[] // tag::class[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonServiceSpec.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonServiceSpec.kt index c248f7c2..c764e8e0 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonServiceSpec.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/PersonServiceSpec.kt @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException // end::imports[] // tag::test[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPattern.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPattern.kt index f166300a..0c474144 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPattern.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPattern.kt @@ -16,7 +16,7 @@ package io.micronaut.docs.ioc.validation.custom // tag::imports[] -import javax.validation.Constraint +import jakarta.validation.Constraint import kotlin.annotation.AnnotationRetention.RUNTIME // end::imports[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPatternValidatorSpec.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPatternValidatorSpec.kt index f8a6b0e2..6c7ab8c1 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPatternValidatorSpec.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/DurationPatternValidatorSpec.kt @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException @MicronautTest internal class DurationPatternValidatorSpec { diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/HolidayService.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/HolidayService.kt index 8e70b21d..e43d19ca 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/HolidayService.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/custom/HolidayService.kt @@ -17,7 +17,7 @@ package io.micronaut.docs.ioc.validation.custom import java.time.Duration import jakarta.inject.Singleton -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank // tag::class[] @Singleton diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonService.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonService.kt index 9e463115..3856aed9 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonService.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonService.kt @@ -19,7 +19,7 @@ package io.micronaut.docs.ioc.validation.pojo import io.micronaut.docs.ioc.validation.Person import jakarta.inject.Singleton -import javax.validation.Valid +import jakarta.validation.Valid // end::imports[] diff --git a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonServiceSpec.kt b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonServiceSpec.kt index 7e9932ad..588b5db4 100644 --- a/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonServiceSpec.kt +++ b/test-suite-kotlin/src/test/kotlin/io/micronaut/docs/ioc/validation/pojo/PersonServiceSpec.kt @@ -8,7 +8,7 @@ import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test import jakarta.inject.Inject -import javax.validation.ConstraintViolationException +import jakarta.validation.ConstraintViolationException // end::imports[] // tag::test[] diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/Person.java b/test-suite/src/test/java/io/micronaut/docs/validation/Person.java index 346e6087..f8747109 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/Person.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/Person.java @@ -18,8 +18,8 @@ // tag::class[] import io.micronaut.core.annotation.Introspected; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; @Introspected public class Person { diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/PersonService.java b/test-suite/src/test/java/io/micronaut/docs/validation/PersonService.java index cf84cf9b..80bd7fa9 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/PersonService.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/PersonService.java @@ -19,7 +19,7 @@ import jakarta.inject.Singleton; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; // end::imports[] // tag::class[] diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/PersonServiceSpec.java b/test-suite/src/test/java/io/micronaut/docs/validation/PersonServiceSpec.java index b26b00e3..5a4fa9d4 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/PersonServiceSpec.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/PersonServiceSpec.java @@ -21,7 +21,7 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import javax.validation.ConstraintViolationException; +import jakarta.validation.ConstraintViolationException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPattern.java b/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPattern.java index f6ee4b1a..6d7f98ea 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPattern.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPattern.java @@ -16,7 +16,7 @@ package io.micronaut.docs.validation.custom; // tag::imports[] -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.java b/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.java index b874379c..75ad9bd3 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/custom/DurationPatternValidatorSpec.java @@ -20,8 +20,8 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/custom/HolidayService.java b/test-suite/src/test/java/io/micronaut/docs/validation/custom/HolidayService.java index e6307ae5..54d42b72 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/custom/HolidayService.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/custom/HolidayService.java @@ -18,7 +18,7 @@ import io.micronaut.context.annotation.Executable; import jakarta.inject.Singleton; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import java.time.Duration; // tag::class[] diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfo.java b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfo.java index 5980f632..f07a2196 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfo.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfo.java @@ -5,8 +5,8 @@ // tag::object[] -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; public class BookInfo { private List<@NotBlank String> authors; // <1> diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoService.java b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoService.java index a56520a9..004192e2 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoService.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoService.java @@ -2,8 +2,8 @@ import jakarta.inject.Singleton; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import java.util.List; import java.util.Map; diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoSpec.java b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoSpec.java index 58db2710..efae047b 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoSpec.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/iterable/BookInfoSpec.java @@ -6,7 +6,7 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import javax.validation.ConstraintViolationException; +import jakarta.validation.ConstraintViolationException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonService.java b/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonService.java index 19664712..c8626760 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonService.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonService.java @@ -21,7 +21,7 @@ import io.micronaut.docs.validation.Person; import jakarta.inject.Singleton; -import javax.validation.Valid; +import jakarta.validation.Valid; // end::imports[] // tag::class[] diff --git a/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonServiceSpec.java b/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonServiceSpec.java index 75a28371..20d6c06b 100644 --- a/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonServiceSpec.java +++ b/test-suite/src/test/java/io/micronaut/docs/validation/pojo/PersonServiceSpec.java @@ -23,8 +23,8 @@ import jakarta.inject.Inject; import org.junit.jupiter.api.Test; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/tests/jakarta-validation-tck/build.gradle b/tests/jakarta-validation-tck/build.gradle new file mode 100644 index 00000000..33b6b6cc --- /dev/null +++ b/tests/jakarta-validation-tck/build.gradle @@ -0,0 +1,61 @@ +plugins { + id "java-library" + id "io.micronaut.build.internal.validation-base" +} + +dependencies { + implementation mn.logback.classic + implementation mn.micronaut.inject.java + implementation mn.micronaut.inject + implementation mn.micronaut.aop + implementation mn.micronaut.context + + implementation projects.validation + implementation projects.validationProcessor + implementation mnReactor.micronaut.reactor + + implementation 'org.jboss.arquillian.container:arquillian-container-test-spi:1.7.0.Alpha2' + implementation libs.jakarta.validation.tck.tests + testImplementation(libs.jakarta.validation.tck.tests) { + artifact { + classifier = "sources" + } + } + + testImplementation 'javax.annotation:javax.annotation-api:1.3.2' +} + +test { + scanForTestClasses false + useTestNG { + systemProperties = [ + 'validation.provider': 'io.micronaut.validation.tck.runtime.TckValidationProvider' + ] + suites file("tck-tests.xml") + } +} + +tasks.register('singleTest', Test) { + scanForTestClasses false + useTestNG { + suiteXmlBuilder().suite(name: 'Sample Suite') { + test(name : 'Sample Test') { + classes('') { +// 'class'(name: 'org.hibernate.beanvalidation.tck.tests.methodvalidation.MethodValidationTest') { +// methods() { +// include(name: 'methodParameterValidationIncludesConstraintsFromImplementedInterface') +// } +// } + 'class'(name: 'org.hibernate.beanvalidation.tck.tests.constraints.builtinconstraints.NotEmptyConstraintTest') { + methods() { + include(name: 'testNotEmptyConstraint') + } + } + } + } + } + systemProperties = [ + 'validation.provider': 'io.micronaut.validation.tck.runtime.TckValidationProvider' + ] + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilationException.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilationException.java new file mode 100644 index 00000000..3772fa4c --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilationException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; + +/** + * The exception indicates compilation error. + * + * @author Denis Stepanov + */ +@Internal +public final class ArchiveCompilationException extends Exception { + + public ArchiveCompilationException(String message) { + super(message); + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompiler.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompiler.java new file mode 100644 index 00000000..68615f1a --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompiler.java @@ -0,0 +1,222 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.annotation.processing.AggregatingTypeElementVisitorProcessor; +import io.micronaut.annotation.processing.BeanDefinitionInjectProcessor; +import io.micronaut.annotation.processing.PackageConfigurationInjectProcessor; +import io.micronaut.annotation.processing.TypeElementVisitorProcessor; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.Introspected; +import org.hibernate.beanvalidation.tck.tests.constraints.containerelement.ContainerElementConstraintCustomContainerTest; +import org.hibernate.beanvalidation.tck.tests.constraints.containerelement.ContainerElementConstraintMapKeyTest; +import org.hibernate.beanvalidation.tck.tests.constraints.containerelement.ContainerElementConstraintMapValueTest; +import org.hibernate.beanvalidation.tck.tests.constraints.containerelement.NestedContainerElementConstraintsTest; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePath; +import org.jboss.shrinkwrap.api.Node; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +import javax.annotation.processing.Processor; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * IMPORTANT: assumes that it is possible to iterate through the {@code Archive} + * and for each {@code .class} file in there, find a corresponding {@code .java} + * file in this class's classloader. In other words, the CDI TCK source JAR must + * be on classpath. + */ +@Internal +final class ArchiveCompiler { + private final DeploymentDir deploymentDir; + private final Archive deploymentArchive; + + ArchiveCompiler(DeploymentDir deploymentDir, Archive deploymentArchive) { + this.deploymentDir = deploymentDir; + this.deploymentArchive = deploymentArchive; + } + + void compile() throws ArchiveCompilationException, ArchiveCompilerException { + try { + if (deploymentArchive instanceof WebArchive) { + compileWar(); + } else { + throw new ArchiveCompilerException("Unknown archive type: " + deploymentArchive); + } + } catch (IOException e) { + throw new ArchiveCompilerException("Compilation failed", e); + } + } + + private void compileWar() throws ArchiveCompilationException, ArchiveCompilerException, IOException { + List sourceFiles = new ArrayList<>(); + for (Map.Entry entry : deploymentArchive.getContent().entrySet()) { + String path = entry.getKey().get(); + if (path.startsWith("/WEB-INF/classes") && path.endsWith(".class")) { + String sourceFile = path.replace("/WEB-INF/classes", "") + .replace(".class", ".java"); + + if (sourceFile.contains("$") && !sourceFile.endsWith("$Dollar.java")) { + // skip nested classes + // + // special case for $Dollar, which is the only class in CDI TCK + // whose name actually intentionally contains '$' + // + // this is crude, maybe there's a better way? + continue; + } + + Path sourceFilePath = deploymentDir.source.resolve(sourceFile.substring(1)); // sourceFile begins with `/` + + sourceFiles.add(sourceFilePath.toFile()); + + Files.createDirectories(sourceFilePath.getParent()); // make sure the directory exists + try (InputStream in = ArchiveCompiler.class.getResourceAsStream(sourceFile)) { + if (in == null) { + throw new ArchiveCompilerException("Source file not found: " + sourceFile); + } + Files.copy(in, sourceFilePath); + } + } else if (path.startsWith("/WEB-INF/lib") && path.endsWith(".jar")) { + String jarFile = path.replace("/WEB-INF/lib", ""); + Path jarFilePath = deploymentDir.lib.resolve(jarFile.substring(1)); // jarFile begins with `/` + + Files.createDirectories(jarFilePath.getParent()); // make sure the directory exists + try (InputStream in = entry.getValue().getAsset().openStream()) { + Files.copy(in, jarFilePath); + } + } + } + + doCompile(sourceFiles, deploymentDir.target.toFile()); + } + + private void doCompile(Collection testSources, File outputDir) throws ArchiveCompilationException, IOException { + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + try (StandardJavaFileManager mgr = compiler.getStandardFileManager(diagnostics, null, null)) { + final String targetDir = outputDir.getAbsolutePath(); + JavaCompiler.CompilationTask task = compiler.getTask( + null, + mgr, + diagnostics, + Arrays.asList("-d", targetDir, "-verbose", "-parameters"), + null, + mgr.getJavaFileObjectsFromFiles( + Stream.concat( + testSources.stream(), + Stream.of( + applicationClass(testSources).toFile() + ) + ).toList() + ) + ); + task.setProcessors(getAnnotationProcessors()); + Boolean success = task.call(); + if (!Boolean.TRUE.equals(success)) { + outputDiagnostics(diagnostics); + } + } + } + + private Path applicationClass(Collection testSources) throws IOException { + List importBeans = new ArrayList<>(); + String annotations = ""; + String packageName = "org.hibernate.beanvalidation.tck.tests"; + if (testSources.stream().anyMatch(f -> f.getName().contains(ContainerElementConstraintMapKeyTest.class.getSimpleName()))) { + importBeans.add(ContainerElementConstraintMapKeyTest.class.getName() + "$TypeWithMap1"); + packageName = ContainerElementConstraintMapKeyTest.class.getPackageName(); + } + if (testSources.stream().anyMatch(f -> f.getName().contains(ContainerElementConstraintMapValueTest.class.getSimpleName()))) { + importBeans.add(ContainerElementConstraintMapValueTest.class.getName() + "$TypeWithMap1"); + packageName = ContainerElementConstraintMapValueTest.class.getPackageName(); + } + if (testSources.stream().anyMatch(f -> f.getName().contains(ContainerElementConstraintCustomContainerTest.class.getSimpleName()))) { + importBeans.add(ContainerElementConstraintCustomContainerTest.class.getName() + "$TypeWithCustomContainer1"); + packageName = ContainerElementConstraintCustomContainerTest.class.getPackageName(); + } + if (testSources.stream().anyMatch(f -> f.getName().contains(NestedContainerElementConstraintsTest.class.getSimpleName()))) { + importBeans.add(NestedContainerElementConstraintsTest.class.getName() + "$ListOfIterables"); + importBeans.add(NestedContainerElementConstraintsTest.class.getName() + "$ListOfMaps"); + importBeans.add(NestedContainerElementConstraintsTest.class.getName() + "$MapOfLists"); + importBeans.add(NestedContainerElementConstraintsTest.class.getName() + "$MapOfListsWithAutomaticUnwrapping"); + packageName = NestedContainerElementConstraintsTest.class.getPackageName(); + } + if (!importBeans.isEmpty()) { + annotations = "@" + Introspected.class.getName() + "(" + + "visibility = io.micronaut.core.annotation.Introspected.Visibility.ANY, " + + "accessKind = io.micronaut.core.annotation.Introspected.AccessKind.FIELD, " + + "classNames = {" + importBeans.stream().map(c -> "\"" + c + "\"").collect(Collectors.joining(", ")) + "}" + + ") "; + } + final Path packagePath = deploymentDir.target.resolve( + packageName.replace('.', '/') + ); + Files.createDirectories(packagePath); + final Path applicationSource = packagePath.resolve("Application.java"); + String sourceCode = "package " + packageName + ";\n" + + annotations + " class Application {}"; + Files.writeString(applicationSource, sourceCode); + return applicationSource; + } + + private void outputDiagnostics(DiagnosticCollector diagnostics) throws ArchiveCompilationException { + throw new ArchiveCompilationException("Compilation failed:\n" + diagnostics.getDiagnostics() + .stream() + .map(it -> { + System.out.println(it); + if (it.getSource() == null) { + return "- " + it.getMessage(Locale.US); + } + Path source = deploymentDir.source.relativize(Paths.get(it.getSource().toUri().getPath())); + return "- " + source + ":" + it.getLineNumber() + " " + it.getMessage(Locale.US); + }) + .collect(Collectors.joining("\n"))); + } + + private List getAnnotationProcessors() { + List result = new ArrayList<>(); + result.add(new TypeElementVisitorProcessor()); + result.add(new AggregatingTypeElementVisitorProcessor()); + result.add(new PackageConfigurationInjectProcessor()); + result.add(new BeanDefinitionInjectProcessor() { + @Override + protected boolean isProcessedAnnotation(String annotationName) { + return true; + } + }); + return result; + } + +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilerException.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilerException.java new file mode 100644 index 00000000..65ddbbcf --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/ArchiveCompilerException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; + +/** + * The exception indicates internal compiler error. + * + * @author Denis Stepanov + */ +@Internal +public final class ArchiveCompilerException extends Exception { + public ArchiveCompilerException(String message) { + super(message); + } + + public ArchiveCompilerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentClassLoader.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentClassLoader.java new file mode 100644 index 00000000..46263873 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentClassLoader.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Internal +final class DeploymentClassLoader extends URLClassLoader { + static { + ClassLoader.registerAsParallelCapable(); + } + + DeploymentClassLoader(DeploymentDir deploymentDir) throws IOException { + super(findUrls(deploymentDir)); + } + + private static URL[] findUrls(DeploymentDir deploymentDir) throws IOException { + List result = new ArrayList<>(); + + result.add(deploymentDir.target.toUri().toURL()); + + try (Stream stream = Files.walk(deploymentDir.lib)) { + List jars = stream.filter(p -> p.toString().endsWith(".jar")).collect(Collectors.toList()); + for (Path jar : jars) { + result.add(jar.toUri().toURL()); + } + } + + return result.toArray(new URL[0]); + } + + @Override + public InputStream getResourceAsStream(String name) { + return super.getResourceAsStream(name); + } + + @Override + public URL getResource(String name) { + return super.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + return super.getResources(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class clazz = findLoadedClass(name); + if (clazz != null) { + return clazz; + } + + try { + clazz = findClass(name); + if (resolve) { + resolveClass(clazz); + } + return clazz; + } catch (ClassNotFoundException ignored) { + return super.loadClass(name, resolve); + } + } + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentDir.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentDir.java new file mode 100644 index 00000000..4cfe9446 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/DeploymentDir.java @@ -0,0 +1,38 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@Internal +final class DeploymentDir { + final Path root; + final Path source; + final Path target; + final Path lib; + + DeploymentDir() throws IOException { + this.root = Files.createTempDirectory("odi-arquillian-"); + + this.source = Files.createDirectory(root.resolve("source")); + this.target = Files.createDirectory(root.resolve("target")); + this.lib = Files.createDirectory(root.resolve("lib")); + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckContainerConfiguration.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckContainerConfiguration.java new file mode 100644 index 00000000..18a75eae --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckContainerConfiguration.java @@ -0,0 +1,27 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; +import org.jboss.arquillian.container.spi.ConfigurationException; +import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration; + +@Internal +final class TckContainerConfiguration implements ContainerConfiguration { + @Override + public void validate() throws ConfigurationException { + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckDeployableContainer.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckDeployableContainer.java new file mode 100644 index 00000000..db0ecab7 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckDeployableContainer.java @@ -0,0 +1,203 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.core.annotation.Internal; +import io.micronaut.validation.tck.runtime.TestClassVisitor; +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; +import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; +import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; +import org.jboss.arquillian.core.api.Instance; +import org.jboss.arquillian.core.api.InstanceProducer; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.container.LibraryContainer; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.descriptor.api.Descriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Objects; + +@Internal +public final class TckDeployableContainer implements DeployableContainer { + + private static final Logger LOGGER = LoggerFactory.getLogger(TckDeployableContainer.class); + + static ClassLoader old; + + public static ThreadLocal APP = new ThreadLocal<>(); + + @Inject + @DeploymentScoped + private InstanceProducer runningApplicationContext; + + @Inject + @DeploymentScoped + private InstanceProducer applicationClassLoader; + + @Inject + @DeploymentScoped + private InstanceProducer deploymentDir; + + @Inject + private Instance testClass; + + static Object testInstance; + + @Override + public void deploy(Descriptor descriptor) { + throw new UnsupportedOperationException("Container does not support deployment of Descriptors"); + + } + + @Override + public void undeploy(Descriptor descriptor) { + throw new UnsupportedOperationException("Container does not support deployment of Descriptors"); + + } + + @Override + public Class getConfigurationClass() { + return TckContainerConfiguration.class; + } + + @Override + public void setup(TckContainerConfiguration configuration) { + } + + @Override + public void start() { + } + + @Override + public void stop() { + } + + @Override + public ProtocolDescription getDefaultProtocol() { + return new ProtocolDescription("Micronaut"); + } + + private static JavaArchive buildSupportLibrary() { + JavaArchive supportLib = ShrinkWrap.create(JavaArchive.class, "micronaut-validation-tck-support.jar") + .addAsManifestResource("META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor") + .addPackage(TestClassVisitor.class.getPackage()); + return supportLib; + } + + @Override + public ProtocolMetaData deploy(Archive archive) { + if (archive instanceof LibraryContainer) { + ((LibraryContainer) archive).addAsLibrary(buildSupportLibrary()); + } else { + throw new IllegalStateException("Expected library container!"); + } + old = Thread.currentThread().getContextClassLoader(); + if (testClass.get() == null) { + throw new IllegalStateException("Test class not available"); + } + Class testJavaClass = testClass.get().getJavaClass(); + Objects.requireNonNull(testJavaClass); + + try { + DeploymentDir deploymentDir = new DeploymentDir(); + this.deploymentDir.set(deploymentDir); + + new ArchiveCompiler(deploymentDir, archive).compile(); + + ClassLoader classLoader = new DeploymentClassLoader(deploymentDir); + applicationClassLoader.set(classLoader); + + ApplicationContext applicationContext = ApplicationContext.builder() + .classLoader(classLoader) + .build() + .start(); + + testInstance = applicationContext.getBean(classLoader.loadClass(testJavaClass.getName())); + + runningApplicationContext.set(applicationContext); + APP.set(applicationContext); + Thread.currentThread().setContextClassLoader(classLoader); + + } catch (Throwable e) { + throw new RuntimeException(e); + } finally { + Thread.currentThread().setContextClassLoader(old); + } + + return new ProtocolMetaData(); + } + + @Override + public void undeploy(Archive archive) { + try { + ApplicationContext appContext = runningApplicationContext.get(); + if (appContext != null) { + Thread.currentThread().setContextClassLoader(runningApplicationContext.get().getClassLoader()); + appContext.stop(); + } + testInstance = null; + + DeploymentDir deploymentDir = this.deploymentDir.get(); + if (deploymentDir != null) { + deleteDirectory(deploymentDir.root); + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private static void deleteDirectory(Path dir) { + try { + Files.walkFileTree(dir, new FileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.warn("Unable to delete directory: " + dir, e); + } + } +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckExtension.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckExtension.java new file mode 100644 index 00000000..e1fd702f --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckExtension.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.container.test.spi.client.protocol.Protocol; +import org.jboss.arquillian.core.spi.LoadableExtension; + +/** + * TCK loadable extension. + */ +@Internal +public class TckExtension implements LoadableExtension { + + @Override + public void register(ExtensionBuilder builder) { +// SLF4JBridgeHandler.removeHandlersForRootLogger(); +// SLF4JBridgeHandler.install(); +// Logger.getLogger("").setLevel(Level.FINEST); + + builder.service(DeployableContainer.class, TckDeployableContainer.class); + builder.service(Protocol.class, TckProtocol.class); + } + +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckProtocol.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckProtocol.java new file mode 100644 index 00000000..c286d347 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/TckProtocol.java @@ -0,0 +1,153 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck; + +import io.micronaut.core.annotation.Internal; +import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; +import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; +import org.jboss.arquillian.container.test.impl.client.protocol.local.LocalDeploymentPackager; +import org.jboss.arquillian.container.test.impl.execution.event.LocalExecutionEvent; +import org.jboss.arquillian.container.test.spi.ContainerMethodExecutor; +import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentPackager; +import org.jboss.arquillian.container.test.spi.client.protocol.Protocol; +import org.jboss.arquillian.container.test.spi.client.protocol.ProtocolConfiguration; +import org.jboss.arquillian.container.test.spi.command.CommandCallback; +import org.jboss.arquillian.core.api.Event; +import org.jboss.arquillian.core.api.Injector; +import org.jboss.arquillian.core.api.Instance; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.arquillian.test.spi.TestMethodExecutor; +import org.jboss.arquillian.test.spi.TestResult; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@Internal +final class TckProtocol implements Protocol { + + @Inject + Instance injector; + + @Override + public Class getProtocolConfigurationClass() { + return TckProtocolConfiguration.class; + } + + @Override + public ProtocolDescription getDescription() { + return new ProtocolDescription("Micronaut"); + } + + @Override + public DeploymentPackager getPackager() { + return new LocalDeploymentPackager(); + } + + @Override + public ContainerMethodExecutor getExecutor(TckProtocolConfiguration protocolConfiguration, + ProtocolMetaData metaData, + CommandCallback callback) { + return injector.get().inject(new TckMethodExecutor()); + } + + public static class TckProtocolConfiguration implements ProtocolConfiguration { + } + + static class TckMethodExecutor implements ContainerMethodExecutor { + + @Inject + Event event; + + @Inject + Instance testResult; + + @Inject + Instance classLoaderInstance; + + @Override + public TestResult invoke(TestMethodExecutor testMethodExecutor) { + + event.fire(new LocalExecutionEvent(new TestMethodExecutor() { + + @Override + public void invoke(Object... parameters) throws Throwable { + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(classLoaderInstance.get()); + + Object actualTestInstance = TckDeployableContainer.testInstance; + Method actualMethod = null; + try { + actualMethod = actualTestInstance.getClass().getMethod(getMethod().getName(), + convertToTCCL(getMethod().getParameterTypes())); + } catch (NoSuchMethodException e) { + // the method should still be present, just not public, let's try declared methods + actualMethod = actualTestInstance.getClass().getDeclaredMethod(getMethod().getName(), + convertToTCCL(getMethod().getParameterTypes())); + actualMethod.setAccessible(true); + } + try { + actualMethod.invoke(actualTestInstance, parameters); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause != null) { + throw cause; + } else { + throw e; + } + } + } finally { + Thread.currentThread().setContextClassLoader(loader); + } + } + + @Override + public Method getMethod() { + return testMethodExecutor.getMethod(); + } + + @Override + public Object getInstance() { + return TckDeployableContainer.testInstance; + } + + @Override + public String getMethodName() { + return testMethodExecutor.getMethod().getName(); + } + })); + return testResult.get(); + } + + } + + static Class[] convertToTCCL(Class[] classes) throws ClassNotFoundException { + return convertToCL(classes, Thread.currentThread().getContextClassLoader()); + } + + static Class[] convertToCL(Class[] classes, ClassLoader classLoader) throws ClassNotFoundException { + Class[] result = new Class[classes.length]; + for (int i = 0; i < classes.length; i++) { + if (classes[i].getClassLoader() != classLoader) { + result[i] = classLoader.loadClass(classes[i].getName()); + } else { + result[i] = classes[i]; + } + } + return result; + } + +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidationProvider.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidationProvider.java new file mode 100644 index 00000000..e6de5712 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidationProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck.runtime; + +import io.micronaut.core.annotation.Internal; +import io.micronaut.validation.validator.DefaultValidatorFactory; +import jakarta.validation.Configuration; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.spi.BootstrapState; +import jakarta.validation.spi.ConfigurationState; +import jakarta.validation.spi.ValidationProvider; + +@Internal +public final class TckValidationProvider implements ValidationProvider { + + @Override + public TckValidatorConfiguration createSpecializedConfiguration(BootstrapState state) { + return new TckValidatorConfiguration(); + } + + @Override + public Configuration createGenericConfiguration(BootstrapState state) { + throw new RuntimeException(); + } + + @Override + public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) { + return new DefaultValidatorFactory(); + } + +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidatorConfiguration.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidatorConfiguration.java new file mode 100644 index 00000000..9ec03e9e --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TckValidatorConfiguration.java @@ -0,0 +1,130 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck.runtime; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.beans.BeanIntrospector; +import io.micronaut.validation.tck.TckDeployableContainer; +import io.micronaut.validation.validator.DefaultValidatorConfiguration; +import io.micronaut.validation.validator.DefaultValidatorFactory; +import io.micronaut.validation.validator.ValidatorConfiguration; +import jakarta.validation.BootstrapConfiguration; +import jakarta.validation.ClockProvider; +import jakarta.validation.Configuration; +import jakarta.validation.ConstraintValidatorFactory; +import jakarta.validation.MessageInterpolator; +import jakarta.validation.ParameterNameProvider; +import jakarta.validation.TraversableResolver; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.valueextraction.ValueExtractor; + +import java.io.InputStream; + +@Internal +public final class TckValidatorConfiguration implements Configuration { + + private DefaultValidatorConfiguration validatorConfiguration = new DefaultValidatorConfiguration(); + + @Override + public TckValidatorConfiguration ignoreXmlConfiguration() { + return this; + } + + @Override + public TckValidatorConfiguration messageInterpolator(MessageInterpolator interpolator) { + return this; + } + + @Override + public TckValidatorConfiguration traversableResolver(TraversableResolver resolver) { + return this; + } + + @Override + public TckValidatorConfiguration constraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) { + return this; + } + + @Override + public TckValidatorConfiguration parameterNameProvider(ParameterNameProvider parameterNameProvider) { + return this; + } + + @Override + public TckValidatorConfiguration clockProvider(ClockProvider clockProvider) { + validatorConfiguration.setClockProvider(clockProvider); + return this; + } + + @Override + public TckValidatorConfiguration addValueExtractor(ValueExtractor extractor) { + validatorConfiguration.addValueExtractor(extractor); + return this; + } + + @Override + public TckValidatorConfiguration addMapping(InputStream stream) { + return this; + } + + @Override + public TckValidatorConfiguration addProperty(String name, String value) { + return this; + } + + @Override + public MessageInterpolator getDefaultMessageInterpolator() { + return null; + } + + @Override + public TraversableResolver getDefaultTraversableResolver() { + return null; + } + + @Override + public ConstraintValidatorFactory getDefaultConstraintValidatorFactory() { + return null; + } + + @Override + public ParameterNameProvider getDefaultParameterNameProvider() { + return null; + } + + @Override + public ClockProvider getDefaultClockProvider() { + return null; + } + + @Override + public BootstrapConfiguration getBootstrapConfiguration() { + return null; + } + + @Override + public ValidatorFactory buildValidatorFactory() { + ApplicationContext applicationContext = TckDeployableContainer.APP.get(); + ClassLoader classLoader = applicationContext.getClassLoader(); + ValidatorConfiguration contextValidatorConfiguration = applicationContext.getBean(ValidatorConfiguration.class); + validatorConfiguration.setExecutionHandleLocator(applicationContext); + validatorConfiguration.setBeanIntrospector(BeanIntrospector.forClassLoader(classLoader)); + validatorConfiguration.setConstraintValidatorRegistry(contextValidatorConfiguration.getConstraintValidatorRegistry()); + return new DefaultValidatorFactory(validatorConfiguration); + } + +} diff --git a/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TestClassVisitor.java b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TestClassVisitor.java new file mode 100644 index 00000000..c31db160 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/java/io/micronaut/validation/tck/runtime/TestClassVisitor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.tck.runtime; + +import io.micronaut.context.annotation.Executable; +import io.micronaut.context.annotation.Prototype; +import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.Introspected; +import io.micronaut.inject.ast.ClassElement; +import io.micronaut.inject.visitor.TypeElementVisitor; +import io.micronaut.inject.visitor.VisitorContext; +import io.micronaut.validation.validator.constraints.ConstraintValidator; +import jakarta.validation.ClockProvider; +import jakarta.validation.valueextraction.ValueExtractor; + +@Internal +public final class TestClassVisitor implements TypeElementVisitor { + + @Override + public VisitorKind getVisitorKind() { + return VisitorKind.ISOLATING; + } + + @Override + public void visitClass(ClassElement element, VisitorContext context) { + process(element); + } + + private void process(ClassElement element) { + if (element.getName().startsWith("org.hibernate.beanvalidation.tck.tests")) { + if (element.isAssignable(ClockProvider.class)) { + return; + } + if (element.isAssignable(ConstraintValidator.class)) { + return; + } + if (element.isAssignable(ValueExtractor.class)) { + return; + } + element.annotate(Introspected.class, builder -> { + builder.member("accessKind", new Introspected.AccessKind[]{Introspected.AccessKind.FIELD, Introspected.AccessKind.METHOD}); + builder.member("visibility", Introspected.Visibility.ANY); + }); + element.annotate(Executable.class); + element.annotate(Prototype.class); + } + } +} diff --git a/tests/jakarta-validation-tck/src/main/resources/META-INF/cdi-tck.properties b/tests/jakarta-validation-tck/src/main/resources/META-INF/cdi-tck.properties new file mode 100644 index 00000000..b3376d89 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/resources/META-INF/cdi-tck.properties @@ -0,0 +1,4 @@ +#org.jboss.cdi.tck.cdiLiteMode=true +#org.jboss.cdi.tck.spi.Beans=org.eclipse.odi.tck.porting.BeansImpl +#org.jboss.cdi.tck.spi.Contexts=org.eclipse.odi.tck.porting.ContextsImpl +#org.jboss.cdi.tck.spi.EL=org.eclipse.odi.tck.porting.ELImpl diff --git a/tests/jakarta-validation-tck/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor b/tests/jakarta-validation-tck/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor new file mode 100644 index 00000000..eb6805e0 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/resources/META-INF/services/io.micronaut.inject.visitor.TypeElementVisitor @@ -0,0 +1 @@ +io.micronaut.validation.tck.runtime.TestClassVisitor diff --git a/tests/jakarta-validation-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/tests/jakarta-validation-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 00000000..d012a66a --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +io.micronaut.validation.tck.TckExtension diff --git a/tests/jakarta-validation-tck/src/main/resources/arquillian.xml b/tests/jakarta-validation-tck/src/main/resources/arquillian.xml new file mode 100644 index 00000000..43fb3099 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/resources/arquillian.xml @@ -0,0 +1,15 @@ + + + + + 40 + + + + + + + + + + \ No newline at end of file diff --git a/tests/jakarta-validation-tck/src/main/resources/logback.xml b/tests/jakarta-validation-tck/src/main/resources/logback.xml new file mode 100644 index 00000000..c416cf41 --- /dev/null +++ b/tests/jakarta-validation-tck/src/main/resources/logback.xml @@ -0,0 +1,17 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + diff --git a/tests/jakarta-validation-tck/src/test/java/test/DummyTest.java b/tests/jakarta-validation-tck/src/test/java/test/DummyTest.java new file mode 100644 index 00000000..1d11cc5d --- /dev/null +++ b/tests/jakarta-validation-tck/src/test/java/test/DummyTest.java @@ -0,0 +1,8 @@ +package test; + +import org.testng.annotations.Test; + +@Test +public class DummyTest { + // Keep is so Gradle doesn't ignore the tests +} diff --git a/tests/jakarta-validation-tck/tck-tests.xml b/tests/jakarta-validation-tck/tck-tests.xml new file mode 100644 index 00000000..3ef42376 --- /dev/null +++ b/tests/jakarta-validation-tck/tck-tests.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/validation-processor/src/main/java/io/micronaut/validation/visitor/IntrospectedValidationIndexesVisitor.java b/validation-processor/src/main/java/io/micronaut/validation/visitor/IntrospectedValidationIndexesVisitor.java index 8876cc0c..38ad51d2 100644 --- a/validation-processor/src/main/java/io/micronaut/validation/visitor/IntrospectedValidationIndexesVisitor.java +++ b/validation-processor/src/main/java/io/micronaut/validation/visitor/IntrospectedValidationIndexesVisitor.java @@ -39,8 +39,8 @@ @Internal public class IntrospectedValidationIndexesVisitor implements TypeElementVisitor { - private static final String ANN_CONSTRAINT = "javax.validation.Constraint"; - private static final String ANN_VALID = "javax.validation.Valid"; + private static final String ANN_CONSTRAINT = "jakarta.validation.Constraint"; + private static final String ANN_VALID = "jakarta.validation.Valid"; private static final AnnotationValue INTROSPECTION_INDEXED_CONSTRAINT = AnnotationValue.builder(Introspected.IndexedAnnotation.class) .member("annotation", new AnnotationClassValue<>(ANN_CONSTRAINT)) diff --git a/validation-processor/src/main/java/io/micronaut/validation/visitor/ValidationVisitor.java b/validation-processor/src/main/java/io/micronaut/validation/visitor/ValidationVisitor.java index 4ef13993..20ccef59 100644 --- a/validation-processor/src/main/java/io/micronaut/validation/visitor/ValidationVisitor.java +++ b/validation-processor/src/main/java/io/micronaut/validation/visitor/ValidationVisitor.java @@ -18,6 +18,7 @@ import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.Internal; +import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import io.micronaut.inject.ast.ClassElement; import io.micronaut.inject.ast.ConstructorElement; @@ -35,7 +36,7 @@ /** * The visitor creates annotations utilized by the Validator. - *

+ * * It adds @RequiresValidation annotation to fields if they require validation, and to methods * if one of the parameters or return value require validation. * @@ -45,8 +46,8 @@ @Internal public class ValidationVisitor implements TypeElementVisitor { - private static final String ANN_CONSTRAINT = "javax.validation.Constraint"; - private static final String ANN_VALID = "javax.validation.Valid"; + private static final String ANN_CONSTRAINT = "jakarta.validation.Constraint"; + private static final String ANN_VALID = "jakarta.validation.Valid"; private ClassElement classElement; @@ -69,6 +70,9 @@ public VisitorKind getVisitorKind() { @Override public void visitClass(ClassElement element, VisitorContext context) { classElement = element; + if (classElement.hasAnnotation("jakarta.validation.GroupSequence")) { + classElement.annotate(Introspected.class); + } } @Override diff --git a/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpec.groovy b/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpec.groovy index 4dab2259..6e6752d4 100644 --- a/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpec.groovy +++ b/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpec.groovy @@ -18,12 +18,12 @@ package test; class Test { @io.micronaut.context.annotation.Executable - public void setName(@javax.validation.constraints.NotBlank String name) { + public void setName(@jakarta.validation.constraints.NotBlank String name) { } @io.micronaut.context.annotation.Executable - public void setName2(@javax.validation.Valid String name) { + public void setName2(@jakarta.validation.Valid String name) { } } @@ -42,7 +42,7 @@ package test; import io.micronaut.http.annotation.Get; import io.micronaut.http.client.annotation.Client; -import javax.validation.constraints.PastOrPresent; +import jakarta.validation.constraints.PastOrPresent; import java.time.LocalDate; @Client("https://exchangeratesapi.io") @@ -63,7 +63,7 @@ interface ExchangeRates { package test; import java.util.List; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @jakarta.inject.Singleton class Test { @@ -88,7 +88,7 @@ class Test { package test; import java.util.List; -import javax.validation.Valid; +import jakarta.validation.Valid; import io.micronaut.core.annotation.Introspected; import io.micronaut.http.annotation.Body; @@ -119,8 +119,8 @@ class Test { package test; import java.util.List; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import io.micronaut.context.annotation.Executable; @jakarta.inject.Singleton @@ -156,7 +156,7 @@ class Test { def definition = buildBeanDefinition('test.Test',''' package test; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import io.micronaut.context.annotation.Executable; import reactor.core.publisher.Mono; @@ -201,9 +201,9 @@ final class TrackedSortedSet> { def definition = buildBeanDefinition('test.Test',''' package test; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import io.micronaut.context.annotation.Executable; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; @jakarta.inject.Singleton class Test { diff --git a/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpecGroovy.groovy b/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpecGroovy.groovy index 2cf52e30..67bdd6a6 100644 --- a/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpecGroovy.groovy +++ b/validation-processor/src/test/groovy/io/micronaut/validation/visitor/ValidatedParseSpecGroovy.groovy @@ -16,8 +16,8 @@ class ValidatedParseSpecGroovy extends AbstractBeanDefinitionSpec { def definition = buildBeanDefinition('validateparse1.Test',''' package validateparse1 import io.micronaut.context.annotation.Executable -import javax.validation.Valid -import javax.validation.constraints.NotBlank +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank @jakarta.inject.Singleton class Test { @@ -39,7 +39,7 @@ class Test { BeanIntrospection beanIntrospection = buildBeanIntrospection('validateparse2.Test',''' package validateparse2; import io.micronaut.core.annotation.Introspected -import javax.validation.Constraint +import jakarta.validation.Constraint import java.lang.annotation.ElementType import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @@ -72,7 +72,7 @@ class Test { package validateparse3 import io.micronaut.http.annotation.Get import io.micronaut.http.client.annotation.Client -import javax.validation.constraints.PastOrPresent +import jakarta.validation.constraints.PastOrPresent import java.time.LocalDate @Client("https://exchangeratesapi.io") diff --git a/validation/src/main/java/io/micronaut/validation/ValidatingInterceptor.java b/validation/src/main/java/io/micronaut/validation/ValidatingInterceptor.java index f00a4422..607702ba 100644 --- a/validation/src/main/java/io/micronaut/validation/ValidatingInterceptor.java +++ b/validation/src/main/java/io/micronaut/validation/ValidatingInterceptor.java @@ -28,10 +28,10 @@ import jakarta.inject.Singleton; import java.lang.reflect.Method; import java.util.Set; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.ValidatorFactory; -import javax.validation.executable.ExecutableValidator; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidatorFactory; +import jakarta.validation.executable.ExecutableValidator; /** * A {@link MethodInterceptor} that validates method invocations. @@ -64,7 +64,7 @@ public ValidatingInterceptor(@Nullable Validator micronautValidator, this.conversionService = conversionService; if (validatorFactory != null) { - javax.validation.Validator validator = validatorFactory.getValidator(); + jakarta.validation.Validator validator = validatorFactory.getValidator(); if (validator instanceof Validator) { this.micronautValidator = (ExecutableMethodValidator) validator; this.executableValidator = null; diff --git a/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java b/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java index 7fdaa307..33625baf 100644 --- a/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java +++ b/validation/src/main/java/io/micronaut/validation/exceptions/ConstraintExceptionHandler.java @@ -27,10 +27,10 @@ import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.ElementKind; -import javax.validation.Path; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ElementKind; +import jakarta.validation.Path; import java.util.Iterator; import java.util.Set; import java.util.stream.Collectors; diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultAnnotatedElementValidator.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultAnnotatedElementValidator.java index 8535118b..0f7fcd14 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultAnnotatedElementValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultAnnotatedElementValidator.java @@ -27,7 +27,7 @@ import java.lang.annotation.Annotation; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -45,8 +45,7 @@ public class DefaultAnnotatedElementValidator extends DefaultValidator implement * Default constructor. */ public DefaultAnnotatedElementValidator() { - super(new DefaultValidatorConfiguration() - .setConstraintValidatorRegistry(new LocalConstraintValidators())); + super(new DefaultValidatorConfiguration().setConstraintValidatorRegistry(new LocalConstraintValidators())); } /** @@ -54,7 +53,7 @@ public DefaultAnnotatedElementValidator() { */ private static final class LocalConstraintValidators extends DefaultConstraintValidators { - private Map, ConstraintValidator> validatorMap; + private Map> validatorMap; @Override protected Optional> findLocalConstraintValidator(@NonNull Class constraintType, @NonNull Class targetType) { @@ -67,26 +66,23 @@ private Optional> findConstr } return validatorMap.entrySet().stream() .filter(entry -> { - final ValidatorKey key = entry.getKey(); + final ValidatorKey key = entry.getKey(); final Class[] left = {constraintType, targetType}; return TypeArgumentQualifier.areTypesCompatible( left, - Arrays.asList(key.getConstraintType(), key.getTargetType()) + Arrays.asList(key.constraintType(), key.targetType()) ); }) .findFirst().map(e -> (ConstraintValidator) e.getValue()); } - private Map, ConstraintValidator> initializeValidatorMap() { - validatorMap = new HashMap<>(); + private Map> initializeValidatorMap() { + validatorMap = new LinkedHashMap<>(); for (ConstraintValidator validator : SoftServiceLoader.load(ConstraintValidator.class).collectAll()) { try { - final Class[] typeArgs = GenericTypeUtils.resolveInterfaceTypeArguments(validator.getClass(), ConstraintValidator.class); + final Class[] typeArgs = GenericTypeUtils.resolveInterfaceTypeArguments(validator.getClass(), ConstraintValidator.class); if (ArrayUtils.isNotEmpty(typeArgs) && typeArgs.length == 2) { - validatorMap.put( - new ValidatorKey<>(typeArgs[0], typeArgs[1]), - validator - ); + validatorMap.put(new ValidatorKey(typeArgs[0], typeArgs[1]), validator); } } catch (Exception e) { // as this will occur in the compiler, we print a warning and not log it diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultClockProvider.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultClockProvider.java index df2f3ab2..43c1acf9 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultClockProvider.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultClockProvider.java @@ -17,7 +17,7 @@ import jakarta.inject.Singleton; -import javax.validation.ClockProvider; +import jakarta.validation.ClockProvider; import java.time.Clock; /** diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintDescriptor.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintDescriptor.java index 04c1a1e3..e58ef318 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintDescriptor.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultConstraintDescriptor.java @@ -19,11 +19,11 @@ import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Internal; -import javax.validation.ConstraintTarget; -import javax.validation.ConstraintValidator; -import javax.validation.Payload; -import javax.validation.metadata.ConstraintDescriptor; -import javax.validation.metadata.ValidateUnwrappedValue; +import jakarta.validation.ConstraintTarget; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.Payload; +import jakarta.validation.metadata.ConstraintDescriptor; +import jakarta.validation.metadata.ValidateUnwrappedValue; import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.Collections; diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java index 5730dad7..e34516f9 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidator.java @@ -26,7 +26,6 @@ import io.micronaut.context.exceptions.BeanInstantiationException; import io.micronaut.core.annotation.AnnotatedElement; import io.micronaut.core.annotation.AnnotationMetadata; -import io.micronaut.core.annotation.AnnotationMetadataProvider; import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; @@ -56,29 +55,30 @@ import io.micronaut.validation.validator.constraints.ConstraintValidatorRegistry; import io.micronaut.validation.validator.extractors.ValueExtractorRegistry; import jakarta.inject.Singleton; +import jakarta.validation.ClockProvider; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ElementKind; +import jakarta.validation.GroupSequence; +import jakarta.validation.Path; +import jakarta.validation.TraversableResolver; +import jakarta.validation.Valid; +import jakarta.validation.ValidationException; +import jakarta.validation.groups.Default; +import jakarta.validation.metadata.BeanDescriptor; +import jakarta.validation.metadata.ConstraintDescriptor; +import jakarta.validation.metadata.ConstructorDescriptor; +import jakarta.validation.metadata.ElementDescriptor; +import jakarta.validation.metadata.MethodDescriptor; +import jakarta.validation.metadata.MethodType; +import jakarta.validation.metadata.PropertyDescriptor; +import jakarta.validation.metadata.Scope; +import jakarta.validation.valueextraction.ValueExtractor; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.validation.ClockProvider; -import javax.validation.Constraint; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.ElementKind; -import javax.validation.Path; -import javax.validation.TraversableResolver; -import javax.validation.Valid; -import javax.validation.ValidationException; -import javax.validation.groups.Default; -import javax.validation.metadata.BeanDescriptor; -import javax.validation.metadata.ConstraintDescriptor; -import javax.validation.metadata.ConstructorDescriptor; -import javax.validation.metadata.ElementDescriptor; -import javax.validation.metadata.MethodDescriptor; -import javax.validation.metadata.MethodType; -import javax.validation.metadata.PropertyDescriptor; -import javax.validation.metadata.Scope; -import javax.validation.valueextraction.ValueExtractor; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.Constructor; @@ -88,7 +88,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; @@ -99,6 +98,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.stream.Collectors; @@ -113,8 +113,9 @@ @Primary @Requires(property = ValidatorConfiguration.ENABLED, value = StringUtils.TRUE, defaultValue = StringUtils.TRUE) public class DefaultValidator implements - Validator, ExecutableMethodValidator, ReactiveValidator, AnnotatedElementValidator, BeanDefinitionValidator { + Validator, ExecutableMethodValidator, ReactiveValidator, AnnotatedElementValidator, BeanDefinitionValidator { + private static final Map, List>> GROUP_SEQUENCES = new ConcurrentHashMap<>(); private static final List> DEFAULT_GROUPS = Collections.singletonList(Default.class); private final ConstraintValidatorRegistry constraintValidatorRegistry; private final ClockProvider clockProvider; @@ -123,13 +124,14 @@ public class DefaultValidator implements private final ExecutionHandleLocator executionHandleLocator; private final MessageSource messageSource; private final ConversionService conversionService; + private final BeanIntrospector beanIntrospector; /** * Default constructor. * * @param configuration The validator configuration */ - protected DefaultValidator(@NonNull ValidatorConfiguration configuration) { + public DefaultValidator(@NonNull ValidatorConfiguration configuration) { ArgumentUtils.requireNonNull("configuration", configuration); this.constraintValidatorRegistry = configuration.getConstraintValidatorRegistry(); this.clockProvider = configuration.getClockProvider(); @@ -138,6 +140,7 @@ protected DefaultValidator(@NonNull ValidatorConfiguration configuration) { this.executionHandleLocator = configuration.getExecutionHandleLocator(); this.messageSource = configuration.getMessageSource(); this.conversionService = configuration.getConversionService(); + this.beanIntrospector = configuration.getBeanIntrospector(); } @NonNull @@ -146,7 +149,7 @@ public Set> validate(@NonNull T object, @Nullable Cla ArgumentUtils.requireNonNull("object", object); final BeanIntrospection introspection = getBeanIntrospection(object); if (introspection == null) { - return Collections.emptySet(); + throw new ValidationException("Bean introspection not found for the class: " + object.getClass()); } return validate(introspection, object, groups); } @@ -169,9 +172,14 @@ public Set> validate(@NonNull BeanIntrospection in if (introspection == null) { throw new ValidationException("Passed object [" + object + "] cannot be introspected. Please annotate with @Introspected"); } - DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(object, groups); - doValidate(context, introspection, object); - return context.overallViolations; + for (Class[] groupSequence : findGroupSequences(groups)) { + DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(object, groupSequence); + doValidate(context, introspection, object); + if (!context.overallViolations.isEmpty()) { + return context.overallViolations; + } + } + return Collections.emptySet(); } @NonNull @@ -193,13 +201,7 @@ public Set> validateProperty(@NonNull T object, DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(object, groups); - final BeanProperty constrainedProperty = property.get(); - - Path.Node node = context.addPropertyNode(constrainedProperty.getName()); - - final Object propertyValue = constrainedProperty.get(object); - - validateElement(context, object, constrainedProperty.asArgument(), propertyValue, node, false, false); + visitProperty(context, object, property.get()); return Collections.unmodifiableSet(context.overallViolations); } @@ -219,14 +221,16 @@ public Set> validateValue(@NonNull Class beanType, } final BeanProperty beanProperty = introspection.getProperty(propertyName) - .orElseThrow(() -> new ValidationException("No property [" + propertyName + "] found on type: " + beanType)); + .orElseThrow(() -> new ValidationException("No property [" + propertyName + "] found on type: " + beanType)); DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(beanType, groups); - // create node, that will be removed inside validateElement() - Path.Node node = context.addPropertyNode(beanProperty.getName()); - - validateElement(context, null, beanProperty.asArgument(), value, node); + context.addPropertyNode(beanProperty.getName()); + try { + visitElement(context, null, beanProperty.asArgument(), value); + } finally { + context.removeLast(); + } return Collections.unmodifiableSet(context.overallViolations); } @@ -242,9 +246,14 @@ public Set validatedAnnotatedElement(@NonNull AnnotatedElement element, final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(value); Argument type = value != null ? Argument.of((Class) value.getClass(), element.getAnnotationMetadata()) : Argument.OBJECT_ARGUMENT; - // create node, that will be removed inside validateElement() - Path.Node node = context.addPropertyNode(element.getName()); - validateElement(context, element, type, value, node); + + context.addPropertyNode(element.getName()); + + try { + visitElement(context, element, type, value); + } finally { + context.removeLast(); + } return context.overallViolations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toUnmodifiableSet()); } @@ -274,9 +283,9 @@ public T createValid(@NonNull Class beanType, Object... arguments) throws @Override public BeanDescriptor getConstraintsForClass(Class clazz) { - return BeanIntrospector.SHARED.findIntrospection(clazz) - .map((Function, BeanDescriptor>) IntrospectedBeanDescriptor::new) - .orElseGet(() -> new EmptyDescriptor(clazz)); + return beanIntrospector.findIntrospection(clazz) + .map((Function, BeanDescriptor>) IntrospectedBeanDescriptor::new) + .orElseGet(() -> new EmptyDescriptor(clazz)); } @Override @@ -352,11 +361,11 @@ public Set> validateParameters(@NonNull T object, @Nullable Class... groups) { ArgumentUtils.requireNonNull("method", method); return executionHandleLocator.findExecutableMethod( - method.getDeclaringClass(), - method.getName(), - method.getParameterTypes() - ).map(executableMethod -> validateParameters(object, executableMethod, parameterValues, groups)) - .orElse(Collections.emptySet()); + method.getDeclaringClass(), + method.getName(), + method.getParameterTypes() + ).map(executableMethod -> validateParameters(object, executableMethod, parameterValues, groups)) + .orElse(Collections.emptySet()); } @NonNull @@ -368,11 +377,11 @@ public Set> validateReturnValue(@NonNull T object, ArgumentUtils.requireNonNull("method", method); ArgumentUtils.requireNonNull("object", object); return executionHandleLocator.findExecutableMethod( - method.getDeclaringClass(), - method.getName(), - method.getParameterTypes() - ).map(executableMethod -> validateReturnValue(object, executableMethod, returnValue, groups)) - .orElse(Collections.emptySet()); + method.getDeclaringClass(), + method.getName(), + method.getParameterTypes() + ).map(executableMethod -> validateReturnValue(object, executableMethod, returnValue, groups)) + .orElse(Collections.emptySet()); } @Override @@ -383,15 +392,17 @@ public Set> validateReturnValue(@NonNull T object, final ReturnType returnType = executableMethod.getReturnType(); final DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(object, groups); - // The {BeanDefinitionInjectProcessor adds the Valid annotation to the method if return type has it} - // In case of cascading @Valid to iterables - it might only be added to method - // (as described in validateElement() method's NOTE) boolean hasValid = executableMethod.hasStereotype(Valid.class); boolean hasConstraint = executableMethod.hasStereotype(Constraint.class); - Path.Node node = context.addReturnValueNode(returnType.asArgument().getName()); - - validateElement(context, object, returnType.asArgument(), returnValue, node, hasValid, hasConstraint); + context.addMethodNode(executableMethod); + context.addReturnValueNode(); + try { + visitElement(context, object, returnType.asArgument(), returnValue, hasValid, hasConstraint); + } finally { + context.removeLast(); + context.removeLast(); + } return context.overallViolations; } @@ -465,15 +476,15 @@ public Publisher validatePublisher(@NonNull ReturnType returnType, Publisher output; if (Publishers.isSingle(returnType.getType())) { output = Mono.from(publisher).flatMap(value -> { - Set> violations = validatePublisherValue(returnType, publisherArgument, publisher, groups, typeParameter, value); + Set> violations = validatePublisherValue(publisherArgument, publisher, groups, typeParameter, value); return violations.isEmpty() ? Mono.just(value) : - Mono.error(new ConstraintViolationException(violations)); + Mono.error(new ConstraintViolationException(violations)); }); } else { output = Flux.from(publisher).flatMap(value -> { - Set> violations = validatePublisherValue(returnType, publisherArgument, publisher, groups, typeParameter, value); + Set> violations = validatePublisherValue(publisherArgument, publisher, groups, typeParameter, value); return violations.isEmpty() ? Flux.just(value) : - Flux.error(new ConstraintViolationException(violations)); + Flux.error(new ConstraintViolationException(violations)); }); } Class returnClass = returnType.getType(); @@ -486,22 +497,28 @@ public Publisher validatePublisher(@NonNull ReturnType returnType, /** * A method used inside the {@link #validatePublisher} method. */ - private Set> validatePublisherValue(ReturnType returnType, - Argument publisherArgument, + private Set> validatePublisherValue(Argument publisherArgument, @NonNull T publisher, Class[] groups, Argument valueArgument, E value ) { DefaultConstraintValidatorContext context = new DefaultConstraintValidatorContext<>(publisher, groups); - context.addReturnValueNode(returnType.asArgument().getName()); - - validateIterableValue(context, - publisher, - publisherArgument, - valueArgument, - value, - null, null, true); + context.addReturnValueNode(); + try { + validateIterableValue(context, + publisher, + "", + publisherArgument, + valueArgument, + value, + null, null, 0, true, + publisherArgument.getAnnotationMetadata().hasAnnotation(Valid.class) || valueArgument.getAnnotationMetadata().hasAnnotation(Valid.class), + valueArgument.getAnnotationMetadata().hasAnnotation(Constraint.class) + ); + } finally { + context.removeLast(); + } return context.overallViolations; } @@ -539,15 +556,14 @@ public void validateBeanArgument(@NonNull BeanResolutionContext resolutionCo final Class rootClass = injectionPoint.getDeclaringBean().getBeanType(); context.addConstructorNode( - rootClass.getName(), injectionPoint.getDeclaringBean().getConstructor().getArguments()); + rootClass.getName(), injectionPoint.getDeclaringBean().getConstructor().getArguments()); - // Handle cascade validation annotation - // create node, that will be removed inside validateElement() - Path.Node node = context.addPropertyNode(argument.getName()); - validateElement(context, null, argument, value, node); - - // remove constructor node - context.removeLast(); + context.addPropertyNode(argument.getName()); + try { + visitElement(context, null, argument, value); + } finally { + context.removeLast(); + } failOnError(resolutionContext, context.overallViolations, rootClass); } @@ -586,9 +602,12 @@ public void validateBean(@NonNull BeanResolutionContext resolutionContext, final Object value = executableMethod.invoke(bean); final ReturnType returnType = (ReturnType) executableMethod.getReturnType(); - Path.Node node = context.addPropertyNode(executableMethod.getName()); - // create node, that will be removed inside validateElement() - validateElement(context, bean, returnType.asArgument(), value, node); + context.addPropertyNode(executableMethod.getName()); + try { + visitElement(context, bean, returnType.asArgument(), value); + } finally { + context.removeLast(); + } } } } @@ -615,8 +634,8 @@ protected BeanIntrospection getBeanIntrospection(@NonNull T object, if (object == null) { return null; } - return BeanIntrospector.SHARED.findIntrospection((Class) object.getClass()) - .orElseGet(() -> BeanIntrospector.SHARED.findIntrospection(definedClass).orElse(null)); + return beanIntrospector.findIntrospection((Class) object.getClass()) + .orElseGet(() -> beanIntrospector.findIntrospection(definedClass).orElse(null)); } /** @@ -636,7 +655,7 @@ protected BeanIntrospection getBeanIntrospection(@NonNull T object) { if (object instanceof Class) { return getBeanIntrospection((Class) object); } - return BeanIntrospector.SHARED.findIntrospection((Class) object.getClass()).orElse(null); + return beanIntrospector.findIntrospection((Class) object.getClass()).orElse(null); } /** @@ -649,7 +668,7 @@ protected BeanIntrospection getBeanIntrospection(@NonNull T object) { @SuppressWarnings({"WeakerAccess"}) @Nullable protected BeanIntrospection getBeanIntrospection(@NonNull Class type) { - return BeanIntrospector.SHARED.findIntrospection(type).orElse(null); + return beanIntrospector.findIntrospection(type).orElse(null); } /** @@ -676,20 +695,20 @@ private void instrumentPublisherArgumentWithValidation(@NonNull DefaultCo Publisher objectPublisher; if (publisherArgument.isSpecifiedSingle()) { objectPublisher = Mono.from(publisher) - .flatMap(value -> { + .flatMap(value -> { - validatePublishedValue(valueContext, argumentIndex, publisherArgument, parameterValue, value); + validatePublishedValue(valueContext, argumentIndex, publisherArgument, parameterValue, value); - return valueContext.overallViolations.isEmpty() ? Mono.just(value) : - Mono.error(new ConstraintViolationException(valueContext.overallViolations)); - }); + return valueContext.overallViolations.isEmpty() ? Mono.just(value) : + Mono.error(new ConstraintViolationException(valueContext.overallViolations)); + }); } else { objectPublisher = Flux.from(publisher).flatMap(value -> { validatePublishedValue(valueContext, argumentIndex, publisherArgument, parameterValue, value); return valueContext.overallViolations.isEmpty() ? Flux.just(value) : - Flux.error(new ConstraintViolationException(valueContext.overallViolations)); + Flux.error(new ConstraintViolationException(valueContext.overallViolations)); }); } argumentValues[argumentIndex] = Publishers.convertPublisher(conversionService, objectPublisher, publisherArgument.getType()); @@ -714,10 +733,10 @@ private void validatePublishedValue(@NonNull DefaultConstraintValidatorCo // Create the parameter node and the container element node context.addParameterNode(publisherArgument.getName(), argumentIndex); - Path.Node node = context.addContainerElementNode(valueArgument, value.getClass(), null, null, true); + context.addContainerElementNode("", value.getClass(), null, null, true, 0); try { // node is removed from context inside validateElement() - validateElement(context, context.getRootBean(), valueArgument, publisherInstance, node); + visitElement(context, context.getRootBean(), valueArgument, publisherInstance); } finally { context.removeLast(); } @@ -748,11 +767,10 @@ private void instrumentCompletionStageArgumentWithValidation(@NonNull Def // Create the parameter node and the container element node newContext.addParameterNode(completionStageArgument.getName(), argumentIndex); - - Path.Node node = newContext.addContainerElementNode(valueArgument, parameterValue.getClass(), null, null, true); + newContext.addContainerElementNode("", parameterValue.getClass(), null, null, true, 0); try { // node is removed from context inside validateElement() - validateElement(newContext, newContext.getRootBean(), valueArgument, value, node); + visitElement(newContext, newContext.getRootBean(), valueArgument, value); } finally { newContext.removeLast(); } @@ -776,14 +794,6 @@ private void validateParametersInternal(@NonNull DefaultConstraintValidatorC Argument argument = (Argument) arguments[parameterIndex]; final Class parameterType = argument.getType(); - final AnnotationMetadata annotationMetadata = argument.getAnnotationMetadata(); - final boolean hasValid = annotationMetadata.hasStereotype(Validator.ANN_VALID) || - doesHaveValidatedTypeParameters(context, argument); - final boolean hasConstraint = annotationMetadata.hasStereotype(Validator.ANN_CONSTRAINT); - if (!hasValid && !hasConstraint) { - continue; - } - Object parameterValue = parameters[parameterIndex]; final boolean hasValue = parameterValue != null; @@ -799,242 +809,121 @@ private void validateParametersInternal(@NonNull DefaultConstraintValidatorC continue; } - // create node, that will be removed inside validateElement() - Path.Node node = context.addParameterNode(argument.getName(), parameterIndex); - - validateElement(context, bean, argument, parameterValue, node, false, false); - } - } - - private void validatePojoInternal(@NonNull DefaultConstraintValidatorContext context, - @NonNull Class parameterType, - @NonNull E parameterValue, - @NonNull Class pojoConstraint, - @NonNull AnnotationValue constraintAnnotation) { - ConstraintValidator constraintValidator = constraintValidatorRegistry - .findConstraintValidator(pojoConstraint, parameterType).orElse(null); - - if (constraintValidator == null) { - return; - } - if (constraintValidator.isValid(parameterValue, constraintAnnotation, context)) { - final String currentMessageTemplate = context.getMessageTemplate().orElse(null); - context.messageTemplate(currentMessageTemplate); - return; + context.addParameterNode(argument.getName(), parameterIndex); + try { + visitElement(context, bean, argument, parameterValue); + } finally { + context.removeLast(); + } } - - BeanIntrospection beanIntrospection = getBeanIntrospection(parameterValue); - if (beanIntrospection == null) { - throw new ValidationException("Passed object [" + parameterValue + "] cannot be introspected. Please annotate with @Introspected"); - } - AnnotationMetadata beanAnnotationMetadata = beanIntrospection.getAnnotationMetadata(); - AnnotationValue annotationValue = beanAnnotationMetadata.getAnnotation(pojoConstraint); - - final String propertyValue = ""; - final String messageTemplate = buildMessageTemplate(context, annotationValue, beanAnnotationMetadata); - final Map variables = newConstraintVariables(annotationValue, propertyValue, beanAnnotationMetadata); - context.overallViolations.add(new DefaultConstraintViolation<>( - context.getRootBean(), - context.getRootClass(), - parameterValue, - parameterValue, - messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(variables)), - messageTemplate, - new PathImpl(context.currentPath), - new DefaultConstraintDescriptor<>(beanAnnotationMetadata, pojoConstraint, annotationValue), - null)); } private void doValidate(@NonNull DefaultConstraintValidatorContext context, @NonNull BeanIntrospection introspection, @NonNull T object) { - final Collection> cascadeNestedProperties = introspection.getBeanProperties().stream() - .filter(p -> p.hasStereotype(Valid.class) || doesHaveValidatedTypeParameters(context, p.asArgument())) - .toList(); - for (BeanProperty cascadeProperty : cascadeNestedProperties) { - final Object propertyValue = cascadeProperty.get(object); + visitElement( + context, + object, + Argument.of(introspection.getBeanType(), introspection.getAnnotationMetadata()), + object + ); - Path.Node node = context.addPropertyNode(cascadeProperty.getName()); - try { - validateCascadeElement( - context, - object, - cascadeProperty.asArgument(), - propertyValue, - node - ); - } finally { - context.removeLast(); - } + for (BeanProperty property : introspection.getBeanProperties()) { + visitProperty(context, object, property); } + } - for (BeanProperty constrainedProperty : introspection.getIndexedProperties(Constraint.class)) { - if (constrainedProperty.isWriteOnly()) { - continue; - } - context.addPropertyNode(constrainedProperty.getName()); - validateConstrainedElement(context, object, constrainedProperty.asArgument(), constrainedProperty.get(object)); - context.removeLast(); + private void visitProperty(DefaultConstraintValidatorContext context, T object, BeanProperty property) { + if (property.isWriteOnly()) { + return; } - for (Class pojoConstraint : introspection.getAnnotationTypesByStereotype(Constraint.class)) { - validatePojoInternal(context, introspection, object, pojoConstraint); + context.addPropertyNode(property.getName()); + try { + visitElement( + context, + object, + property.asArgument(), + property.get(object) + ); + } finally { + context.removeLast(); } - - } - - private void validatePojoInternal(@NonNull DefaultConstraintValidatorContext context, - @NonNull BeanIntrospection introspection, - @NonNull T object, - @NonNull Class constraintAnnotation) { - validatePojoInternal(context, introspection.getBeanType(), object, constraintAnnotation, introspection.getAnnotation(constraintAnnotation)); - } - - private boolean doesRequireValidation(@NonNull DefaultConstraintValidatorContext context, - @NonNull Argument validatedArgument) { - return validatedArgument.getAnnotationMetadata().hasStereotype(Constraint.class) || - validatedArgument.getAnnotationMetadata().hasStereotype(Valid.class) || - doesHaveValidatedTypeParameters(context, validatedArgument); } private boolean canCascade(@NonNull DefaultConstraintValidatorContext context, - Object propertyValue, - Path.Node node) { + Object propertyValue) { final boolean isReachable = traversableResolver.isReachable( - propertyValue, - node, - context.getRootClass(), - context.currentPath, - ElementType.FIELD + propertyValue, + context.currentPath.nodes.peekLast(), + context.getRootClass(), + context.currentPath, + ElementType.FIELD ); if (!isReachable) { return false; } return traversableResolver.isCascadable( - propertyValue, - node, - context.getRootClass(), - context.currentPath, - ElementType.FIELD + propertyValue, + context.currentPath.nodes.peekLast(), + context.getRootClass(), + context.currentPath, + ElementType.FIELD ); } - /** - * Whether type parameters of a given element require validation. - * - * @param context the validation context - * @param validatedArgument the validated argument - */ - private boolean doesHaveValidatedTypeParameters(DefaultConstraintValidatorContext context, - Argument validatedArgument) { - if (!context.elementRequireCascadeValidation.containsKey(validatedArgument)) { - context.elementRequireCascadeValidation.put(validatedArgument, false); - - Argument[] arguments = validatedArgument.getTypeParameters(); - - for (Argument argument : arguments) { - AnnotationMetadata metadata = argument.getAnnotationMetadata(); - boolean hasValid = metadata.hasStereotype(Valid.class); - boolean hasConstraint = metadata.hasStereotype(Constraint.class); - if (hasValid || hasConstraint) { - context.elementRequireCascadeValidation.put(validatedArgument, true); - } else if (doesHaveValidatedTypeParameters(context, argument)) { - context.elementRequireCascadeValidation.put(validatedArgument, true); - } - } - } - - return context.elementRequireCascadeValidation.get(validatedArgument); + private void visitElement(DefaultConstraintValidatorContext context, + Object bean, + Argument annotatedElementType, + E elementValue) { + visitElement(context, + bean, + annotatedElementType, + elementValue, + annotatedElementType.getAnnotationMetadata().hasStereotype(Valid.class), + annotatedElementType.getAnnotationMetadata().hasStereotype(Constraint.class) + ); } - /** - * ValidatesElement on @Valid and Constraint annotations. - * Works for properties, method arguments and return values. - * For iterables validates iterable items with generic parameter annotations and iterables themselves. - * NOTE: IntrospectedTypeVisitor adds @Valid on iterable if its arguments have any annotations. - * NOTE: Removes the element node - * - * @param elementArgument - the type of annotatedElement (not essentially value type) - * @param elementValue - the value - * @param hasValidCascade - if it has Valid that is cascaded from above (e.g. in ReturnType the annotations are on - * the method itself and not on the ReturnType) - * @param hasConstraintCascade - if it has Constraints that cascaded from above - */ - private void validateElement(DefaultConstraintValidatorContext context, - Object leftBean, - Argument elementArgument, - E elementValue, - @Nullable Path.Node elementNode, - boolean hasValidCascade, - boolean hasConstraintCascade) { - - AnnotationMetadata annotationMetadata = elementArgument.getAnnotationMetadata(); - boolean hasValid = hasValidCascade || annotationMetadata.hasStereotype(Valid.class); - boolean hasConstraint = hasConstraintCascade || annotationMetadata.hasStereotype(Constraint.class); - boolean doesRequireCascadeValidation = doesHaveValidatedTypeParameters(context, elementArgument); - - if (hasValid || doesRequireCascadeValidation) { - try { - validateCascadeElement(context, leftBean, elementArgument, elementValue, elementNode); - } catch (Exception e) { - if (elementNode != null) { - context.removeLast(); - } - throw e; - } - } + private void visitElement(DefaultConstraintValidatorContext context, + Object leftBean, + Argument elementArgument, + E elementValue, + boolean hasValid, + boolean hasConstraint) { + + boolean iterableValidated = visitIterable(context, leftBean, elementArgument, elementValue); if (hasConstraint) { - validateConstrainedElement(context, leftBean, elementArgument, elementValue); + validateConstrains(context, leftBean, elementArgument, elementValue); } - if (elementNode != null) { - context.removeLast(); + if (hasValid && !iterableValidated) { + propagateValidation(context, leftBean, elementArgument, elementValue); } } - private void validateElement(DefaultConstraintValidatorContext context, - Object bean, - Argument annotatedElementType, - E elementValue, - Path.Node elementNode) { - validateElement(context, bean, annotatedElementType, elementValue, elementNode, false, false); - } + private void propagateValidation(DefaultConstraintValidatorContext context, + Object leftBean, + Argument elementType, + E elementValue) { - /** - * Validates element when it has @Valid annotation. - * Checks if it is an iterable and then validates its arguments. - * Otherwise cascades validation to the element - * - * @param elementType - the type of the element (this type will be used for getting value extractor in case of - * iterable and introspection in case of a cascade validation) - * @param elementValue - the value - * @param node - the node of this annotated element in the path - */ - private void validateCascadeElement(DefaultConstraintValidatorContext context, - Object leftBean, - Argument elementType, - E elementValue, - Path.Node node) { - // handle validation of iterables - boolean cascadedToIterable = validateIterable(context, leftBean, elementValue, elementType); - if (cascadedToIterable) { + // otherwise it needs cascading as a bean + if (elementValue == null || context.validatedObjects.contains(elementValue)) { return; } - - // otherwise it needs cascading as a bean - if (elementValue != null && !context.validatedObjects.contains(elementValue)) { - final BeanIntrospection beanIntrospection = getBeanIntrospection(elementValue, elementType.getType()); - - if (beanIntrospection == null) { - // Error if not introspected - DefaultConstraintViolation violation = createIntrospectionConstraintViolation(context, leftBean, elementType, elementValue); - context.overallViolations.add(violation); - } else if (canCascade(context, elementValue, node)) { - cascadeToObjectIntrospection(context, elementValue, beanIntrospection); - } + final BeanIntrospection beanIntrospection = getBeanIntrospection(elementValue, elementType.getType()); + if (beanIntrospection == null) { + // Error if not introspected + DefaultConstraintViolation violation = createIntrospectionConstraintViolation(context, leftBean, elementType, elementValue); + context.overallViolations.add(violation); + return; + } + if (canCascade(context, elementValue)) { + context.validatedObjects.add(elementValue); + doValidate(context, beanIntrospection, elementValue); } } @@ -1044,12 +933,12 @@ private void validateCascadeElement(DefaultConstraintValidatorContext * * @return - whether element was an iterable */ - private boolean validateIterable(DefaultConstraintValidatorContext context, - Object leftBean, - I iterable, - Argument iterableType) { + private boolean visitIterable(DefaultConstraintValidatorContext context, + Object leftBean, + Argument iterableArgument, + I iterable) { // Check if it has valueExtractor - final Optional> opt = valueExtractorRegistry.findValueExtractor(iterableType.getType()); + final Optional> opt = valueExtractorRegistry.findValueExtractor(iterableArgument.getType()); if (opt.isEmpty()) { return false; @@ -1059,162 +948,121 @@ private boolean validateIterable(DefaultConstraintValidatorContext con return true; } - // Get its type parameters - ValueExtractor valueExtractor = opt.get(); - - Argument[] arguments = iterableType.getTypeParameters(); + Argument[] arguments = iterableArgument.getTypeParameters(); // Check if its values need validation - final boolean keyValidation, valueValidation; + final boolean keyHasValid; + final boolean valueHasValid; + final boolean keyHasConstraint; + final boolean valueHasConstraint; + boolean iterableIsValid = iterableArgument.getAnnotationMetadata().hasStereotype(Valid.class); if (arguments.length == 1) { // Iterable with one generic parameter - keyValidation = false; - valueValidation = doesRequireValidation(context, arguments[0]) || isIterableRequiresValidation(iterableType); + keyHasConstraint = false; + keyHasValid = false; + valueHasValid = iterableIsValid || arguments[0].getAnnotationMetadata().hasStereotype(Valid.class); + valueHasConstraint = arguments[0].getAnnotationMetadata().hasStereotype(Constraint.class); } else if (arguments.length == 2) { - // Map has 2 parameters - keyValidation = doesRequireValidation(context, arguments[0]) || isIterableRequiresValidation(iterableType); - valueValidation = doesRequireValidation(context, arguments[1]) || isIterableRequiresValidation(iterableType); + keyHasValid = iterableIsValid || arguments[0].getAnnotationMetadata().hasStereotype(Valid.class); + keyHasConstraint = arguments[0].getAnnotationMetadata().hasStereotype(Constraint.class); + valueHasValid = iterableIsValid || arguments[1].getAnnotationMetadata().hasStereotype(Valid.class); + valueHasConstraint = arguments[1].getAnnotationMetadata().hasStereotype(Constraint.class); } else { - // Filling the final values - keyValidation = false; - valueValidation = false; - } - - if (!keyValidation && !valueValidation) { - return true; + // Some optional + keyHasConstraint = false; + keyHasValid = false; + valueHasConstraint = iterableArgument.getAnnotationMetadata().hasStereotype(Constraint.class); + valueHasValid = iterableIsValid; } // extract and validate values - valueExtractor.extractValues(iterable, new ValueExtractor.ValueReceiver() { + opt.get().extractValues(iterable, new ValueExtractor.ValueReceiver() { @Override public void value(String nodeName, Object value) { - Argument argument = (Argument) arguments[0]; - validateIterableValue(context, leftBean, iterableType, argument, value, null, null, false); + Argument argument = asArgument(value); + validateIterableValue(context, leftBean, nodeName, iterableArgument, argument, value, null, null, 0, false, valueHasValid, valueHasConstraint); } @Override public void iterableValue(String nodeName, Object iterableValue) { Argument argument = (Argument) arguments[0]; - validateIterableValue(context, leftBean, iterableType, argument, iterableValue, null, null, true); + validateIterableValue(context, leftBean, nodeName, iterableArgument, argument, iterableValue, null, null, 0, true, valueHasValid, valueHasConstraint); } @Override public void indexedValue(String nodeName, int i, Object iterableValue) { - Argument argument = (Argument) arguments[0]; - validateIterableValue(context, leftBean, iterableType, argument, iterableValue, i, null, true); + Argument argument = asArgument(iterableValue); + validateIterableValue(context, leftBean, nodeName, iterableArgument, argument, iterableValue, i, null, 0, true, valueHasValid, valueHasConstraint); } @Override public void keyedValue(String nodeName, Object key, Object keyedValue) { - if (keyValidation) { - Argument argument = (Argument) arguments[0]; - validateIterableValue(context, leftBean, iterableType, argument, key, null, key, true); - } + Argument argumentKey = (Argument) arguments[0]; + validateIterableValue(context, leftBean, "", iterableArgument, argumentKey, key, null, key, 0, true, keyHasValid, keyHasConstraint); + + Argument argumentValue = (Argument) arguments[1]; + validateIterableValue(context, leftBean, "", iterableArgument, argumentValue, keyedValue, null, key, 1, true, valueHasValid, valueHasConstraint); + } - if (valueValidation) { - Argument argument = (Argument) arguments[1]; - validateIterableValue(context, leftBean, iterableType, argument, keyedValue, null, key, true); + private Argument asArgument(Object value) { + Argument argument; + if (arguments.length == 0) { + argument = Argument.of(value == null ? Object.class : (Class) value.getClass(), iterableArgument.getAnnotationMetadata()); + } else { + argument = (Argument) arguments[0]; } + return argument; } + }); return true; } - /** - * Cascades to an element of iterable. - * - * @param iterableArgument - the type of annotated iterable - * @param valueArgument - the Argument representing iterable item - */ private void validateIterableValue(DefaultConstraintValidatorContext context, Object leftBean, + String name, Argument iterableArgument, Argument valueArgument, - E iterableValue, + E value, Integer index, Object key, - boolean isInIterable) { - AnnotationMetadata metadata = valueArgument.getAnnotationMetadata(); + Integer typeArgumentIndex, + boolean isInIterable, + boolean hasValid, + boolean hasConstraint) { - boolean hasValid = metadata.hasStereotype(Valid.class) || doesHaveValidatedTypeParameters(context, valueArgument) || isIterableRequiresValidation(iterableArgument); - boolean hasConstraint = metadata.hasStereotype(Constraint.class); - - if (!hasValid && !hasConstraint) { - return; + if (isInIterable) { + context.addContainerElementNode(name, iterableArgument.getType(), index, key, isInIterable, typeArgumentIndex); } - Path.Node node = context.addContainerElementNode(valueArgument, iterableArgument.getType(), index, key, isInIterable); - - if (hasValid) { - try { - validateCascadeElement(context, leftBean, valueArgument, iterableValue, node); - } catch (Exception e) { - context.removeLast(); - throw e; - } - } + visitElement(context, leftBean, valueArgument, value, hasValid, hasConstraint); - if (hasConstraint) { - validateConstrainedElement(context, leftBean, valueArgument, iterableValue); + if (isInIterable) { + context.removeLast(); } - - context.removeLast(); } - private static boolean isIterableRequiresValidation(Argument iterableArgument) { - // Validation 2 behaviour would validate the items if the container is annotated with @Valid - return iterableArgument.getAnnotationMetadata().hasStereotype(Valid.class); - } - - /** - * Validates the given object (all its properties) with its introspection. - * - * @param object - the object to validate - * @param beanIntrospection - its introspection - */ - private void cascadeToObjectIntrospection(@NonNull DefaultConstraintValidatorContext context, - @NonNull E object, - @NonNull BeanIntrospection beanIntrospection) { - context.validatedObjects.add(object); - - doValidate(context, beanIntrospection, object); - } - - /** - * Validates the constraints on the given value. - * - * @param leftBean - the object that this element belongs to (like object of property) - * @param elementArgument - the type of the value - * @param elementValue - the value to validate constraints - */ - private void validateConstrainedElement(DefaultConstraintValidatorContext context, - @Nullable Object leftBean, - @NonNull Argument elementArgument, - @Nullable E elementValue) { - final AnnotationMetadata annotationMetadata = elementArgument.getAnnotationMetadata(); - final List> constraintTypes = - annotationMetadata.getAnnotationTypesByStereotype(Constraint.class); - - final String currentMessageTemplate = context.getMessageTemplate().orElse(null); + private void validateConstrains(DefaultConstraintValidatorContext context, + @Nullable Object leftBean, + @NonNull Argument elementArgument, + @Nullable E elementValue) { - for (Class constraintType : constraintTypes) { + for (Class constraintType : elementArgument.getAnnotationMetadata().getAnnotationTypesByStereotype(Constraint.class)) { valueConstraintOnElement(context, leftBean, elementArgument, elementValue, constraintType); } - context.messageTemplate(currentMessageTemplate); } - private void valueConstraintOnElement(DefaultConstraintValidatorContext context, + private void valueConstraintOnElement(DefaultConstraintValidatorContext context, @Nullable Object leafBean, - Argument elementType, + Argument elementArgument, @Nullable E elementValue, Class constraintType) { - final AnnotationMetadata annotationMetadata = elementType.getAnnotationMetadata(); - List> annotationValues = annotationMetadata.getAnnotationValuesByType(constraintType); - Set> constraints = new LinkedHashSet<>(3); boolean isDefaultGroup = context.groups == DEFAULT_GROUPS || context.groups.contains(Default.class); - for (AnnotationValue annotationValue : annotationValues) { + List> annotationConstraints = elementArgument.getAnnotationMetadata().getAnnotationValuesByType(constraintType); + Set> constraints = CollectionUtils.newLinkedHashSet(annotationConstraints.size()); + for (AnnotationValue annotationValue : annotationConstraints) { final Class[] classValues = annotationValue.classValues("groups"); if (isDefaultGroup && ArrayUtils.isEmpty(classValues)) { constraints.add(annotationValue); @@ -1226,7 +1074,18 @@ private void valueConstraintOnElement(DefaultConstr } } - final ConstraintValidator validator = constraintValidatorRegistry.findConstraintValidator(constraintType, elementType.getType()).orElse(null); + validateConstraint(context, leafBean, elementArgument.getAnnotationMetadata(), elementValue, constraintType, constraints, elementArgument.getType()); + } + + private void validateConstraint(DefaultConstraintValidatorContext context, + Object leafBean, + AnnotationMetadata annotationMetadata, + E elementValue, + Class constraintType, + Set> constraints, + Class type) { + + ConstraintValidator validator = constraintValidatorRegistry.findConstraintValidator(constraintType, type).orElse(null); if (validator == null) { return; } @@ -1239,18 +1098,20 @@ private void valueConstraintOnElement(DefaultConstr final String message = messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(variables)); final ConstraintDescriptor constraintDescriptor = new DefaultConstraintDescriptor<>(annotationMetadata, constraintType, annotationValue); - context.overallViolations.add( - new DefaultConstraintViolation<>( + DefaultConstraintViolation constraintViolation = new DefaultConstraintViolation<>( context.getRootBean(), context.getRootClass(), leafBean, elementValue, message, messageTemplate, - new PathImpl(context.currentPath), constraintDescriptor, + new PathImpl(context.currentPath), + constraintDescriptor, context.executableParameterValues - ) ); + + context.overallViolations.add(constraintViolation); + context.messageTemplate(null); } } @@ -1280,9 +1141,9 @@ private String buildMessageTemplate(final DefaultConstraintValidatorContext< final AnnotationValue annotationValue, final AnnotationMetadata annotationMetadata) { return context.getMessageTemplate() - .orElseGet(() -> annotationValue.stringValue("message") - .orElseGet(() -> annotationMetadata.getDefaultValue(annotationValue.getAnnotationName(), "message", String.class) - .orElse("{" + annotationValue.getAnnotationName() + ".message}"))); + .orElseGet(() -> annotationValue.stringValue("message") + .orElseGet(() -> annotationMetadata.getDefaultValue(annotationValue.getAnnotationName(), "message", String.class) + .orElse("{" + annotationValue.getAnnotationName() + ".message}"))); } private void failOnError(@NonNull BeanResolutionContext resolutionContext, @@ -1290,9 +1151,9 @@ private void failOnError(@NonNull BeanResolutionContext resolutionContext, Class beanType) { if (!errors.isEmpty()) { StringBuilder builder = new StringBuilder() - .append("Validation failed for bean definition [") - .append(beanType.getName()) - .append("]\nList of constraint violations:[\n"); + .append("Validation failed for bean definition [") + .append(beanType.getName()) + .append("]\nList of constraint violations:[\n"); for (ConstraintViolation violation : errors) { builder.append('\t').append(violation.getPropertyPath()).append(" - ").append(violation.getMessage()).append('\n'); } @@ -1308,15 +1169,30 @@ private DefaultConstraintViolation createIntrospectionConstraintVio E invalidValue) { final String messageTemplate = context.getMessageTemplate().orElseGet(() -> "{" + Introspected.class.getName() + ".message}"); return new DefaultConstraintViolation<>( - context.getRootBean(), - context.getRootClass(), - leftBean, - invalidValue, - messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(Collections.singletonMap("type", invalidValueType.getType().getName()))), - messageTemplate, - new PathImpl(context.currentPath), - null, - context.executableParameterValues); + context.getRootBean(), + context.getRootClass(), + leftBean, + invalidValue, + messageSource.interpolate(messageTemplate, MessageSource.MessageContext.of(Collections.singletonMap("type", invalidValueType.getType().getName()))), + messageTemplate, + new PathImpl(context.currentPath), + null, + context.executableParameterValues); + } + + private List[]> findGroupSequences(Class[] groups) { + if (groups.length == 0 || groups.length > 1) { + return Collections.singletonList(groups); + } + List> groupSequence = GROUP_SEQUENCES.computeIfAbsent(groups[0], group -> { + return beanIntrospector.findIntrospection(group).stream() + .>flatMap(introspection -> Arrays.stream(introspection.classValues(GroupSequence.class))) + .toList(); + }); + if (groupSequence.isEmpty()) { + return Collections.singletonList(groups); + } + return groupSequence.stream().map(c -> new Class[]{c}).toList(); } /** @@ -1330,7 +1206,6 @@ private final class DefaultConstraintValidatorContext implements ConstraintVa private final Class rootClass; @Nullable private final Object[] executableParameterValues; - private final Map elementRequireCascadeValidation = new HashMap<>(1); private final Set validatedObjects = new HashSet<>(20); private final PathImpl currentPath; private final List> groups; @@ -1386,7 +1261,7 @@ private void sanityCheckGroups(Class[] groups) { } if (!clazz.isInterface()) { throw new IllegalArgumentException( - "Validation groups must be interfaces. " + clazz.getName() + " is not."); + "Validation groups must be interfaces. " + clazz.getName() + " is not."); } } } @@ -1438,19 +1313,19 @@ Path.Node addParameterNode(String name, int index) { return node; } - Path.Node addReturnValueNode(String name) { - final DefaultReturnValueNode returnValueNode = new DefaultReturnValueNode(name); + Path.Node addReturnValueNode() { + final DefaultReturnValueNode returnValueNode = new DefaultReturnValueNode(""); currentPath.nodes.add(returnValueNode); return returnValueNode; } - Path.Node addContainerElementNode(Argument elementArgument, + Path.Node addContainerElementNode(String name, Class containerClass, Integer index, Object key, - boolean isInIterable) { - final DefaultContainerElementNode node = new DefaultContainerElementNode( - elementArgument, containerClass, index, key, isInIterable); + boolean isInIterable, + Integer typeArgumentIndex) { + final DefaultContainerElementNode node = new DefaultContainerElementNode(name, containerClass, index, key, isInIterable, typeArgumentIndex); currentPath.nodes.add(node); return node; } @@ -1547,9 +1422,7 @@ public String toString() { builder.append(']'); } - if (node.getName() != null) { - builder.append('<').append(node.getName()).append('>'); - } + builder.append(node.getName()); } else { builder.append(firstNode ? "" : "."); builder.append(node.getName()); @@ -1598,6 +1471,9 @@ public String toString() { @Override public T as(Class nodeType) { + if (nodeType.isInstance(this)) { + return nodeType.cast(this); + } throw new UnsupportedOperationException("Unwrapping is unsupported by this implementation"); } } @@ -1703,34 +1579,29 @@ public ElementKind getKind() { /** * Default container element node implementation. */ - private static final class DefaultContainerElementNode - extends DefaultNode implements Path.ContainerElementNode { + private static final class DefaultContainerElementNode extends DefaultNode implements Path.ContainerElementNode { private final Class containerClass; private final Integer index; private final Object key; private final boolean isInIterable; - - public DefaultContainerElementNode( - @Nullable String name, Class containerClass, - @Nullable Integer index, @Nullable Object key, boolean isInIterable - ) { + private final Integer typeArgumentIndex; + + public DefaultContainerElementNode(@Nullable + String name, + Class containerClass, + @Nullable + Integer index, + @Nullable + Object key, + boolean isInIterable, + @Nullable + Integer typeArgumentIndex) { super(name); this.containerClass = containerClass; this.index = index; this.key = key; this.isInIterable = isInIterable; - } - - public DefaultContainerElementNode( - @Nullable Argument elementArgument, Class containerClass, - @Nullable Integer index, @Nullable Object key, boolean isInIterable - ) { - this(createName(elementArgument), containerClass, index, key, isInIterable); - } - - private static String createName(@Nullable Argument elementArgument) { - return elementArgument == null ? null : - elementArgument.getName() + " " + elementArgument.getType().getSimpleName(); + this.typeArgumentIndex = typeArgumentIndex; } @Override @@ -1740,7 +1611,7 @@ public Class getContainerClass() { @Override public Integer getTypeArgumentIndex() { - return null; + return typeArgumentIndex; } @Override @@ -1779,15 +1650,15 @@ public ElementKind getKind() { * @param The bean type. */ private record DefaultConstraintViolation( - @Nullable T rootBean, - @Nullable Class rootBeanClass, - Object leafBean, - Object invalidValue, - String message, - String messageTemplate, - Path path, - ConstraintDescriptor constraintDescriptor, - @Nullable Object[] executableParameterValues + @Nullable T rootBean, + @Nullable Class rootBeanClass, + Object leafBean, + Object invalidValue, + String message, + String messageTemplate, + Path path, + ConstraintDescriptor constraintDescriptor, + @Nullable Object[] executableParameterValues ) implements ConstraintViolation { @Override @@ -1818,8 +1689,8 @@ public Object getLeafBean() { @Override public Object[] getExecutableParameters() { return Objects.requireNonNullElse( - executableParameterValues, - ArrayUtils.EMPTY_OBJECT_ARRAY + executableParameterValues, + ArrayUtils.EMPTY_OBJECT_ARRAY ); } @@ -1851,10 +1722,10 @@ public U unwrap(Class type) { @Override public String toString() { return "DefaultConstraintViolation{" + - "rootBean=" + rootBeanClass + - ", invalidValue=" + invalidValue + - ", path=" + path + - '}'; + "rootBean=" + rootBeanClass + + ", invalidValue=" + invalidValue + + ", path=" + path + + '}'; } } @@ -1864,7 +1735,7 @@ public String toString() { * @param elementClass the class of element */ private record EmptyDescriptor( - Class elementClass + Class elementClass ) implements BeanDescriptor, ElementDescriptor.ConstraintFinder { @Override diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorConfiguration.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorConfiguration.java index ebb94250..6dffa5f4 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorConfiguration.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorConfiguration.java @@ -20,8 +20,10 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; +import io.micronaut.core.beans.BeanIntrospector; import io.micronaut.core.convert.ConversionService; import io.micronaut.core.convert.ConversionServiceAware; +import io.micronaut.core.type.Argument; import io.micronaut.core.util.Toggleable; import io.micronaut.validation.validator.constraints.ConstraintValidatorRegistry; import io.micronaut.validation.validator.constraints.DefaultConstraintValidators; @@ -29,18 +31,23 @@ import io.micronaut.validation.validator.extractors.ValueExtractorRegistry; import io.micronaut.validation.validator.messages.DefaultValidationMessages; import jakarta.inject.Inject; +import jakarta.validation.ClockProvider; +import jakarta.validation.ConstraintValidatorFactory; +import jakarta.validation.MessageInterpolator; +import jakarta.validation.ParameterNameProvider; +import jakarta.validation.Path; +import jakarta.validation.TraversableResolver; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorContext; +import jakarta.validation.valueextraction.ValueExtractor; -import javax.validation.ClockProvider; -import javax.validation.ConstraintValidatorFactory; -import javax.validation.MessageInterpolator; -import javax.validation.ParameterNameProvider; -import javax.validation.Path; -import javax.validation.TraversableResolver; -import javax.validation.Validator; -import javax.validation.ValidatorContext; -import javax.validation.valueextraction.ValueExtractor; import java.lang.annotation.ElementType; -import java.util.Objects; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; /** * The default configuration for the validator. @@ -71,6 +78,8 @@ public class DefaultValidatorConfiguration implements ValidatorConfiguration, To private ConversionService conversionService = ConversionService.SHARED; + private BeanIntrospector beanIntrospector = BeanIntrospector.SHARED; + private boolean enabled = true; /** @@ -92,7 +101,10 @@ public ConversionService getConversionService() { @Override @NonNull public ConstraintValidatorRegistry getConstraintValidatorRegistry() { - return Objects.requireNonNullElseGet(constraintValidatorRegistry, DefaultConstraintValidators::new); + if (constraintValidatorRegistry == null) { + constraintValidatorRegistry = new DefaultConstraintValidators(); + } + return constraintValidatorRegistry; } @Override @@ -113,6 +125,7 @@ public DefaultValidatorConfiguration setEnabled(boolean enabled) { /** * Sets the constraint validator registry to use. + * * @param constraintValidatorRegistry The registry to use * @return this configuration */ @@ -125,11 +138,15 @@ public DefaultValidatorConfiguration setConstraintValidatorRegistry(@Nullable Co @Override @NonNull public ValueExtractorRegistry getValueExtractorRegistry() { - return Objects.requireNonNullElseGet(valueExtractorRegistry, DefaultValueExtractors::new); + if (valueExtractorRegistry == null) { + valueExtractorRegistry = new DefaultValueExtractors(); + } + return valueExtractorRegistry; } /** * Sets the value extractor registry use. + * * @param valueExtractorRegistry The registry * @return this configuration */ @@ -142,11 +159,15 @@ public DefaultValidatorConfiguration setValueExtractorRegistry(@Nullable ValueEx @Override @NonNull public ClockProvider getClockProvider() { - return Objects.requireNonNullElseGet(clockProvider, DefaultClockProvider::new); + if (clockProvider == null) { + clockProvider = new DefaultClockProvider(); + } + return clockProvider; } /** * Sets the clock provider to use. + * * @param clockProvider The clock provider * @return this configuration */ @@ -159,21 +180,25 @@ public DefaultValidatorConfiguration setClockProvider(@Nullable ClockProvider cl @Override @NonNull public TraversableResolver getTraversableResolver() { - return Objects.requireNonNullElseGet(traversableResolver, () -> new TraversableResolver() { - @Override - public boolean isReachable(Object object, Path.Node node, Class rootType, Path path, ElementType elementType) { - return true; - } - - @Override - public boolean isCascadable(Object object, Path.Node node, Class rootType, Path path, ElementType elementType) { - return true; - } - }); + if (traversableResolver == null) { + traversableResolver = new TraversableResolver() { + @Override + public boolean isReachable(Object object, Path.Node node, Class rootType, Path path, ElementType elementType) { + return true; + } + + @Override + public boolean isCascadable(Object object, Path.Node node, Class rootType, Path path, ElementType elementType) { + return true; + } + }; + } + return traversableResolver; } /** * Sets the traversable resolver to use. + * * @param traversableResolver The resolver * @return This configuration */ @@ -186,10 +211,10 @@ public DefaultValidatorConfiguration setTraversableResolver(@Nullable Traversabl @Override @NonNull public MessageSource getMessageSource() { - if (messageSource != null) { - return messageSource; + if (messageSource == null) { + messageSource = new DefaultValidationMessages(); } - return new DefaultValidationMessages(); + return messageSource; } /** @@ -207,7 +232,10 @@ public DefaultValidatorConfiguration setMessageSource(@Nullable MessageSource me @Override @NonNull public ExecutionHandleLocator getExecutionHandleLocator() { - return Objects.requireNonNullElse(executionHandleLocator, ExecutionHandleLocator.EMPTY); + if (executionHandleLocator == null) { + executionHandleLocator = ExecutionHandleLocator.EMPTY; + } + return executionHandleLocator; } /** @@ -251,11 +279,62 @@ public ValidatorContext clockProvider(ClockProvider clockProvider) { @Override public ValidatorContext addValueExtractor(ValueExtractor extractor) { - throw new UnsupportedOperationException("Method addValueExtractor(..) not supported"); + List annotatedTypes = new ArrayList<>(); + determineValueExtractorDefinitions(annotatedTypes, extractor.getClass()); + if (annotatedTypes.size() != 1) { + throw new IllegalStateException("Expected to find one annotation type! Got: " + annotatedTypes); + } + Class clazz = (Class) Argument.of(annotatedTypes.get(0).getType()).getTypeParameters()[0].getType(); + ValueExtractor v = (ValueExtractor) extractor; + getValueExtractorRegistry().addValueExtractor(clazz, v); + return this; } @Override public Validator getValidator() { return new DefaultValidator(this); } + + @Override + public BeanIntrospector getBeanIntrospector() { + return beanIntrospector; + } + + public final void setBeanIntrospector(BeanIntrospector beanIntrospector) { + this.beanIntrospector = beanIntrospector; + } + + private static void determineValueExtractorDefinitions(List valueExtractorDefinitions, Class extractorImplementationType) { + if (!ValueExtractor.class.isAssignableFrom(extractorImplementationType)) { + return; + } + + Class superClass = extractorImplementationType.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) { + determineValueExtractorDefinitions(valueExtractorDefinitions, superClass); + } + for (Class implementedInterface : extractorImplementationType.getInterfaces()) { + if (!ValueExtractor.class.equals(implementedInterface)) { + determineValueExtractorDefinitions(valueExtractorDefinitions, implementedInterface); + } + } + for (AnnotatedType annotatedInterface : extractorImplementationType.getAnnotatedInterfaces()) { + if (ValueExtractor.class.equals(getClassFromType(annotatedInterface.getType()))) { + valueExtractorDefinitions.add(annotatedInterface); + } + } + } + + public static Class getClassFromType(Type type) { + if (type instanceof Class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + return getClassFromType(((ParameterizedType) type).getRawType()); + } + if (type instanceof GenericArrayType) { + return Object[].class; + } + throw new IllegalArgumentException(); + } } diff --git a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorFactory.java b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorFactory.java index d10f1850..af4e1899 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorFactory.java +++ b/validation/src/main/java/io/micronaut/validation/validator/DefaultValidatorFactory.java @@ -17,15 +17,16 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.Internal; +import jakarta.inject.Inject; import jakarta.inject.Singleton; -import javax.validation.ClockProvider; -import javax.validation.ConstraintValidatorFactory; -import javax.validation.MessageInterpolator; -import javax.validation.ParameterNameProvider; -import javax.validation.TraversableResolver; -import javax.validation.ValidatorContext; -import javax.validation.ValidatorFactory; +import jakarta.validation.ClockProvider; +import jakarta.validation.ConstraintValidatorFactory; +import jakarta.validation.MessageInterpolator; +import jakarta.validation.ParameterNameProvider; +import jakarta.validation.TraversableResolver; +import jakarta.validation.ValidatorContext; +import jakarta.validation.ValidatorFactory; /** * Default validator factory implementation. @@ -41,18 +42,34 @@ public class DefaultValidatorFactory implements ValidatorFactory { private final Validator validator; private final ValidatorConfiguration configuration; + /** + * The constructor. + */ + public DefaultValidatorFactory() { + this(new DefaultValidatorConfiguration()); + } + + /** + * The constructor. + * @param configuration The configuration. + */ + public DefaultValidatorFactory(ValidatorConfiguration configuration) { + this(new DefaultValidator(configuration), configuration); + } + /** * Default constructor. * @param validator The validator. * @param configuration The configuration. */ - protected DefaultValidatorFactory(Validator validator, ValidatorConfiguration configuration) { + @Inject + public DefaultValidatorFactory(Validator validator, ValidatorConfiguration configuration) { this.validator = validator; this.configuration = configuration; } @Override - public javax.validation.Validator getValidator() { + public jakarta.validation.Validator getValidator() { return validator; } diff --git a/validation/src/main/java/io/micronaut/validation/validator/ExecutableMethodValidator.java b/validation/src/main/java/io/micronaut/validation/validator/ExecutableMethodValidator.java index 6670fa03..48944cf2 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/ExecutableMethodValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/ExecutableMethodValidator.java @@ -22,9 +22,9 @@ import io.micronaut.core.type.MutableArgumentValue; import io.micronaut.inject.ExecutableMethod; -import javax.validation.ConstraintViolation; -import javax.validation.ConstraintViolationException; -import javax.validation.executable.ExecutableValidator; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.executable.ExecutableValidator; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.Collection; diff --git a/validation/src/main/java/io/micronaut/validation/validator/IntrospectedBeanDescriptor.java b/validation/src/main/java/io/micronaut/validation/validator/IntrospectedBeanDescriptor.java index 7692b922..8378f98d 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/IntrospectedBeanDescriptor.java +++ b/validation/src/main/java/io/micronaut/validation/validator/IntrospectedBeanDescriptor.java @@ -21,9 +21,9 @@ import io.micronaut.core.beans.BeanProperty; import io.micronaut.core.util.ArgumentUtils; -import javax.validation.Constraint; -import javax.validation.Valid; -import javax.validation.metadata.*; +import jakarta.validation.Constraint; +import jakarta.validation.Valid; +import jakarta.validation.metadata.*; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.util.Collections; diff --git a/validation/src/main/java/io/micronaut/validation/validator/ReactiveValidator.java b/validation/src/main/java/io/micronaut/validation/validator/ReactiveValidator.java index 2a261b36..3c0be137 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/ReactiveValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/ReactiveValidator.java @@ -31,7 +31,7 @@ public interface ReactiveValidator { /** * Validate the given publisher by returning a new Publisher that validates each emitted value. If a - * constraint violation error occurs a {@link javax.validation.ConstraintViolationException} will be thrown. + * constraint violation error occurs a {@link jakarta.validation.ConstraintViolationException} will be thrown. * * @param returnType The required type of publisher * @param publisher The publisher @@ -44,7 +44,7 @@ public interface ReactiveValidator { /** * Validate the given CompletionStage by returning a new CompletionStage that validates the emitted value. If a - * constraint violation error occurs a {@link javax.validation.ConstraintViolationException} will be thrown. + * constraint violation error occurs a {@link jakarta.validation.ConstraintViolationException} will be thrown. * * @param completionStage The completion stage * @param groups The groups diff --git a/validation/src/main/java/io/micronaut/validation/validator/Validator.java b/validation/src/main/java/io/micronaut/validation/validator/Validator.java index 76011710..66404444 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/Validator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/Validator.java @@ -19,20 +19,20 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.beans.BeanIntrospection; -import javax.validation.Constraint; -import javax.validation.ConstraintViolation; -import javax.validation.Valid; +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Valid; import java.util.Set; /** - * Extended version of the {@link javax.validation.Valid} interface for Micronaut's implementation. + * Extended version of the {@link jakarta.validation.Valid} interface for Micronaut's implementation. * *

The {@link #getConstraintsForClass(Class)} method is not supported by the implementation.

* * @author graemerocher * @since 1.2 */ -public interface Validator extends javax.validation.Validator { +public interface Validator extends jakarta.validation.Validator { /** * Annotation used to define an object as valid. diff --git a/validation/src/main/java/io/micronaut/validation/validator/ValidatorConfiguration.java b/validation/src/main/java/io/micronaut/validation/validator/ValidatorConfiguration.java index e37e87e2..fba1442c 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/ValidatorConfiguration.java +++ b/validation/src/main/java/io/micronaut/validation/validator/ValidatorConfiguration.java @@ -18,12 +18,13 @@ import io.micronaut.context.ExecutionHandleLocator; import io.micronaut.context.MessageSource; import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.beans.BeanIntrospector; import io.micronaut.core.convert.ConversionServiceProvider; import io.micronaut.validation.validator.constraints.ConstraintValidatorRegistry; import io.micronaut.validation.validator.extractors.ValueExtractorRegistry; -import javax.validation.ClockProvider; -import javax.validation.TraversableResolver; +import jakarta.validation.ClockProvider; +import jakarta.validation.TraversableResolver; /** * Configuration for the {@link Validator}. @@ -80,4 +81,11 @@ public interface ValidatorConfiguration extends ConversionServiceProvider { @NonNull ExecutionHandleLocator getExecutionHandleLocator(); + /** + * The bean introspector. + * @return The introspector + */ + default BeanIntrospector getBeanIntrospector() { + return BeanIntrospector.SHARED; + } } diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/AbstractPatternValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/AbstractPatternValidator.java index b50abbe9..1d3fa26b 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/AbstractPatternValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/AbstractPatternValidator.java @@ -18,8 +18,8 @@ import io.micronaut.core.annotation.AnnotationValue; import io.micronaut.core.annotation.NonNull; -import javax.validation.ValidationException; -import javax.validation.constraints.Pattern; +import jakarta.validation.ValidationException; +import jakarta.validation.constraints.Pattern; import java.lang.annotation.Annotation; import java.util.Map; import java.util.Optional; diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidator.java index 653a2cb6..23d97f96 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidator.java @@ -20,14 +20,14 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import javax.validation.ClockProvider; -import javax.validation.Constraint; +import jakarta.validation.ClockProvider; +import jakarta.validation.Constraint; import java.lang.annotation.Annotation; import java.util.Optional; /** * Constraint validator that can be used at either runtime or compilation time and - * is capable of validation {@link javax.validation.Constraint} instances. Allows defining validators that work with both Hibernate validator and Micronaut's validator. + * is capable of validation {@link jakarta.validation.Constraint} instances. Allows defining validators that work with both Hibernate validator and Micronaut's validator. * *

Unlike the specification's interface this one can uses as a functional interface. Implementor should not implement the {@link #initialize(Annotation)} method and should instead read the passed {@link AnnotationValue}.

* @@ -36,7 +36,7 @@ */ @Indexed(ConstraintValidator.class) @FunctionalInterface -public interface ConstraintValidator extends javax.validation.ConstraintValidator { +public interface ConstraintValidator extends jakarta.validation.ConstraintValidator { /** * A constraint validator that just returns the object as being valid. @@ -60,7 +60,7 @@ boolean isValid( @NonNull ConstraintValidatorContext context); @Override - default boolean isValid(T value, javax.validation.ConstraintValidatorContext context) { + default boolean isValid(T value, jakarta.validation.ConstraintValidatorContext context) { // simply adapt the interfaces for now. return isValid(value, new AnnotationValue<>(Constraint.class.getName()), new ConstraintValidatorContext() { diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorContext.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorContext.java index 1c5eab79..59d9a73d 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorContext.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorContext.java @@ -18,10 +18,10 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import javax.validation.ClockProvider; +import jakarta.validation.ClockProvider; /** - * Subset of the {@link javax.validation.ConstraintValidatorContext} interface without the unnecessary parts. + * Subset of the {@link jakarta.validation.ConstraintValidatorContext} interface without the unnecessary parts. * * @author graemerocher * @since 1.2 @@ -42,7 +42,7 @@ public interface ConstraintValidatorContext { @NonNull ClockProvider getClockProvider(); /** - * In case of using this constraint validator with {@code javax.validation.ConstraintValidator} returns null, because JRS-303 doesn't + * In case of using this constraint validator with {@code jakarta.validation.ConstraintValidator} returns null, because JRS-303 doesn't * support passing a root bean in their validation context. * * @return The root bean under validation. @@ -58,5 +58,5 @@ public interface ConstraintValidatorContext { default void messageTemplate(@Nullable final String messageTemplate) { throw new UnsupportedOperationException("not implemented"); } - + } diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorRegistry.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorRegistry.java index 81a41e45..5b541ff6 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorRegistry.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/ConstraintValidatorRegistry.java @@ -17,7 +17,7 @@ import io.micronaut.core.annotation.NonNull; -import javax.validation.ValidationException; +import jakarta.validation.ValidationException; import java.lang.annotation.Annotation; import java.util.Optional; diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMaxValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMaxValidator.java index bcfb60d8..49f57dbf 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMaxValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMaxValidator.java @@ -20,8 +20,8 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ConversionService; -import javax.validation.ValidationException; -import javax.validation.constraints.DecimalMax; +import jakarta.validation.ValidationException; +import jakarta.validation.constraints.DecimalMax; import java.math.BigDecimal; /** diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMinValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMinValidator.java index 52d87375..a14fd2b5 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMinValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/DecimalMinValidator.java @@ -20,8 +20,8 @@ import io.micronaut.core.annotation.Nullable; import io.micronaut.core.convert.ConversionService; -import javax.validation.ValidationException; -import javax.validation.constraints.DecimalMin; +import jakarta.validation.ValidationException; +import jakarta.validation.constraints.DecimalMin; import java.math.BigDecimal; /** diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/DefaultConstraintValidators.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/DefaultConstraintValidators.java index 5a11df2a..a5327325 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/DefaultConstraintValidators.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/DefaultConstraintValidators.java @@ -20,40 +20,28 @@ import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.beans.BeanProperty; -import io.micronaut.core.beans.BeanWrapper; import io.micronaut.core.reflect.ReflectionUtils; import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArgumentUtils; import io.micronaut.core.util.ArrayUtils; import io.micronaut.core.util.CollectionUtils; -import io.micronaut.core.util.StringUtils; import io.micronaut.core.util.clhm.ConcurrentLinkedHashMap; import io.micronaut.inject.qualifiers.Qualifiers; import io.micronaut.inject.qualifiers.TypeArgumentQualifier; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import jakarta.validation.constraints.DecimalMax; +import jakarta.validation.constraints.DecimalMin; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; -import javax.validation.ValidationException; -import javax.validation.constraints.*; import java.lang.annotation.Annotation; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.*; -import java.time.chrono.HijrahDate; -import java.time.chrono.JapaneseDate; -import java.time.chrono.MinguoDate; -import java.time.chrono.ThaiBuddhistDate; -import java.time.temporal.TemporalAccessor; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; +import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; -import java.util.Date; -import java.util.concurrent.atomic.DoubleAccumulator; -import java.util.concurrent.atomic.DoubleAdder; /** * A factory bean that contains implementation for many of the default validations. @@ -67,300 +55,12 @@ @Introspected public class DefaultConstraintValidators implements ConstraintValidatorRegistry { - private final Map, ConstraintValidator> validatorCache = new ConcurrentLinkedHashMap. - Builder, ConstraintValidator>().initialCapacity(10).maximumWeightedCapacity(40).build(); + private final Map> validatorCache = new ConcurrentLinkedHashMap. + Builder>().initialCapacity(10).maximumWeightedCapacity(40).build(); - private final ConstraintValidator assertFalseValidator = - (value, annotationMetadata, context) -> value == null || !value; - - private final ConstraintValidator assertTrueValidator = - (value, annotationMetadata, context) -> value == null || value; - - private final DecimalMaxValidator decimalMaxValidatorCharSequence = - (value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal); - - private final DecimalMaxValidator decimalMaxValidatorNumber = DefaultConstraintValidators::compareNumber; - - private final DecimalMinValidator decimalMinValidatorCharSequence = - (value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal); - - private final DecimalMinValidator decimalMinValidatorNumber = DefaultConstraintValidators::compareNumber; - - private final DigitsValidator digitsValidatorNumber = value -> { - if (value instanceof BigDecimal) { - return (BigDecimal) value; - } - return new BigDecimal(value.toString()); - }; - - private final DigitsValidator digitsValidatorCharSequence = - value -> new BigDecimal(value.toString()); - - private final ConstraintValidator maxNumberValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - return true; // nulls are allowed according to spec - } - final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() -> - new ValidationException("@Max annotation specified without value") - ); - - if (value instanceof BigInteger) { - return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) <= 0; - } else if (value instanceof BigDecimal) { - return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) <= 0; - } - return value.longValue() <= max; - }; - - private final ConstraintValidator minNumberValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - return true; // nulls are allowed according to spec - } - final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() -> - new ValidationException("@Min annotation specified without value") - ); - - if (value instanceof BigInteger) { - return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) >= 0; - } else if (value instanceof BigDecimal) { - return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) >= 0; - } - return value.longValue() >= max; - }; - - private final ConstraintValidator negativeNumberValidator = - (value, annotationMetadata, context) -> { - // null is allowed according to spec - if (value == null) { - return true; - } - if (value instanceof BigDecimal) { - return ((BigDecimal) value).signum() < 0; - } - if (value instanceof BigInteger) { - return ((BigInteger) value).signum() < 0; - } - if (value instanceof Double || - value instanceof Float || - value instanceof DoubleAdder || - value instanceof DoubleAccumulator) { - return value.doubleValue() < 0; - } - return value.longValue() < 0; - }; - - private final ConstraintValidator negativeOrZeroNumberValidator = - (value, annotationMetadata, context) -> { - // null is allowed according to spec - if (value == null) { - return true; - } - if (value instanceof BigDecimal) { - return ((BigDecimal) value).signum() <= 0; - } - if (value instanceof BigInteger) { - return ((BigInteger) value).signum() <= 0; - } - if (value instanceof Double || - value instanceof Float || - value instanceof DoubleAdder || - value instanceof DoubleAccumulator) { - return value.doubleValue() <= 0; - } - return value.longValue() <= 0; - }; - - private final ConstraintValidator positiveNumberValidator = - (value, annotationMetadata, context) -> { - // null is allowed according to spec - if (value == null) { - return true; - } - if (value instanceof BigDecimal) { - return ((BigDecimal) value).signum() > 0; - } - if (value instanceof BigInteger) { - return ((BigInteger) value).signum() > 0; - } - if (value instanceof Double || - value instanceof Float || - value instanceof DoubleAdder || - value instanceof DoubleAccumulator) { - return value.doubleValue() > 0; - } - return value.longValue() > 0; - }; - - private final ConstraintValidator positiveOrZeroNumberValidator = - (value, annotationMetadata, context) -> { - // null is allowed according to spec - if (value == null) { - return true; - } - if (value instanceof BigDecimal) { - return ((BigDecimal) value).signum() >= 0; - } - if (value instanceof BigInteger) { - return ((BigInteger) value).signum() >= 0; - } - if (value instanceof Double || - value instanceof Float || - value instanceof DoubleAdder || - value instanceof DoubleAccumulator) { - return value.doubleValue() >= 0; - } - return value.longValue() >= 0; - }; - - private final ConstraintValidator notBlankValidator = - (value, annotationMetadata, context) -> - StringUtils.isNotEmpty(value) && value.toString().trim().length() > 0; - - private final ConstraintValidator notNullValidator = - (value, annotationMetadata, context) -> value != null; - - private final ConstraintValidator nullValidator = - (value, annotationMetadata, context) -> value == null; - - private final ConstraintValidator notEmptyByteArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyCharArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyBooleanArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyDoubleArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyFloatArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyIntArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyLongArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyObjectArrayValidator = (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyShortArrayValidator = - (value, annotationMetadata, context) -> value != null && value.length > 0; - - private final ConstraintValidator notEmptyCharSequenceValidator = - (value, annotationMetadata, context) -> StringUtils.isNotEmpty(value); - - private final ConstraintValidator notEmptyCollectionValidator = - (value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value); - - private final ConstraintValidator notEmptyMapValidator = - (value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value); - - private final SizeValidator sizeObjectArrayValidator = value -> value.length; - - private final SizeValidator sizeByteArrayValidator = value -> value.length; - - private final SizeValidator sizeCharArrayValidator = value -> value.length; - - private final SizeValidator sizeBooleanArrayValidator = value -> value.length; - - private final SizeValidator sizeDoubleArrayValidator = value -> value.length; - - private final SizeValidator sizeFloatArrayValidator = value -> value.length; - - private final SizeValidator sizeIntArrayValidator = value -> value.length; - - private final SizeValidator sizeLongArrayValidator = value -> value.length; - - private final SizeValidator sizeShortArrayValidator = value -> value.length; - - private final SizeValidator sizeCharSequenceValidator = CharSequence::length; - - private final SizeValidator sizeCollectionValidator = Collection::size; - - private final SizeValidator sizeMapValidator = Map::size; - - private final ConstraintValidator pastTemporalAccessorConstraintValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = getNow(value, context.getClockProvider().getClock()); - return comparable.compareTo(value) > 0; - }; - - private final ConstraintValidator pastDateConstraintValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); - return comparable.compareTo(value) > 0; - }; - - private final ConstraintValidator pastOrPresentTemporalAccessorConstraintValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = getNow(value, context.getClockProvider().getClock()); - return comparable.compareTo(value) >= 0; - }; - - private final ConstraintValidator pastOrPresentDateConstraintValidator = - (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); - return comparable.compareTo(value) >= 0; - }; - - private final ConstraintValidator futureTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = getNow(value, context.getClockProvider().getClock()); - return comparable.compareTo(value) < 0; - }; - - private final ConstraintValidator futureDateConstraintValidator = (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); - return comparable.compareTo(value) < 0; - }; - - private final ConstraintValidator futureOrPresentTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = getNow(value, context.getClockProvider().getClock()); - return comparable.compareTo(value) <= 0; - }; - - private final ConstraintValidator futureOrPresentDateConstraintValidator = (value, annotationMetadata, context) -> { - if (value == null) { - // null is valid according to spec - return true; - } - Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); - return comparable.compareTo(value) <= 0; - }; - - private final @Nullable BeanContext beanContext; - private final Map, ConstraintValidator> localValidators; + @Nullable + private final BeanContext beanContext; + private final Map> internalValidators; /** * Default constructor. @@ -375,56 +75,49 @@ public DefaultConstraintValidators() { * @param beanContext The bean context */ @Inject - protected DefaultConstraintValidators(@Nullable BeanContext beanContext) { + public DefaultConstraintValidators(@Nullable BeanContext beanContext) { this.beanContext = beanContext; - BeanWrapper wrapper = BeanWrapper.findWrapper(DefaultConstraintValidators.class, this).orElse(null); - if (wrapper != null) { - - final Collection> beanProperties = wrapper.getBeanProperties(); - Map, ConstraintValidator> validatorMap = CollectionUtils.newHashMap(beanProperties.size()); - for (BeanProperty property : beanProperties) { - if (ConstraintValidator.class.isAssignableFrom(property.getType())) { - final Argument[] typeParameters = property.asArgument().getTypeParameters(); - if (ArrayUtils.isNotEmpty(typeParameters)) { - final int len = typeParameters.length; - - wrapper.getProperty(property.getName(), ConstraintValidator.class).ifPresent(constraintValidator -> { - if (len == 2) { - final Class targetType = ReflectionUtils.getWrapperType(typeParameters[1].getType()); - final ValidatorKey key = new ValidatorKey<>(typeParameters[0].getType(), targetType); - validatorMap.put(key, constraintValidator); - } else if (len == 1) { - Class type = typeParameters[0].getType(); - if (constraintValidator instanceof SizeValidator) { - final ValidatorKey key = new ValidatorKey<>(Size.class, type); - validatorMap.put(key, constraintValidator); - } else if (constraintValidator instanceof DigitsValidator) { - final ValidatorKey key = new ValidatorKey<>(Digits.class, type); - validatorMap.put(key, constraintValidator); - } else if (constraintValidator instanceof DecimalMaxValidator) { - final ValidatorKey key = new ValidatorKey<>(DecimalMax.class, type); - validatorMap.put(key, constraintValidator); - } else if (constraintValidator instanceof DecimalMinValidator) { - final ValidatorKey key = new ValidatorKey<>(DecimalMin.class, type); - validatorMap.put(key, constraintValidator); - } - } - }); - } + List, ConstraintValidator>> constraintValidators = InternalConstraintValidators.getConstraintValidators(); + Map> validatorMap = CollectionUtils.newHashMap(constraintValidators.size()); + for (Map.Entry, ConstraintValidator> entry : constraintValidators) { + Argument definition = entry.getKey(); + ConstraintValidator constraintValidator = entry.getValue(); + final Argument[] typeParameters = definition.getTypeParameters(); + if (ArrayUtils.isEmpty(typeParameters)) { + continue; + } + + final int len = typeParameters.length; + if (len == 2) { + final Class targetType = ReflectionUtils.getWrapperType(typeParameters[1].getType()); + final ValidatorKey key = new ValidatorKey(typeParameters[0].getType(), targetType); + validatorMap.put(key, constraintValidator); + } else if (len == 1) { + Class type = typeParameters[0].getType(); + if (constraintValidator instanceof SizeValidator) { + final ValidatorKey key = new ValidatorKey(Size.class, type); + validatorMap.put(key, constraintValidator); + } else if (constraintValidator instanceof DigitsValidator) { + final ValidatorKey key = new ValidatorKey(Digits.class, type); + validatorMap.put(key, constraintValidator); + } else if (constraintValidator instanceof DecimalMaxValidator) { + final ValidatorKey key = new ValidatorKey(DecimalMax.class, type); + validatorMap.put(key, constraintValidator); + } else if (constraintValidator instanceof DecimalMinValidator) { + final ValidatorKey key = new ValidatorKey(DecimalMin.class, type); + validatorMap.put(key, constraintValidator); } } - validatorMap.put( - new ValidatorKey<>(Pattern.class, CharSequence.class), - new PatternValidator() - ); - validatorMap.put( - new ValidatorKey<>(Email.class, CharSequence.class), - new EmailValidator() - ); - this.localValidators = validatorMap; - } else { - this.localValidators = Collections.emptyMap(); } + validatorMap.put( + new ValidatorKey(Pattern.class, CharSequence.class), + new PatternValidator() + ); + validatorMap.put( + new ValidatorKey(Email.class, CharSequence.class), + new EmailValidator() + ); + this.internalValidators = validatorMap; } @SuppressWarnings("unchecked") @@ -433,9 +126,9 @@ protected DefaultConstraintValidators(@Nullable BeanContext beanContext) { public Optional> findConstraintValidator(@NonNull Class constraintType, @NonNull Class targetType) { ArgumentUtils.requireNonNull("constraintType", constraintType); ArgumentUtils.requireNonNull("targetType", targetType); - final ValidatorKey key = new ValidatorKey(constraintType, targetType); - Class targetWrapperType = (Class) ReflectionUtils.getWrapperType(targetType); - ConstraintValidator constraintValidator = localValidators.get(key); + final ValidatorKey key = new ValidatorKey(constraintType, targetType); + targetType = (Class) ReflectionUtils.getWrapperType(targetType); + ConstraintValidator constraintValidator = internalValidators.get(key); if (constraintValidator != null) { return Optional.of((ConstraintValidator) constraintValidator); } else { @@ -443,23 +136,16 @@ public Optional> findConstra if (constraintValidator != null) { return Optional.of((ConstraintValidator) constraintValidator); } else { - Argument> argument = (Argument) Argument.of(ConstraintValidator.class); - final Qualifier> qualifier = Qualifiers.byTypeArguments( - constraintType, - ReflectionUtils.getWrapperType(targetWrapperType) - ); - Class finalTargetType = targetWrapperType; - final Class[] finalTypeArguments = {constraintType, finalTargetType}; - Optional> local = localValidators.entrySet().stream().filter(entry -> { - final ValidatorKey k = entry.getKey(); - return TypeArgumentQualifier.areTypesCompatible(finalTypeArguments, Arrays.asList(k.constraintType, k.targetType)); - } - ).map(Map.Entry::getValue).findFirst(); - + Optional> local = findInternalConstraintValidator(constraintType, targetType); if (local.isPresent()) { validatorCache.put(key, local.get()); - return (Optional) local; + return local; } else if (beanContext != null) { + Argument> argument = (Argument) Argument.of(ConstraintValidator.class); + final Qualifier> qualifier = Qualifiers.byTypeArguments( + constraintType, + targetType + ); Optional> bean = beanContext.findBean(argument, qualifier); if (bean.isEmpty()) { validatorCache.put(key, ConstraintValidator.VALID); @@ -470,11 +156,11 @@ public Optional> findConstra } } else { // last chance lookup - final ConstraintValidator cv = findLocalConstraintValidator(constraintType, targetWrapperType) - .orElse((ConstraintValidator) ConstraintValidator.VALID); + final ConstraintValidator cv = findLocalConstraintValidator(constraintType, targetType) + .orElse((ConstraintValidator) ConstraintValidator.VALID); validatorCache.put(key, cv); if (cv != ConstraintValidator.VALID) { - return Optional.of((ConstraintValidator) cv); + return Optional.of(cv); } } } @@ -482,565 +168,40 @@ public Optional> findConstra return Optional.empty(); } - /** - * The {@link AssertFalse} validator. - * - * @return The validator - */ - public ConstraintValidator getAssertFalseValidator() { - return assertFalseValidator; - } - - /** - * The {@link AssertTrue} validator. - * - * @return The validator - */ - public ConstraintValidator getAssertTrueValidator() { - return assertTrueValidator; - } - - /** - * The {@link DecimalMax} validator for char sequences. - * - * @return The validator - */ - public DecimalMaxValidator getDecimalMaxValidatorCharSequence() { - return decimalMaxValidatorCharSequence; - } - - /** - * The {@link DecimalMax} validator for number. - * - * @return The validator - */ - public DecimalMaxValidator getDecimalMaxValidatorNumber() { - return decimalMaxValidatorNumber; - } - - /** - * The {@link DecimalMin} validator for char sequences. - * - * @return The validator - */ - public DecimalMinValidator getDecimalMinValidatorCharSequence() { - return decimalMinValidatorCharSequence; - } - - /** - * The {@link DecimalMin} validator for number. - * - * @return The validator - */ - public DecimalMinValidator getDecimalMinValidatorNumber() { - return decimalMinValidatorNumber; - } - - /** - * The {@link Digits} validator for number. - * - * @return The validator - */ - public DigitsValidator getDigitsValidatorNumber() { - return digitsValidatorNumber; - } - - /** - * The {@link Digits} validator for char sequence. - * - * @return The validator - */ - public DigitsValidator getDigitsValidatorCharSequence() { - return digitsValidatorCharSequence; - } - - /** - * The {@link Max} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getMaxNumberValidator() { - return maxNumberValidator; - } - - /** - * The {@link Min} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getMinNumberValidator() { - return minNumberValidator; - } - - /** - * The {@link Negative} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getNegativeNumberValidator() { - return negativeNumberValidator; - } - - /** - * The {@link NegativeOrZero} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getNegativeOrZeroNumberValidator() { - return negativeOrZeroNumberValidator; - } - - /** - * The {@link Positive} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getPositiveNumberValidator() { - return positiveNumberValidator; - } - - /** - * The {@link PositiveOrZero} validator for numbers. - * - * @return The validator - */ - public ConstraintValidator getPositiveOrZeroNumberValidator() { - return positiveOrZeroNumberValidator; - } - - /** - * The {@link NotBlank} validator for char sequences. - * - * @return The validator - */ - public ConstraintValidator getNotBlankValidator() { - return notBlankValidator; - } - - /** - * The {@link NotNull} validator. - * - * @return The validator - */ - public ConstraintValidator getNotNullValidator() { - return notNullValidator; - } - - /** - * The {@link Null} validator. - * - * @return The validator - */ - public ConstraintValidator getNullValidator() { - return nullValidator; - } - - /** - * The {@link NotEmpty} validator for byte[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyByteArrayValidator() { - return notEmptyByteArrayValidator; - } - - /** - * The {@link NotEmpty} validator for char[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyCharArrayValidator() { - return notEmptyCharArrayValidator; - } - - /** - * The {@link NotEmpty} validator for boolean[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyBooleanArrayValidator() { - return notEmptyBooleanArrayValidator; - } - - /** - * The {@link NotEmpty} validator for double[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyDoubleArrayValidator() { - return notEmptyDoubleArrayValidator; - } - - /** - * The {@link NotEmpty} validator for float[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyFloatArrayValidator() { - return notEmptyFloatArrayValidator; - } - - /** - * The {@link NotEmpty} validator for int[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyIntArrayValidator() { - return notEmptyIntArrayValidator; - } - - /** - * The {@link NotEmpty} validator for long[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyLongArrayValidator() { - return notEmptyLongArrayValidator; - } - - /** - * The {@link NotEmpty} validator for Object[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyObjectArrayValidator() { - return notEmptyObjectArrayValidator; - } - - /** - * The {@link NotEmpty} validator for short[]. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyShortArrayValidator() { - return notEmptyShortArrayValidator; - } - - /** - * The {@link NotEmpty} validator for char sequence. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyCharSequenceValidator() { - return notEmptyCharSequenceValidator; - } - - /** - * The {@link NotEmpty} validator for collection. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyCollectionValidator() { - return notEmptyCollectionValidator; - } - - /** - * The {@link NotEmpty} validator for map. - * - * @return The validator - */ - public ConstraintValidator getNotEmptyMapValidator() { - return notEmptyMapValidator; - } - - /** - * The {@link Size} validator for Object[]. - * - * @return The validator - */ - public SizeValidator getSizeObjectArrayValidator() { - return sizeObjectArrayValidator; - } - - /** - * The {@link Size} validator for byte[]. - * - * @return The validator - */ - public SizeValidator getSizeByteArrayValidator() { - return sizeByteArrayValidator; - } - - /** - * The {@link Size} validator for char[]. - * - * @return The validator - */ - public SizeValidator getSizeCharArrayValidator() { - return sizeCharArrayValidator; - } - - /** - * The {@link Size} validator for boolean[]. - * - * @return The validator - */ - public SizeValidator getSizeBooleanArrayValidator() { - return sizeBooleanArrayValidator; - } - - /** - * The {@link Size} validator for double[]. - * - * @return The validator - */ - public SizeValidator getSizeDoubleArrayValidator() { - return sizeDoubleArrayValidator; - } - - /** - * The {@link Size} validator for float[]. - * - * @return The validator - */ - public SizeValidator getSizeFloatArrayValidator() { - return sizeFloatArrayValidator; - } - - /** - * The {@link Size} validator for int[]. - * - * @return The validator - */ - public SizeValidator getSizeIntArrayValidator() { - return sizeIntArrayValidator; - } - - /** - * The {@link Size} validator for long[]. - * - * @return The validator - */ - public SizeValidator getSizeLongArrayValidator() { - return sizeLongArrayValidator; - } - - /** - * The {@link Size} validator for short[]. - * - * @return The validator - */ - public SizeValidator getSizeShortArrayValidator() { - return sizeShortArrayValidator; - } - - /** - * The {@link Size} validator for CharSequence. - * - * @return The validator - */ - public SizeValidator getSizeCharSequenceValidator() { - return sizeCharSequenceValidator; - } - - /** - * The {@link Size} validator for Collection. - * - * @return The validator - */ - public SizeValidator getSizeCollectionValidator() { - return sizeCollectionValidator; - } - - /** - * The {@link Size} validator for Map. - * - * @return The validator - */ - public SizeValidator getSizeMapValidator() { - return sizeMapValidator; - } - - /** - * The {@link Past} validator for temporal accessor. - * - * @return The validator - */ - public ConstraintValidator getPastTemporalAccessorConstraintValidator() { - return pastTemporalAccessorConstraintValidator; - } - - /** - * The {@link Past} validator for Date accessor. - * - * @return The validator - */ - public ConstraintValidator getPastDateConstraintValidator() { - return pastDateConstraintValidator; - } - - /** - * The {@link PastOrPresent} validator for temporal accessor. - * - * @return The validator - */ - public ConstraintValidator getPastOrPresentTemporalAccessorConstraintValidator() { - return pastOrPresentTemporalAccessorConstraintValidator; - } - - /** - * The {@link PastOrPresent} validator for Date accessor. - * - * @return The validator - */ - public ConstraintValidator getPastOrPresentDateConstraintValidator() { - return pastOrPresentDateConstraintValidator; - } - - /** - * The {@link Future} validator for temporal accessor. - * - * @return The validator - */ - public ConstraintValidator getFutureTemporalAccessorConstraintValidator() { - return futureTemporalAccessorConstraintValidator; - } - - /** - * The {@link Future} validator for Date accessor. - * - * @return The validator - */ - public ConstraintValidator getFutureDateConstraintValidator() { - return futureDateConstraintValidator; - } - - /** - * The {@link FutureOrPresent} validator for temporal accessor. - * - * @return The validator - */ - public ConstraintValidator getFutureOrPresentTemporalAccessorConstraintValidator() { - return futureOrPresentTemporalAccessorConstraintValidator; - } - - /** - * The {@link FutureOrPresent} validator for Date accessor. - * - * @return The validator - */ - public ConstraintValidator getFutureOrPresentDateConstraintValidator() { - return futureOrPresentDateConstraintValidator; + private Optional> findInternalConstraintValidator(Class constraintType, + Class validationType) { + final Class[] finalTypeArguments = {constraintType, validationType}; + return internalValidators.entrySet().stream() + .filter(entry -> { + final ValidatorKey k = entry.getKey(); + return TypeArgumentQualifier.areTypesCompatible(finalTypeArguments, Arrays.asList(k.constraintType, k.targetType)); + } + ).map(e -> (ConstraintValidator) e.getValue()) + .findFirst(); } /** * Last chance resolve for constraint validator. + * * @param constraintType The constraint type - * @param targetType The target type - * @param The annotation type - * @param The target type + * @param targetType The target type + * @param The annotation type + * @param The target type * @return The validator if present */ - protected Optional> findLocalConstraintValidator( - @NonNull Class constraintType, @NonNull Class targetType) { + protected Optional> findLocalConstraintValidator(@NonNull Class constraintType, + @NonNull Class targetType) { return Optional.empty(); } - private Comparable getNow(TemporalAccessor value, Clock clock) { - if (!(value instanceof Comparable)) { - throw new IllegalArgumentException("TemporalAccessor value must be comparable"); - } - - if (value instanceof LocalDateTime) { - return LocalDateTime.now(clock); - } else if (value instanceof Instant) { - return Instant.now(clock); - } else if (value instanceof ZonedDateTime) { - return ZonedDateTime.now(clock); - } else if (value instanceof OffsetDateTime) { - return OffsetDateTime.now(clock); - } else if (value instanceof LocalDate) { - return LocalDate.now(clock); - } else if (value instanceof LocalTime) { - return LocalTime.now(clock); - } else if (value instanceof OffsetTime) { - return OffsetTime.now(clock); - } else if (value instanceof MonthDay) { - return MonthDay.now(clock); - } else if (value instanceof Year) { - return Year.now(clock); - } else if (value instanceof YearMonth) { - return YearMonth.now(clock); - } else if (value instanceof HijrahDate) { - return HijrahDate.now(clock); - } else if (value instanceof JapaneseDate) { - return JapaneseDate.now(clock); - } else if (value instanceof ThaiBuddhistDate) { - return ThaiBuddhistDate.now(clock); - } else if (value instanceof MinguoDate) { - return MinguoDate.now(clock); - } - throw new IllegalArgumentException("TemporalAccessor value type not supported: " + value.getClass()); - } - - /** - * Performs the comparision for number. - * @param value The value - * @param bigDecimal The big decimal - * @return The result - */ - private static int compareNumber(@NonNull Number value, @NonNull BigDecimal bigDecimal) { - int result; - if (value instanceof BigDecimal) { - result = ((BigDecimal) value).compareTo(bigDecimal); - } else if (value instanceof BigInteger) { - result = new BigDecimal((BigInteger) value).compareTo(bigDecimal); - } else { - result = BigDecimal.valueOf(value.doubleValue()).compareTo(bigDecimal); - } - return result; - } - /** * Key for caching validators. - * @param The annotation type - * @param The target type. + * + * @param constraintType The constraint type + * @param targetType The target type */ - protected final class ValidatorKey { - private final Class constraintType; - private final Class targetType; - - /** - * The key to lookup the validator. - * @param constraintType THe constraint type - * @param targetType The target type - */ - public ValidatorKey(@NonNull Class constraintType, @NonNull Class targetType) { - this.constraintType = constraintType; - this.targetType = targetType; - } - - /** - * @return The constraint type - */ - public Class getConstraintType() { - return constraintType; - } - - /** - * @return The target type - */ - public Class getTargetType() { - return targetType; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ValidatorKey key = (ValidatorKey) o; - return constraintType.equals(key.constraintType) && - targetType.equals(key.targetType); - } - - @Override - public int hashCode() { - return Objects.hash(constraintType, targetType); - } + public record ValidatorKey(@NonNull Class constraintType, + @NonNull Class targetType) { } } diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/DigitsValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/DigitsValidator.java index 80a6fbbe..979182f5 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/DigitsValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/DigitsValidator.java @@ -19,7 +19,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import javax.validation.constraints.Digits; +import jakarta.validation.constraints.Digits; import java.math.BigDecimal; /** diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/EmailValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/EmailValidator.java index 98bfe8ea..7d4e8244 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/EmailValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/EmailValidator.java @@ -26,7 +26,7 @@ import io.micronaut.core.annotation.Nullable; import jakarta.inject.Singleton; -import javax.validation.constraints.Email; +import jakarta.validation.constraints.Email; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/InternalConstraintValidators.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/InternalConstraintValidators.java new file mode 100644 index 00000000..72233669 --- /dev/null +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/InternalConstraintValidators.java @@ -0,0 +1,480 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.validator.constraints; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.annotation.NonNull; +import io.micronaut.core.beans.BeanWrapper; +import io.micronaut.core.type.Argument; +import io.micronaut.core.util.CollectionUtils; +import io.micronaut.core.util.StringUtils; +import jakarta.validation.ValidationException; +import jakarta.validation.constraints.AssertFalse; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Future; +import jakarta.validation.constraints.FutureOrPresent; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.Negative; +import jakarta.validation.constraints.NegativeOrZero; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Null; +import jakarta.validation.constraints.Past; +import jakarta.validation.constraints.PastOrPresent; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.MonthDay; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Year; +import java.time.YearMonth; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.chrono.HijrahDate; +import java.time.chrono.JapaneseDate; +import java.time.chrono.MinguoDate; +import java.time.chrono.ThaiBuddhistDate; +import java.time.temporal.TemporalAccessor; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.atomic.DoubleAccumulator; +import java.util.concurrent.atomic.DoubleAdder; + +/** + * Default local validators. + * @author Denis Stepanov + */ +@Introspected(accessKind = Introspected.AccessKind.FIELD) +final class InternalConstraintValidators { + + final ConstraintValidator assertFalseValidator = + (value, annotationMetadata, context) -> value == null || !value; + + final ConstraintValidator assertTrueValidator = + (value, annotationMetadata, context) -> value == null || value; + + final DecimalMaxValidator decimalMaxValidatorCharSequence = + (value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal); + + final DecimalMaxValidator decimalMaxValidatorNumber = InternalConstraintValidators::compareNumber; + + final DecimalMinValidator decimalMinValidatorCharSequence = + (value, bigDecimal) -> new BigDecimal(value.toString()).compareTo(bigDecimal); + + final DecimalMinValidator decimalMinValidatorNumber = InternalConstraintValidators::compareNumber; + + final DigitsValidator digitsValidatorNumber = value -> { + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + return new BigDecimal(value.toString()); + }; + + final DigitsValidator digitsValidatorCharSequence = + value -> new BigDecimal(value.toString()); + + final ConstraintValidator maxNumberValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + return true; // nulls are allowed according to spec + } + final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() -> + new ValidationException("@Max annotation specified without value") + ); + + if (value instanceof BigInteger) { + return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) <= 0; + } else if (value instanceof BigDecimal) { + return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) <= 0; + } + return value.longValue() <= max; + }; + + final ConstraintValidator minNumberValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + return true; // nulls are allowed according to spec + } + final Long max = annotationMetadata.getValue(Long.class).orElseThrow(() -> + new ValidationException("@Min annotation specified without value") + ); + + if (value instanceof BigInteger) { + return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) >= 0; + } else if (value instanceof BigDecimal) { + return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) >= 0; + } + return value.longValue() >= max; + }; + + final ConstraintValidator negativeNumberValidator = + (value, annotationMetadata, context) -> { + // null is allowed according to spec + if (value == null) { + return true; + } + if (value instanceof BigDecimal) { + return ((BigDecimal) value).signum() < 0; + } + if (value instanceof BigInteger) { + return ((BigInteger) value).signum() < 0; + } + if (value instanceof Double || + value instanceof Float || + value instanceof DoubleAdder || + value instanceof DoubleAccumulator) { + return value.doubleValue() < 0; + } + return value.longValue() < 0; + }; + + final ConstraintValidator negativeOrZeroNumberValidator = + (value, annotationMetadata, context) -> { + // null is allowed according to spec + if (value == null) { + return true; + } + if (value instanceof BigDecimal) { + return ((BigDecimal) value).signum() <= 0; + } + if (value instanceof BigInteger) { + return ((BigInteger) value).signum() <= 0; + } + if (value instanceof Double || + value instanceof Float || + value instanceof DoubleAdder || + value instanceof DoubleAccumulator) { + return value.doubleValue() <= 0; + } + return value.longValue() <= 0; + }; + + final ConstraintValidator positiveNumberValidator = + (value, annotationMetadata, context) -> { + // null is allowed according to spec + if (value == null) { + return true; + } + if (value instanceof BigDecimal) { + return ((BigDecimal) value).signum() > 0; + } + if (value instanceof BigInteger) { + return ((BigInteger) value).signum() > 0; + } + if (value instanceof Double || + value instanceof Float || + value instanceof DoubleAdder || + value instanceof DoubleAccumulator) { + return value.doubleValue() > 0; + } + return value.longValue() > 0; + }; + + final ConstraintValidator positiveOrZeroNumberValidator = + (value, annotationMetadata, context) -> { + // null is allowed according to spec + if (value == null) { + return true; + } + if (value instanceof BigDecimal) { + return ((BigDecimal) value).signum() >= 0; + } + if (value instanceof BigInteger) { + return ((BigInteger) value).signum() >= 0; + } + if (value instanceof Double || + value instanceof Float || + value instanceof DoubleAdder || + value instanceof DoubleAccumulator) { + return value.doubleValue() >= 0; + } + return value.longValue() >= 0; + }; + + final ConstraintValidator notBlankValidator = + (value, annotationMetadata, context) -> + StringUtils.isNotEmpty(value) && value.toString().trim().length() > 0; + + final ConstraintValidator notNullValidator = + (value, annotationMetadata, context) -> value != null; + + final ConstraintValidator nullValidator = + (value, annotationMetadata, context) -> value == null; + + final ConstraintValidator notEmptyByteArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyCharArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyBooleanArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyDoubleArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyFloatArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyIntArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyLongArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyObjectArrayValidator = (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyShortArrayValidator = + (value, annotationMetadata, context) -> value != null && value.length > 0; + + final ConstraintValidator notEmptyCharSequenceValidator = + (value, annotationMetadata, context) -> StringUtils.isNotEmpty(value); + + final ConstraintValidator notEmptyCollectionValidator = + (value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value); + + final ConstraintValidator notEmptyMapValidator = + (value, annotationMetadata, context) -> CollectionUtils.isNotEmpty(value); + + final SizeValidator sizeObjectArrayValidator = value -> value.length; + + final SizeValidator sizeByteArrayValidator = value -> value.length; + + final SizeValidator sizeCharArrayValidator = value -> value.length; + + final SizeValidator sizeBooleanArrayValidator = value -> value.length; + + final SizeValidator sizeDoubleArrayValidator = value -> value.length; + + final SizeValidator sizeFloatArrayValidator = value -> value.length; + + final SizeValidator sizeIntArrayValidator = value -> value.length; + + final SizeValidator sizeLongArrayValidator = value -> value.length; + + final SizeValidator sizeShortArrayValidator = value -> value.length; + + final SizeValidator sizeCharSequenceValidator = CharSequence::length; + + final SizeValidator sizeCollectionValidator = Collection::size; + + final SizeValidator sizeMapValidator = Map::size; + + final ConstraintValidator pastTemporalAccessorConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(value, context.getClockProvider().getClock()); + return comparable.compareTo(value) > 0; + }; + + final ConstraintValidator pastDateConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); + return comparable.compareTo(value) > 0; + }; + + final ConstraintValidator pastCalendarConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(LocalDateTime.now(), context.getClockProvider().getClock()); + return comparable.compareTo(toLocalDateTime(value)) > 0; + }; + + final ConstraintValidator pastOrPresentTemporalAccessorConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(value, context.getClockProvider().getClock()); + return comparable.compareTo(value) >= 0; + }; + + final ConstraintValidator pastOrPresentDateConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); + return comparable.compareTo(value) >= 0; + }; + + final ConstraintValidator pastOrPresentCalendarConstraintValidator = + (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(LocalDateTime.now(), context.getClockProvider().getClock()); + return comparable.compareTo(toLocalDateTime(value)) >= 0; + }; + + final ConstraintValidator futureTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(value, context.getClockProvider().getClock()); + return comparable.compareTo(value) < 0; + }; + + final ConstraintValidator futureCalendarConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(LocalDateTime.now(), context.getClockProvider().getClock()); + return comparable.compareTo(toLocalDateTime(value)) < 0; + }; + + final ConstraintValidator futureDateConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); + return comparable.compareTo(value) < 0; + }; + + final ConstraintValidator futureOrPresentTemporalAccessorConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(value, context.getClockProvider().getClock()); + return comparable.compareTo(value) <= 0; + }; + + final ConstraintValidator futureOrPresentDateConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = Date.from(context.getClockProvider().getClock().instant()); + return comparable.compareTo(value) <= 0; + }; + + final ConstraintValidator futureOrPresentCalendarConstraintValidator = (value, annotationMetadata, context) -> { + if (value == null) { + // null is valid according to spec + return true; + } + Comparable comparable = getNow(LocalDateTime.now(), context.getClockProvider().getClock()); + return comparable.compareTo(toLocalDateTime(value)) <= 0; + }; + + /** + * Performs the comparison for number. + * + * @param value The value + * @param bigDecimal The big decimal + * @return The result + */ + private static int compareNumber(@NonNull Number value, @NonNull BigDecimal bigDecimal) { + int result; + if (value instanceof BigDecimal) { + result = ((BigDecimal) value).compareTo(bigDecimal); + } else if (value instanceof BigInteger) { + result = new BigDecimal((BigInteger) value).compareTo(bigDecimal); + } else { + result = BigDecimal.valueOf(value.doubleValue()).compareTo(bigDecimal); + } + return result; + } + + public static LocalDateTime toLocalDateTime(Calendar calendar) { + if (calendar == null) { + return null; + } + TimeZone tz = calendar.getTimeZone(); + ZoneId zid = tz == null ? ZoneId.systemDefault() : tz.toZoneId(); + return LocalDateTime.ofInstant(calendar.toInstant(), zid); + } + + private static Comparable getNow(TemporalAccessor value, Clock clock) { + if (!(value instanceof Comparable)) { + throw new IllegalArgumentException("TemporalAccessor value must be comparable"); + } + + if (value instanceof LocalDateTime) { + return LocalDateTime.now(clock); + } else if (value instanceof Instant) { + return Instant.now(clock); + } else if (value instanceof ZonedDateTime) { + return ZonedDateTime.now(clock); + } else if (value instanceof OffsetDateTime) { + return OffsetDateTime.now(clock); + } else if (value instanceof LocalDate) { + return LocalDate.now(clock); + } else if (value instanceof LocalTime) { + return LocalTime.now(clock); + } else if (value instanceof OffsetTime) { + return OffsetTime.now(clock); + } else if (value instanceof MonthDay) { + return MonthDay.now(clock); + } else if (value instanceof Year) { + return Year.now(clock); + } else if (value instanceof YearMonth) { + return YearMonth.now(clock); + } else if (value instanceof HijrahDate) { + return HijrahDate.now(clock); + } else if (value instanceof JapaneseDate) { + return JapaneseDate.now(clock); + } else if (value instanceof ThaiBuddhistDate) { + return ThaiBuddhistDate.now(clock); + } else if (value instanceof MinguoDate) { + return MinguoDate.now(clock); + } + throw new IllegalArgumentException("TemporalAccessor value type not supported: " + value.getClass()); + } + + public static List, ConstraintValidator>> getConstraintValidators() { + InternalConstraintValidators bean = new InternalConstraintValidators(); + BeanWrapper wrapper = BeanWrapper.findWrapper(InternalConstraintValidators.class, bean).orElse(null); + if (wrapper == null) { + throw new IllegalArgumentException("Cannot retrieve constraint validators"); + } + return wrapper.getBeanProperties() + .stream() + ., ConstraintValidator>>map(p -> Map.entry(p.asArgument(), (ConstraintValidator) p.get(bean))) + .toList(); + } +} diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/PatternValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/PatternValidator.java index 267ba113..df29a275 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/PatternValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/PatternValidator.java @@ -20,7 +20,7 @@ import io.micronaut.core.annotation.Nullable; import jakarta.inject.Singleton; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.Pattern; /** * Validator for the {@link Pattern} annotation. diff --git a/validation/src/main/java/io/micronaut/validation/validator/constraints/SizeValidator.java b/validation/src/main/java/io/micronaut/validation/validator/constraints/SizeValidator.java index 5c3f9b40..c707e51f 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/constraints/SizeValidator.java +++ b/validation/src/main/java/io/micronaut/validation/validator/constraints/SizeValidator.java @@ -19,7 +19,7 @@ import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.Size; /** * Abstract implementation of a {@link Size} validator. diff --git a/validation/src/main/java/io/micronaut/validation/validator/extractors/DefaultValueExtractors.java b/validation/src/main/java/io/micronaut/validation/validator/extractors/DefaultValueExtractors.java index a1b700b1..b5f437af 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/extractors/DefaultValueExtractors.java +++ b/validation/src/main/java/io/micronaut/validation/validator/extractors/DefaultValueExtractors.java @@ -20,17 +20,20 @@ import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.NonNull; import io.micronaut.core.annotation.Nullable; -import io.micronaut.core.beans.BeanProperty; -import io.micronaut.core.beans.BeanWrapper; import io.micronaut.core.type.Argument; import io.micronaut.core.util.ArrayUtils; import io.micronaut.core.util.CollectionUtils; import jakarta.inject.Inject; import jakarta.inject.Singleton; +import jakarta.validation.valueextraction.UnwrapByDefault; +import jakarta.validation.valueextraction.ValueExtractor; -import javax.validation.valueextraction.UnwrapByDefault; -import javax.validation.valueextraction.ValueExtractor; -import java.util.*; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; /** * The default value extractors. @@ -42,95 +45,8 @@ @Introspected public class DefaultValueExtractors implements ValueExtractorRegistry { - /** - * Node name for an element of an iterable. - */ - public static final String ITERABLE_ELEMENT_NODE_NAME = ""; - /** - * Node name for an element of an indexed iterable. - */ - public static final String LIST_ELEMENT_NODE_NAME = ""; - /** - * Node name for an value element of a map. - */ - public static final String MAP_VALUE_NODE_NAME = ""; - - private final UnwrapByDefaultValueExtractor optionalValueExtractor = - (originalValue, receiver) -> receiver.value(null, originalValue.orElse(null)); - private final UnwrapByDefaultValueExtractor optionalIntValueExtractor = - (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsInt() : null); - private final UnwrapByDefaultValueExtractor optionalLongValueExtractor = - (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsLong() : null); - private final UnwrapByDefaultValueExtractor optionalDoubleValueExtractor = - (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsDouble() : null); - - private final ValueExtractor iterableValueExtractor = (originalValue, receiver) -> { - if (originalValue instanceof List) { - int i = 0; - for (Object o : originalValue) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i++, o); - } - } else { - for (Object o : originalValue) { - receiver.iterableValue(ITERABLE_ELEMENT_NODE_NAME, o); - } - } - }; - private final ValueExtractor> mapValueExtractor = (originalValue, receiver) -> { - for (Map.Entry entry : originalValue.entrySet()) { - receiver.keyedValue(MAP_VALUE_NODE_NAME, entry.getKey(), entry.getValue()); - } - }; - private final ValueExtractor objectArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor intArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor byteArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor booleanArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor doubleArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor charArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor floatArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final ValueExtractor shortArrayValueExtractor = (originalValue, receiver) -> { - for (int i = 0; i < originalValue.length; i++) { - receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i, originalValue[i]); - } - }; - - private final Map valueExtractors; - private final Set unwrapByDefaultTypes = new HashSet<>(5); + private final Map, ValueExtractor> valueExtractors; + private final Set> unwrapByDefaultTypes = new HashSet<>(5); /** * Default constructor. @@ -146,7 +62,6 @@ public DefaultValueExtractors() { */ @Inject protected DefaultValueExtractors(@Nullable BeanContext beanContext) { - BeanWrapper wrapper = BeanWrapper.findWrapper(this).orElse(null); Map, ValueExtractor> extractorMap = new HashMap<>(); if (beanContext != null && beanContext.containsBean(ValueExtractor.class)) { @@ -165,155 +80,36 @@ protected DefaultValueExtractors(@Nullable BeanContext beanContext) { } } } - - if (wrapper != null) { - - final Collection> properties = wrapper.getBeanProperties(); - for (BeanProperty property : properties) { - if (ValueExtractor.class.isAssignableFrom(property.getType())) { - final ValueExtractor valueExtractor = wrapper.getProperty(property.getName(), ValueExtractor.class).orElse(null); - final Class targetType = property.asArgument().getFirstTypeVariable().map(Argument::getType).orElse(null); - extractorMap.put(targetType, valueExtractor); - if (valueExtractor instanceof UnwrapByDefaultValueExtractor || valueExtractor.getClass().isAnnotationPresent(UnwrapByDefault.class)) { - unwrapByDefaultTypes.add(targetType); - } - } + for (Map.Entry, ValueExtractor> entry : InternalValueExtractors.getValueExtractors()) { + final Argument definition = entry.getKey(); + final ValueExtractor valueExtractor = entry.getValue(); + final Class targetType = definition.getFirstTypeVariable().map(Argument::getType).orElse(null); + extractorMap.put(targetType, valueExtractor); + if (valueExtractor instanceof UnwrapByDefaultValueExtractor || valueExtractor.getClass().isAnnotationPresent(UnwrapByDefault.class)) { + unwrapByDefaultTypes.add(targetType); } - this.valueExtractors = new HashMap<>(extractorMap.size()); - this.valueExtractors.putAll(extractorMap); - } else { - this.valueExtractors = Collections.emptyMap(); } + this.valueExtractors = CollectionUtils.newHashMap(extractorMap.size()); + this.valueExtractors.putAll(extractorMap); } - /** - * Value extractor for optional. - * - * @return The value extractor. - */ - public UnwrapByDefaultValueExtractor getOptionalValueExtractor() { - return optionalValueExtractor; - } - - /** - * Value extractor for {@link OptionalInt}. - * - * @return The value extractor - */ - public UnwrapByDefaultValueExtractor getOptionalIntValueExtractor() { - return optionalIntValueExtractor; - } - - /** - * Value extractor for {@link OptionalLong}. - * - * @return The value extractor - */ - public UnwrapByDefaultValueExtractor getOptionalLongValueExtractor() { - return optionalLongValueExtractor; - } - - /** - * Value extractor for {@link OptionalDouble}. - * - * @return The value extractor - */ - public UnwrapByDefaultValueExtractor getOptionalDoubleValueExtractor() { - return optionalDoubleValueExtractor; - } - - /** - * Value extractor for iterable. - * @return The value extractor - */ - public ValueExtractor getIterableValueExtractor() { - return iterableValueExtractor; - } - - /** - * Value extractor for iterable. - * @return The value extractor - */ - public ValueExtractor> getMapValueExtractor() { - return mapValueExtractor; - } - - /** - * Value extractor for Object[]. - * @return The object[] extractor - */ - public ValueExtractor getObjectArrayValueExtractor() { - return objectArrayValueExtractor; - } - - /** - * Value extractor for int[]. - * @return The int[] extractor - */ - public ValueExtractor getIntArrayValueExtractor() { - return intArrayValueExtractor; - } - - /** - * Value extractor for byte[]. - * @return The byte[] extractor - */ - public ValueExtractor getByteArrayValueExtractor() { - return byteArrayValueExtractor; - } - - /** - * Value extractor for char[]. - * @return The char[] extractor - */ - public ValueExtractor getCharArrayValueExtractor() { - return charArrayValueExtractor; - } - - /** - * Value extractor for boolean[]. - * @return The boolean[] extractor - */ - public ValueExtractor getBooleanArrayValueExtractor() { - return booleanArrayValueExtractor; - } - - /** - * Value extractor for double[]. - * @return The double[] extractor - */ - public ValueExtractor getDoubleArrayValueExtractor() { - return doubleArrayValueExtractor; - } - - /** - * Value extractor for float[]. - * @return The float[] extractor - */ - public ValueExtractor getFloatArrayValueExtractor() { - return floatArrayValueExtractor; - } - - /** - * Value extractor for short[]. - * @return The short[] extractor - */ - public ValueExtractor getShortArrayValueExtractor() { - return shortArrayValueExtractor; + @Override + public void addValueExtractor(Class targetType, ValueExtractor valueExtractor) { + valueExtractors.put(targetType, valueExtractor); } @SuppressWarnings("unchecked") @NonNull @Override public Optional> findValueExtractor(@NonNull Class targetType) { - final ValueExtractor valueExtractor = valueExtractors.get(targetType); + final ValueExtractor valueExtractor = (ValueExtractor) valueExtractors.get(targetType); if (valueExtractor != null) { return Optional.of(valueExtractor); } else { - return (Optional) valueExtractors.entrySet().stream() - .filter(entry -> entry.getKey().isAssignableFrom(targetType)) - .map(Map.Entry::getValue) - .findFirst(); + return valueExtractors.entrySet().stream() + .filter(entry -> entry.getKey().isAssignableFrom(targetType)) + .map(e -> (ValueExtractor) e.getValue()) + .findFirst(); } } @@ -322,7 +118,7 @@ public Optional> findValueExtractor(@NonNull Class targ @Override public Optional> findUnwrapValueExtractor(@NonNull Class targetType) { if (unwrapByDefaultTypes.contains(targetType)) { - return Optional.ofNullable(valueExtractors.get(targetType)); + return Optional.ofNullable((ValueExtractor) valueExtractors.get(targetType)); } return Optional.empty(); } diff --git a/validation/src/main/java/io/micronaut/validation/validator/extractors/InternalValueExtractors.java b/validation/src/main/java/io/micronaut/validation/validator/extractors/InternalValueExtractors.java new file mode 100644 index 00000000..23dc5663 --- /dev/null +++ b/validation/src/main/java/io/micronaut/validation/validator/extractors/InternalValueExtractors.java @@ -0,0 +1,90 @@ +/* + * Copyright 2017-2023 original 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 io.micronaut.validation.validator.extractors; + +import io.micronaut.core.annotation.Introspected; +import io.micronaut.core.beans.BeanWrapper; +import io.micronaut.core.type.Argument; +import jakarta.validation.valueextraction.ValueExtractor; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +/** + * The internal extractors. + * + * @author Denis Stepanov + */ +@Introspected(accessKind = Introspected.AccessKind.FIELD) +final class InternalValueExtractors { + + /** + * Node name for an element of an iterable. + */ + private static final String ITERABLE_ELEMENT_NODE_NAME = ""; + /** + * Node name for an element of an indexed iterable. + */ + private static final String LIST_ELEMENT_NODE_NAME = ""; + /** + * Node name for an value element of a map. + */ + private static final String MAP_VALUE_NODE_NAME = ""; + + final UnwrapByDefaultValueExtractor> optionalValueExtractor = + (originalValue, receiver) -> receiver.value(null, originalValue.orElse(null)); + final UnwrapByDefaultValueExtractor optionalIntValueExtractor = + (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsInt() : null); + final UnwrapByDefaultValueExtractor optionalLongValueExtractor = + (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsLong() : null); + final UnwrapByDefaultValueExtractor optionalDoubleValueExtractor = + (originalValue, receiver) -> receiver.value(null, originalValue.isPresent() ? originalValue.getAsDouble() : null); + + final ValueExtractor> iterableValueExtractor = (originalValue, receiver) -> { + if (originalValue instanceof List) { + int i = 0; + for (Object o : originalValue) { + receiver.indexedValue(LIST_ELEMENT_NODE_NAME, i++, o); + } + } else { + for (Object o : originalValue) { + receiver.iterableValue(ITERABLE_ELEMENT_NODE_NAME, o); + } + } + }; + final ValueExtractor> mapValueExtractor = (originalValue, receiver) -> { + for (Map.Entry entry : originalValue.entrySet()) { + receiver.keyedValue(MAP_VALUE_NODE_NAME, entry.getKey(), entry.getValue()); + } + }; + + public static List, ValueExtractor>> getValueExtractors() { + InternalValueExtractors bean = new InternalValueExtractors(); + BeanWrapper wrapper = BeanWrapper.findWrapper(InternalValueExtractors.class, bean).orElse(null); + if (wrapper == null) { + throw new IllegalArgumentException("Cannot retrieve constraint validators"); + } + return wrapper.getBeanProperties() + .stream() + ., ValueExtractor>>map(p -> Map.entry(p.asArgument(), (ValueExtractor) p.get(bean))) + .toList(); + } + +} diff --git a/validation/src/main/java/io/micronaut/validation/validator/extractors/SimpleValueReceiver.java b/validation/src/main/java/io/micronaut/validation/validator/extractors/SimpleValueReceiver.java index 11fd107b..2dc667cf 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/extractors/SimpleValueReceiver.java +++ b/validation/src/main/java/io/micronaut/validation/validator/extractors/SimpleValueReceiver.java @@ -15,7 +15,7 @@ */ package io.micronaut.validation.validator.extractors; -import javax.validation.valueextraction.ValueExtractor; +import jakarta.validation.valueextraction.ValueExtractor; /** * No-op implementation that makes it easier to use with Lambdas. diff --git a/validation/src/main/java/io/micronaut/validation/validator/extractors/UnwrapByDefaultValueExtractor.java b/validation/src/main/java/io/micronaut/validation/validator/extractors/UnwrapByDefaultValueExtractor.java index a17ad5a4..dc00c50c 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/extractors/UnwrapByDefaultValueExtractor.java +++ b/validation/src/main/java/io/micronaut/validation/validator/extractors/UnwrapByDefaultValueExtractor.java @@ -15,7 +15,7 @@ */ package io.micronaut.validation.validator.extractors; -import javax.validation.valueextraction.ValueExtractor; +import jakarta.validation.valueextraction.ValueExtractor; /** * Interface based alternative for unwrap by default semantics. diff --git a/validation/src/main/java/io/micronaut/validation/validator/extractors/ValueExtractorRegistry.java b/validation/src/main/java/io/micronaut/validation/validator/extractors/ValueExtractorRegistry.java index 0a8325e0..05c32846 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/extractors/ValueExtractorRegistry.java +++ b/validation/src/main/java/io/micronaut/validation/validator/extractors/ValueExtractorRegistry.java @@ -17,8 +17,8 @@ import io.micronaut.core.annotation.NonNull; -import javax.validation.ValidationException; -import javax.validation.valueextraction.ValueExtractor; +import jakarta.validation.ValidationException; +import jakarta.validation.valueextraction.ValueExtractor; import java.util.Optional; /** @@ -28,15 +28,17 @@ * @since 1.2 */ public interface ValueExtractorRegistry { + + void addValueExtractor(Class targetType, ValueExtractor valueExtractor); + /** - * Finds a a {@link ValueExtractor} for the given type. + * Finds a {@link ValueExtractor} for the given type. * @param targetType The target type of the value * @param The target type * @return The extractor */ @NonNull - Optional> findValueExtractor( - @NonNull Class targetType); + Optional> findValueExtractor(@NonNull Class targetType); /** * Finds a concrete {@link ValueExtractor} without searching the hierarchy. @@ -45,8 +47,7 @@ Optional> findValueExtractor( * @return The extractor */ @NonNull - Optional> findUnwrapValueExtractor( - @NonNull Class targetType); + Optional> findUnwrapValueExtractor(@NonNull Class targetType); /** * Gets a a {@link ValueExtractor} for the given type. @@ -56,9 +57,7 @@ Optional> findUnwrapValueExtractor( * @throws ValidationException if no extractor is present */ @NonNull - default ValueExtractor getValueExtractor( - @NonNull Class targetType) { - return findValueExtractor(targetType) - .orElseThrow(() -> new ValidationException("No value extractor for target type [" + targetType + "]")); + default ValueExtractor getValueExtractor(@NonNull Class targetType) { + return findValueExtractor(targetType).orElseThrow(() -> new ValidationException("No value extractor for target type [" + targetType + "]")); } } diff --git a/validation/src/main/java/io/micronaut/validation/validator/messages/DefaultValidationMessages.java b/validation/src/main/java/io/micronaut/validation/validator/messages/DefaultValidationMessages.java index a33b8d09..520afc63 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/messages/DefaultValidationMessages.java +++ b/validation/src/main/java/io/micronaut/validation/validator/messages/DefaultValidationMessages.java @@ -19,7 +19,7 @@ import io.micronaut.core.annotation.Introspected; import jakarta.inject.Singleton; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; /** * The default error messages. diff --git a/validation/src/main/java/io/micronaut/validation/validator/resolver/CompositeTraversableResolver.java b/validation/src/main/java/io/micronaut/validation/validator/resolver/CompositeTraversableResolver.java index 7a04ebfa..22a97c81 100644 --- a/validation/src/main/java/io/micronaut/validation/validator/resolver/CompositeTraversableResolver.java +++ b/validation/src/main/java/io/micronaut/validation/validator/resolver/CompositeTraversableResolver.java @@ -20,8 +20,8 @@ import io.micronaut.core.util.CollectionUtils; import jakarta.inject.Singleton; -import javax.validation.Path; -import javax.validation.TraversableResolver; +import jakarta.validation.Path; +import jakarta.validation.TraversableResolver; import java.lang.annotation.ElementType; import java.util.List; diff --git a/validation/src/test/groovy/io/micronaut/validation/BeanIntrospectionSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/BeanIntrospectionSpec.groovy index 3895d9b6..d3670a53 100644 --- a/validation/src/test/groovy/io/micronaut/validation/BeanIntrospectionSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/BeanIntrospectionSpec.groovy @@ -17,9 +17,9 @@ import io.micronaut.validation.visitor.ValidationVisitor import jakarta.inject.Singleton import javax.annotation.processing.SupportedAnnotationTypes -import javax.validation.Constraint -import javax.validation.constraints.Min -import javax.validation.constraints.NotBlank +import jakarta.validation.Constraint +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank class BeanIntrospectionSpec extends AbstractTypeElementSpec { @@ -30,7 +30,7 @@ package test; import io.micronaut.core.annotation.Creator; import java.util.List; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; @io.micronaut.core.annotation.Introspected public record Foo(List<@Min(10) Long> value){ @@ -54,7 +54,7 @@ package test; import io.micronaut.core.annotation.Creator; import java.util.List; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static java.lang.annotation.ElementType.*; @@ -97,7 +97,7 @@ package test; import io.micronaut.core.annotation.Creator; @io.micronaut.core.annotation.Introspected -public record Foo(@javax.validation.constraints.NotBlank String name, int age){ +public record Foo(@jakarta.validation.constraints.NotBlank String name, int age){ } ''') when: @@ -134,8 +134,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -159,8 +159,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties;import io.micronaut.core.annotation.AccessorsStyle; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -185,8 +185,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -229,8 +229,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.core.annotation.AccessorsStyle; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -271,8 +271,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -313,8 +313,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -347,8 +347,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -380,8 +380,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -430,8 +430,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.core.annotation.AccessorsStyle; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -480,8 +480,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -524,8 +524,8 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties;import io.micronaut.core.annotation.AccessorsStyle; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import java.net.URL; @ConfigurationProperties("foo.bar") @@ -577,7 +577,7 @@ class MyConfig { private int serverPort; @ConfigurationInject - MyConfig(@javax.validation.constraints.NotBlank String host, int serverPort) { + MyConfig(@jakarta.validation.constraints.NotBlank String host, int serverPort) { this.host = host; this.serverPort = serverPort; } @@ -610,7 +610,7 @@ class MyConfig { private int serverPort; @ConfigurationInject - MyConfig(@javax.validation.constraints.NotBlank String host, int serverPort) { + MyConfig(@jakarta.validation.constraints.NotBlank String host, int serverPort) { this.host = host; this.serverPort = serverPort; } @@ -636,7 +636,7 @@ class MyConfig { BeanIntrospection introspection = buildBeanIntrospection('test.Test','''\ package test; import io.micronaut.core.annotation.*; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; import java.util.List; import java.util.Set; @@ -662,11 +662,11 @@ public class Test { property.getAnnotationMetadata().getAnnotationNames().size() == 0 param1.getAnnotationMetadata().getAnnotationNames().size() == 1 - param1.getAnnotationMetadata().getAnnotationNames().asList() == ['javax.validation.constraints.Size$List'] + param1.getAnnotationMetadata().getAnnotationNames().asList() == ['jakarta.validation.constraints.Size$List'] param2.getAnnotationMetadata().getAnnotationNames().size() == 1 - param2.getAnnotationMetadata().getAnnotationNames().asList() == ['javax.validation.constraints.NotEmpty$List'] + param2.getAnnotationMetadata().getAnnotationNames().asList() == ['jakarta.validation.constraints.NotEmpty$List'] param3.getAnnotationMetadata().getAnnotationNames().size() == 1 - param3.getAnnotationMetadata().getAnnotationNames().asList() == ['javax.validation.constraints.NotNull$List'] + param3.getAnnotationMetadata().getAnnotationNames().asList() == ['jakarta.validation.constraints.NotNull$List'] } void "test build introspection"() { @@ -674,7 +674,7 @@ public class Test { def context = buildContext('test.Address', ''' package test; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @io.micronaut.core.annotation.Introspected @@ -707,8 +707,8 @@ interface GroupThree {} BeanIntrospection beanIntrospection = buildBeanIntrospection('test.Book', ''' package test; -import javax.validation.Valid; -import javax.validation.constraints.Size; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; import java.util.List; import io.micronaut.core.annotation.Introspected; @@ -746,7 +746,7 @@ class Book { then: property.name == "authors" - property.asArgument().getTypeParameters()[0].annotationMetadata.hasStereotype("javax.validation.Valid") + property.asArgument().getTypeParameters()[0].annotationMetadata.hasStereotype("jakarta.validation.Valid") } @Override diff --git a/validation/src/test/groovy/io/micronaut/validation/BookmarkController.java b/validation/src/test/groovy/io/micronaut/validation/BookmarkController.java index ac4acc45..9deb82f6 100644 --- a/validation/src/test/groovy/io/micronaut/validation/BookmarkController.java +++ b/validation/src/test/groovy/io/micronaut/validation/BookmarkController.java @@ -20,10 +20,10 @@ import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; -import javax.validation.Valid; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Positive; -import javax.validation.constraints.PositiveOrZero; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; @Controller("/api") public class BookmarkController { diff --git a/validation/src/test/groovy/io/micronaut/validation/Foo.java b/validation/src/test/groovy/io/micronaut/validation/Foo.java index 8557bd5e..ef5fcb56 100644 --- a/validation/src/test/groovy/io/micronaut/validation/Foo.java +++ b/validation/src/test/groovy/io/micronaut/validation/Foo.java @@ -19,9 +19,9 @@ import io.micronaut.core.annotation.Introspected; import jakarta.inject.Singleton; -import javax.validation.Valid; -import javax.validation.constraints.Digits; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.NotNull; import java.util.Collections; import java.util.List; import java.util.Map; diff --git a/validation/src/test/groovy/io/micronaut/validation/ImmutableConfigurationPropertiesSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/ImmutableConfigurationPropertiesSpec.groovy index 141a9b7c..65097fa3 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ImmutableConfigurationPropertiesSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/ImmutableConfigurationPropertiesSpec.groovy @@ -20,7 +20,7 @@ import io.micronaut.context.annotation.EachProperty; @EachProperty("foo.bar") interface MyConfig { - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank String getHost(); int getPort(); @@ -49,7 +49,7 @@ class MyConfig { private int serverPort; @ConfigurationInject - MyConfig(@javax.validation.constraints.NotBlank String host, int serverPort) { + MyConfig(@jakarta.validation.constraints.NotBlank String host, int serverPort) { this.host = host; this.serverPort = serverPort; } diff --git a/validation/src/test/groovy/io/micronaut/validation/InterfaceConfigurationPropertiesSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/InterfaceConfigurationPropertiesSpec.groovy index ccb47614..13f71ce0 100644 --- a/validation/src/test/groovy/io/micronaut/validation/InterfaceConfigurationPropertiesSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/InterfaceConfigurationPropertiesSpec.groovy @@ -25,10 +25,10 @@ import java.time.Duration; @ConfigurationProperties("foo.bar") interface MyConfig extends Toggleable { - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank String getHost(); - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) int getServerPort(); @Bindable(defaultValue = "true") @@ -75,7 +75,7 @@ interface MyConfig { @javax.annotation.Nullable String getHost(); - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) Optional getServerPort(); @io.micronaut.core.bind.annotation.Bindable(defaultValue = "http://default") @@ -130,14 +130,14 @@ import java.time.Duration; interface MyConfig extends ParentConfig { @Executable - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) int getServerPort(); } @ConfigurationProperties("foo") interface ParentConfig { @Executable - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank String getHost(); } @@ -175,11 +175,11 @@ import java.net.URL; @ConfigurationProperties("foo.bar") interface MyConfig { @Executable - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank String getHost(); @Executable - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) int getServerPort(); @ConfigurationProperties("child") @@ -219,11 +219,11 @@ import java.net.URL; @ConfigurationProperties("foo.bar") interface MyConfig { - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank @Executable String getHost(); - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) @Executable int getServerPort(); @@ -268,10 +268,10 @@ import java.time.Duration; @ConfigurationProperties("foo.bar") interface MyConfig { - @javax.validation.constraints.NotBlank + @jakarta.validation.constraints.NotBlank String junk(String s); - @javax.validation.constraints.Min(10L) + @jakarta.validation.constraints.Min(10L) int getServerPort(); } diff --git a/validation/src/test/groovy/io/micronaut/validation/JavaClient.java b/validation/src/test/groovy/io/micronaut/validation/JavaClient.java index 1df62749..70798954 100644 --- a/validation/src/test/groovy/io/micronaut/validation/JavaClient.java +++ b/validation/src/test/groovy/io/micronaut/validation/JavaClient.java @@ -19,7 +19,7 @@ import io.micronaut.http.annotation.Header; import io.micronaut.http.client.annotation.Client; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @Client("/validated/tests") interface JavaClient { diff --git a/validation/src/test/groovy/io/micronaut/validation/MyConfig.java b/validation/src/test/groovy/io/micronaut/validation/MyConfig.java index f2cc6a3a..7d9accbb 100644 --- a/validation/src/test/groovy/io/micronaut/validation/MyConfig.java +++ b/validation/src/test/groovy/io/micronaut/validation/MyConfig.java @@ -3,7 +3,7 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.Requires; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @ConfigurationProperties("my.config") @Requires(property = "my.config") diff --git a/validation/src/test/groovy/io/micronaut/validation/MyEachConfig.java b/validation/src/test/groovy/io/micronaut/validation/MyEachConfig.java index 44a80a92..9cbbb274 100644 --- a/validation/src/test/groovy/io/micronaut/validation/MyEachConfig.java +++ b/validation/src/test/groovy/io/micronaut/validation/MyEachConfig.java @@ -3,7 +3,7 @@ import io.micronaut.context.annotation.EachProperty; import io.micronaut.context.annotation.Requires; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @EachProperty(value = "my.config", primary = "default") @Requires(property = "my.config") diff --git a/validation/src/test/groovy/io/micronaut/validation/PaginationCommand.java b/validation/src/test/groovy/io/micronaut/validation/PaginationCommand.java index f6e1d3be..4e9775bc 100644 --- a/validation/src/test/groovy/io/micronaut/validation/PaginationCommand.java +++ b/validation/src/test/groovy/io/micronaut/validation/PaginationCommand.java @@ -18,9 +18,9 @@ import io.micronaut.core.annotation.Introspected; import io.micronaut.core.annotation.Nullable; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Positive; -import javax.validation.constraints.PositiveOrZero; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; import java.util.List; @Introspected diff --git a/validation/src/test/groovy/io/micronaut/validation/Pojo.java b/validation/src/test/groovy/io/micronaut/validation/Pojo.java index 5701bff9..9d5c874f 100644 --- a/validation/src/test/groovy/io/micronaut/validation/Pojo.java +++ b/validation/src/test/groovy/io/micronaut/validation/Pojo.java @@ -17,8 +17,8 @@ import io.micronaut.core.annotation.Introspected; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; @Introspected public class Pojo { diff --git a/validation/src/test/groovy/io/micronaut/validation/PojoNoIntrospection.java b/validation/src/test/groovy/io/micronaut/validation/PojoNoIntrospection.java index 565bce97..e0db61b6 100644 --- a/validation/src/test/groovy/io/micronaut/validation/PojoNoIntrospection.java +++ b/validation/src/test/groovy/io/micronaut/validation/PojoNoIntrospection.java @@ -15,8 +15,8 @@ */ package io.micronaut.validation; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; public class PojoNoIntrospection { diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedConfig.java b/validation/src/test/groovy/io/micronaut/validation/ValidatedConfig.java index aa37694d..8a008c3c 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedConfig.java +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedConfig.java @@ -18,8 +18,8 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.Requires; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.net.URL; @Requires(property = "spec.name", value = "ValidatedConfigurationSpec") diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedConfigurationSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/ValidatedConfigurationSpec.groovy index b9c8ab3e..b5b79ebe 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedConfigurationSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedConfigurationSpec.groovy @@ -75,7 +75,7 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.validation.Pojo; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @ConfigurationProperties("test.valid") @@ -107,7 +107,7 @@ package test; import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.validation.Pojo; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @ConfigurationProperties("test.valid") diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedController.java b/validation/src/test/groovy/io/micronaut/validation/ValidatedController.java index afa71a82..3d7308fd 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedController.java +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedController.java @@ -21,10 +21,10 @@ import io.micronaut.http.annotation.Post; import io.micronaut.http.annotation.QueryValue; -import javax.validation.Valid; -import javax.validation.constraints.Digits; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Optional; diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedGetterConfig.java b/validation/src/test/groovy/io/micronaut/validation/ValidatedGetterConfig.java index 3fe9d985..18a8580a 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedGetterConfig.java +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedGetterConfig.java @@ -18,8 +18,8 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.context.annotation.Requires; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.net.URL; @Requires(property = "spec.name", value = "ValidatedGetterConfigurationSpec") diff --git a/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy index a56430e9..8f087226 100644 --- a/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/ValidatedSpec.groovy @@ -42,8 +42,8 @@ import io.micronaut.runtime.server.EmbeddedServer import reactor.core.publisher.Flux import spock.lang.Specification -import javax.validation.ConstraintViolationException -import javax.validation.constraints.* +import jakarta.validation.ConstraintViolationException +import jakarta.validation.constraints.* /** * @author Graeme Rocher @@ -112,7 +112,7 @@ class ValidatedSpec extends Specification { then: def e = thrown(ConstraintViolationException) - e.message == "string: must not be null" + e.message == "notNull.: must not be null" cleanup: beanContext.close() @@ -128,7 +128,7 @@ class ValidatedSpec extends Specification { then: def e = thrown(ConstraintViolationException) - e.message == "bar: must not be null" + e.message == "notNullBar.: must not be null" cleanup: beanContext.close() @@ -144,7 +144,7 @@ class ValidatedSpec extends Specification { then: def e = thrown(ConstraintViolationException) - e.message == "bar.prop: must not be null" + e.message == "cascadeValidateReturnValue..prop: must not be null" cleanup: beanContext.close() @@ -160,7 +160,7 @@ class ValidatedSpec extends Specification { then: def e = thrown(ConstraintViolationException) - e.message == "list[0].prop: must not be null" + e.message == "validateReturnList.[0].prop: must not be null" cleanup: beanContext.close() @@ -176,7 +176,7 @@ class ValidatedSpec extends Specification { then: def e = thrown(ConstraintViolationException) - e.message == "map[barObj].prop: must not be null" + e.message == "validateMap.[barObj].prop: must not be null" cleanup: beanContext.close() diff --git a/validation/src/test/groovy/io/micronaut/validation/WebSocketClientValidationSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/WebSocketClientValidationSpec.groovy index c323ea43..db7498e0 100644 --- a/validation/src/test/groovy/io/micronaut/validation/WebSocketClientValidationSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/WebSocketClientValidationSpec.groovy @@ -13,9 +13,9 @@ import spock.lang.Issue import spock.lang.Specification import spock.util.concurrent.PollingConditions -import javax.validation.ConstraintViolationException -import javax.validation.Valid -import javax.validation.constraints.Pattern +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid +import jakarta.validation.constraints.Pattern class WebSocketClientValidationSpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/WebSocketValidationServerHandler.groovy b/validation/src/test/groovy/io/micronaut/validation/WebSocketValidationServerHandler.groovy index a7d5bfcb..cf7d0cdf 100644 --- a/validation/src/test/groovy/io/micronaut/validation/WebSocketValidationServerHandler.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/WebSocketValidationServerHandler.groovy @@ -9,8 +9,8 @@ import io.micronaut.websocket.annotation.OnOpen import io.micronaut.websocket.annotation.ServerWebSocket import jakarta.inject.Inject -import javax.validation.ConstraintViolationException -import javax.validation.Valid +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid // this has to be a top-level class because groovy @Requires(property = 'spec.name', value = 'WebSocketClientValidationSpec') diff --git a/validation/src/test/groovy/io/micronaut/validation/properties/WriteOnlyConfigProperties.java b/validation/src/test/groovy/io/micronaut/validation/properties/WriteOnlyConfigProperties.java index c9a60f3a..a5033b20 100644 --- a/validation/src/test/groovy/io/micronaut/validation/properties/WriteOnlyConfigProperties.java +++ b/validation/src/test/groovy/io/micronaut/validation/properties/WriteOnlyConfigProperties.java @@ -2,7 +2,7 @@ import io.micronaut.context.annotation.ConfigurationProperties; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @ConfigurationProperties("test") public class WriteOnlyConfigProperties { diff --git a/validation/src/test/groovy/io/micronaut/validation/records/Test.java b/validation/src/test/groovy/io/micronaut/validation/records/Test.java index 616bb472..59d4a03e 100644 --- a/validation/src/test/groovy/io/micronaut/validation/records/Test.java +++ b/validation/src/test/groovy/io/micronaut/validation/records/Test.java @@ -6,7 +6,7 @@ import io.micronaut.core.convert.ConversionService; import jakarta.inject.Inject; -import javax.validation.constraints.Min; +import jakarta.validation.constraints.Min; @Requires(property = "spec.name", value = "RecordBeansSpec") @ConfigurationProperties("foo") diff --git a/validation/src/test/groovy/io/micronaut/validation/records/Test2.java b/validation/src/test/groovy/io/micronaut/validation/records/Test2.java index d55e1f13..f788b289 100644 --- a/validation/src/test/groovy/io/micronaut/validation/records/Test2.java +++ b/validation/src/test/groovy/io/micronaut/validation/records/Test2.java @@ -6,7 +6,7 @@ import io.micronaut.core.convert.ConversionService; import jakarta.inject.Inject; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Requires(property = "spec.name", value = "RecordBeansSpec") record Test2( diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ConstraintValidatorRegistrySpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/ConstraintValidatorRegistrySpec.groovy index 09819721..48a67736 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ConstraintValidatorRegistrySpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ConstraintValidatorRegistrySpec.groovy @@ -6,7 +6,7 @@ import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.constraints.NotBlank +import jakarta.validation.constraints.NotBlank class ConstraintValidatorRegistrySpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorGroupsSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorGroupsSpec.groovy index 44437023..d63d4682 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorGroupsSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorGroupsSpec.groovy @@ -7,15 +7,16 @@ import io.micronaut.context.ApplicationContext import io.micronaut.core.annotation.Introspected import io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitor import io.micronaut.inject.visitor.TypeElementVisitor +import io.micronaut.validation.visitor.ValidationVisitor import io.micronaut.validation.visitor.IntrospectedValidationIndexesVisitor import spock.lang.AutoCleanup import spock.lang.Shared import javax.annotation.processing.SupportedAnnotationTypes -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.Size -import javax.validation.groups.Default +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.Size +import jakarta.validation.groups.Default class ValidatorGroupsSpec extends AbstractTypeElementSpec { @@ -42,7 +43,7 @@ class ValidatorGroupsSpec extends AbstractTypeElementSpec { then: violations.size() == 2 - messageTemplates.contains('{javax.validation.constraints.Size.message}') + messageTemplates.contains('{jakarta.validation.constraints.Size.message}') messageTemplates.contains('different message') when: @@ -51,7 +52,7 @@ class ValidatorGroupsSpec extends AbstractTypeElementSpec { then: violations.size() == 2 - messageTemplates.contains('{javax.validation.constraints.Size.message}') + messageTemplates.contains('{jakarta.validation.constraints.Size.message}') messageTemplates.contains('message for default') } @@ -92,7 +93,7 @@ class ValidatorGroupsSpec extends AbstractTypeElementSpec { def introspection = buildBeanIntrospection('test.Address', ''' package test; -import javax.validation.constraints.*; +import jakarta.validation.constraints.*; @io.micronaut.core.annotation.Introspected @@ -130,7 +131,7 @@ interface GroupThree {} static class MyTypeElementVisitorProcessor extends TypeElementVisitorProcessor { @Override protected Collection findTypeElementVisitors() { - return [new IntrospectedValidationIndexesVisitor(), new IntrospectedTypeElementVisitor()] + return [new ValidationVisitor(), new IntrospectedValidationIndexesVisitor(), new IntrospectedTypeElementVisitor()] } } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy index b653fc31..8067c56b 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpec.groovy @@ -9,17 +9,19 @@ import io.micronaut.core.annotation.Introspected import io.micronaut.core.reflect.ClassUtils import io.micronaut.validation.validator.resolver.CompositeTraversableResolver import jakarta.inject.Singleton +import jakarta.validation.Path +import jakarta.validation.Valid +import jakarta.validation.ValidatorFactory +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Size +import jakarta.validation.metadata.BeanDescriptor import spock.lang.AutoCleanup -import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification -import javax.validation.Path -import javax.validation.Valid -import javax.validation.ValidatorFactory -import javax.validation.constraints.* -import javax.validation.metadata.BeanDescriptor - class ValidatorSpec extends Specification { @Shared @@ -46,7 +48,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 4 violations[0].invalidValue == [] - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof Size violations[0].constraintDescriptor.annotation.min() == 1 @@ -56,7 +58,7 @@ class ValidatorSpec extends Specification { violations[1].propertyPath.iterator().next().name == 'pages' violations[1].rootBean == b violations[1].rootBeanClass == Book - violations[1].messageTemplate == '{javax.validation.constraints.Min.message}' + violations[1].messageTemplate == '{jakarta.validation.constraints.Min.message}' violations[1].constraintDescriptor != null violations[1].constraintDescriptor.annotation instanceof Min violations[1].constraintDescriptor.annotation.value() == 100l @@ -81,7 +83,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 1 violations[0].invalidValue == [] - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof Size violations[0].constraintDescriptor.annotation.min() == 1 @@ -94,7 +96,7 @@ class ValidatorSpec extends Specification { then: violations.size() == 1 violations[0].invalidValue == ["a", "b", "c"] - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof Size violations[0].constraintDescriptor.annotation.min() == 1 @@ -107,7 +109,7 @@ class ValidatorSpec extends Specification { then: violations.size() == 1 violations[0].invalidValue == [] - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof Size violations[0].constraintDescriptor.annotation.min() == 1 @@ -120,7 +122,7 @@ class ValidatorSpec extends Specification { then: violations.size() == 1 violations[0].invalidValue == [1L, 2L, 3L] - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof Size violations[0].constraintDescriptor.annotation.min() == 1 @@ -180,7 +182,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 - v1.messageTemplate == '{javax.validation.constraints.Max.message}' + v1.messageTemplate == '{jakarta.validation.constraints.Max.message}' v1.propertyPath.toString() == 'primaryAuthor.age' v1.invalidValue == 150 v1.rootBean.is(b) @@ -189,7 +191,7 @@ class ValidatorSpec extends Specification { v1.constraintDescriptor.annotation instanceof Max v1.constraintDescriptor.annotation.value() == 100l - v2.messageTemplate == '{javax.validation.constraints.NotBlank.message}' + v2.messageTemplate == '{jakarta.validation.constraints.NotBlank.message}' v2.propertyPath.toString() == 'primaryAuthor.name' v2.constraintDescriptor != null v2.constraintDescriptor.annotation instanceof NotBlank @@ -211,7 +213,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 - v1.messageTemplate == '{javax.validation.constraints.Max.message}' + v1.messageTemplate == '{jakarta.validation.constraints.Max.message}' v1.propertyPath.toString() == 'primaryAuthor.age' v1.invalidValue == 150 v1.rootBean.is(b) @@ -220,7 +222,7 @@ class ValidatorSpec extends Specification { v1.constraintDescriptor.annotation instanceof Max v1.constraintDescriptor.annotation.value() == 100l - v2.messageTemplate == '{javax.validation.constraints.NotBlank.message}' + v2.messageTemplate == '{jakarta.validation.constraints.NotBlank.message}' v2.propertyPath.toString() == 'primaryAuthor.name' v2.constraintDescriptor != null v2.constraintDescriptor.annotation instanceof NotBlank @@ -239,12 +241,12 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 violations[0].invalidValue == 'X' - violations[0].messageTemplate == '{javax.validation.constraints.Size.message}' - violations[0].propertyPath.toString() == 'names[0]' + violations[0].messageTemplate == '{jakarta.validation.constraints.Size.message}' + violations[0].propertyPath.toString() == 'names[0]' violations[1].invalidValue == 'TooLongName' - violations[1].messageTemplate == '{javax.validation.constraints.Size.message}' - violations[1].propertyPath.toString() == 'names[2]' + violations[1].messageTemplate == '{jakarta.validation.constraints.Size.message}' + violations[1].propertyPath.toString() == 'names[2]' } void "validate property argument - with iterable constraints"() { @@ -272,7 +274,7 @@ class ValidatorSpec extends Specification { path0.getIndex() == null path0.getKey() == null - path1.getName() == "E String" + path1.getName() == "" path1 instanceof Path.ContainerElementNode path1.isInIterable() path1.getIndex() == 1 @@ -292,10 +294,10 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 violations[0].invalidValue == -10 - violations[0].propertyPath.toString() == 'numbers[Bob]' + violations[0].propertyPath.toString() == 'numbers[Bob]' violations[1].invalidValue == "" - violations[1].propertyPath.toString() == 'numbers[]' + violations[1].propertyPath.toString() == 'numbers[]' } void "test validate property argument cascade"() { @@ -306,7 +308,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 1 violations[0].invalidValue == "" - violations[0].propertyPath.toString() == "authors[0].name" + violations[0].propertyPath.toString() == "authors[0].name" } void "test validate property argument cascade with cycle"() { @@ -318,7 +320,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 1 violations[0].invalidValue == "" - violations[0].propertyPath.toString() == "authors[0].name" + violations[0].propertyPath.toString() == "authors[0].name" } void "test validate property argument cascade of null container"() { @@ -347,10 +349,10 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 violations[0].invalidValue == "" - violations[0].propertyPath.toString() == "books[].authors[1].name" + violations[0].propertyPath.toString() == "books[].authors[1].name" violations[1].invalidValue == "?" - violations[1].propertyPath.toString() == "books[].name" + violations[1].propertyPath.toString() == "books[].name" } void "test validate property argument cascade - to non-introspected - inside map"(){ @@ -395,8 +397,8 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 - violations[0].getPropertyPath().toString() == "matrix[0][1]" - violations[1].getPropertyPath().toString() == "matrix[1][0]" + violations[0].getPropertyPath().toString() == "matrix[0][1]" + violations[1].getPropertyPath().toString() == "matrix[1][0]" } void "test validate argument annotations null"() { @@ -468,7 +470,7 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 violations[0].invalidValue == "" - violations[0].propertyPath.toString() == 'saveBook.book.authors[0].name' + violations[0].propertyPath.toString() == 'saveBook.book.authors[0].name' violations[0].constraintDescriptor != null violations[0].constraintDescriptor.annotation instanceof NotBlank @@ -490,8 +492,8 @@ class ValidatorSpec extends Specification { then: violations.size() == 2 - violations[0].getPropertyPath().toString() == "deposit.banknotes[0]" - violations[1].getPropertyPath().toString() == "deposit.banknotes[3]" + violations[0].getPropertyPath().toString() == "deposit.banknotes[0]" + violations[1].getPropertyPath().toString() == "deposit.banknotes[3]" violations[0].getPropertyPath().size() == 3 when: @@ -508,7 +510,7 @@ class ValidatorSpec extends Specification { node1.getName() == "banknotes" node2 instanceof Path.ContainerElementNode - node2.getName() == "E Integer" + node2.getName() == "" } void "test validate method argument generic annotations cascade"() { @@ -534,8 +536,8 @@ class ValidatorSpec extends Specification { then: violations.size() == 3 violations[0].getPropertyPath().toString() == "createAccount.client.name" - violations[1].getPropertyPath().toString() == "createAccount.clientsWithAccess[]" - violations[2].getPropertyPath().toString() == "createAccount.clientsWithAccess[spouse].name" + violations[1].getPropertyPath().toString() == "createAccount.clientsWithAccess[]" + violations[2].getPropertyPath().toString() == "createAccount.clientsWithAccess[spouse].name" when: var path0 = violations[0].getPropertyPath().iterator() @@ -574,16 +576,14 @@ class ValidatorSpec extends Specification { expect: violations.size() == 1 violations[0].invalidValue == 0 - violations[0].getPropertyPath().toString() == "optionalMethod.number" + violations[0].getPropertyPath().toString() == "optionalMethod.number" violations[0].constraintDescriptor.annotation instanceof Min - violations[0].getPropertyPath().size() == 3 + violations[0].getPropertyPath().size() == 2 def path = violations[0].getPropertyPath().iterator() path.next() instanceof Path.MethodNode - path.next() instanceof Path.ParameterNode def element = path.next() - element instanceof Path.ContainerElementNode - ((Path.ContainerElementNode) element).getContainerClass() == Optional.class + element instanceof Path.ParameterNode } // EXECUTABLE RETURN VALUE VALIDATOR @@ -600,8 +600,11 @@ class ValidatorSpec extends Specification { expect: violations.size() == 1 violations[0].invalidValue == -100 - violations[0].getPropertyPath().toString() == "float" - violations[0].getPropertyPath().iterator().next() instanceof Path.ReturnValueNode + violations[0].getPropertyPath().toString() == "getBalance." + + def iterator = violations[0].getPropertyPath().iterator() + iterator.next() instanceof Path.MethodNode + iterator.next() instanceof Path.ReturnValueNode } void "test validate return value type annotations - cascade"() { @@ -619,19 +622,21 @@ class ValidatorSpec extends Specification { violations = violations.sort{it -> it.getPropertyPath().toString() } expect: - violations[0].getPropertyPath().toString() == "map[]" - violations[1].getPropertyPath().toString() == "map[spouse].name" + violations[0].getPropertyPath().toString() == "getClientsWithAccess.[]" + violations[1].getPropertyPath().toString() == "getClientsWithAccess.[spouse].name" when: var path0 = violations[0].getPropertyPath().iterator() var path1 = violations[1].getPropertyPath().iterator() then: - violations[0].getPropertyPath().size() == 2 + violations[0].getPropertyPath().size() == 3 + path0.next() instanceof Path.MethodNode path0.next() instanceof Path.ReturnValueNode path0.next() instanceof Path.ContainerElementNode - violations[1].getPropertyPath().size() == 3 + violations[1].getPropertyPath().size() == 4 + path1.next() instanceof Path.MethodNode path1.next() instanceof Path.ReturnValueNode path1.next() instanceof Path.ContainerElementNode path1.next() instanceof Path.PropertyNode @@ -661,10 +666,10 @@ class ValidatorSpec extends Specification { expect: violations.size() == 2 - violations[0].propertyPath.toString() == "map[0][0].name" + violations[0].propertyPath.toString() == "getAllAccounts.[0][0].name" violations[0].invalidValue == "Too long name" - violations[1].propertyPath.toString() == "map[33][1].name" + violations[1].propertyPath.toString() == "getAllAccounts.[33][1].name" violations[1].invalidValue == "" } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java index b90d5e7a..9a0fc58a 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ValidatorSpecClasses.java @@ -4,11 +4,11 @@ import io.micronaut.core.annotation.Introspected; import jakarta.inject.Singleton; -import javax.validation.Valid; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.*; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ast/SomeAnn.java b/validation/src/test/groovy/io/micronaut/validation/validator/ast/SomeAnn.java index 6aa5d3f1..db426248 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ast/SomeAnn.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ast/SomeAnn.java @@ -15,7 +15,7 @@ */ package io.micronaut.validation.validator.ast; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import java.lang.annotation.Documented; import java.lang.annotation.Retention; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/ast/ValidateAnnotationMetadataSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/ast/ValidateAnnotationMetadataSpec.groovy index 34a95a36..cc98b256 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/ast/ValidateAnnotationMetadataSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/ast/ValidateAnnotationMetadataSpec.groovy @@ -5,6 +5,7 @@ import io.micronaut.annotation.processing.test.AbstractTypeElementSpec import io.micronaut.annotation.processing.test.JavaParser import io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitor import io.micronaut.inject.visitor.TypeElementVisitor +import io.micronaut.validation.visitor.ValidationVisitor import javax.annotation.processing.SupportedAnnotationTypes @@ -136,7 +137,7 @@ class Test { static class MyTypeElementVisitorProcessor extends TypeElementVisitorProcessor { @Override protected Collection findTypeElementVisitors() { - return [new IntrospectedTypeElementVisitor()] + return [new ValidationVisitor(), new IntrospectedTypeElementVisitor()] } } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintMessagesSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintMessagesSpec.groovy index 3c612c40..653d076f 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintMessagesSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintMessagesSpec.groovy @@ -8,12 +8,15 @@ import io.micronaut.inject.beans.visitor.IntrospectedTypeElementVisitor import io.micronaut.inject.visitor.TypeElementVisitor import io.micronaut.validation.validator.Validator import io.micronaut.validation.visitor.IntrospectedValidationIndexesVisitor +import io.micronaut.validation.visitor.ValidationVisitor import spock.lang.AutoCleanup +import io.micronaut.validation.visitor.ValidationVisitor +import io.micronaut.validation.visitor.IntrospectedValidationIndexesVisitor import spock.lang.Shared import spock.lang.Unroll import javax.annotation.processing.SupportedAnnotationTypes -import javax.validation.constraints.* +import jakarta.validation.constraints.* class ConstraintMessagesSpec extends AbstractTypeElementSpec { @@ -41,10 +44,10 @@ class Test { public ${type.name} getField() { return field; } - + public void setField(${type.name} f) { this.field = f; - } + } } """) def instance = introspection.instantiate() @@ -99,7 +102,7 @@ class Test { static class MyTypeElementVisitorProcessor extends TypeElementVisitorProcessor { @Override protected Collection findTypeElementVisitors() { - return [new IntrospectedValidationIndexesVisitor(), new IntrospectedTypeElementVisitor()] + return [new ValidationVisitor(), new IntrospectedValidationIndexesVisitor(), new IntrospectedTypeElementVisitor()] } } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintsSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintsSpec.groovy index ad9490bd..d19dae71 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintsSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/ConstraintsSpec.groovy @@ -8,8 +8,8 @@ import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Unroll -import javax.validation.ClockProvider -import javax.validation.constraints.* +import jakarta.validation.ClockProvider +import jakarta.validation.constraints.* import java.time.Clock import java.time.Instant import java.time.LocalDateTime @@ -308,6 +308,6 @@ class ConstraintsSpec extends AbstractTypeElementSpec { } private AnnotationValue constraintMetadata(Class annotation, String ann) { - buildAnnotationMetadata(ann, "javax.validation.constraints").getAnnotation(annotation) + buildAnnotationMetadata(ann, "jakarta.validation.constraints").getAnnotation(annotation) } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/AlwaysInvalidConstraint.java b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/AlwaysInvalidConstraint.java index 959136e8..a3129239 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/AlwaysInvalidConstraint.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/AlwaysInvalidConstraint.java @@ -1,6 +1,6 @@ package io.micronaut.validation.validator.constraints.custom; -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomConstraintsSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomConstraintsSpec.groovy index 71d882e6..b589fd9d 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomConstraintsSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomConstraintsSpec.groovy @@ -8,7 +8,7 @@ import spock.lang.Ignore import spock.lang.Shared import spock.lang.Specification -import javax.validation.Valid +import jakarta.validation.Valid @Ignore("Validation module needs to be removed from core first") class CustomConstraintsSpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomMessageConstraint.java b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomMessageConstraint.java index 126d976c..bb59f9df 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomMessageConstraint.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/constraints/custom/CustomMessageConstraint.java @@ -1,6 +1,6 @@ package io.micronaut.validation.validator.constraints.custom; -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeExperienceConstraint.java b/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeExperienceConstraint.java index bf6a755b..50d885ca 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeExperienceConstraint.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeExperienceConstraint.java @@ -1,6 +1,6 @@ package io.micronaut.validation.validator.customwithdefaultconstraints; -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.Retention; import static java.lang.annotation.RetentionPolicy.RUNTIME; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeValidationsSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeValidationsSpec.groovy index 40679a16..c1252fcc 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeValidationsSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/customwithdefaultconstraints/EmployeeValidationsSpec.groovy @@ -11,12 +11,12 @@ import spock.lang.Issue import spock.lang.Shared import spock.lang.Specification -import javax.validation.ConstraintViolation -import javax.validation.ConstraintViolationException -import javax.validation.Valid -import javax.validation.constraints.NotBlank -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull +import jakarta.validation.ConstraintViolation +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid +import jakarta.validation.constraints.NotBlank +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull import java.util.stream.Collectors diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/map/Author.java b/validation/src/test/groovy/io/micronaut/validation/validator/map/Author.java index bd35038f..593318ab 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/map/Author.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/map/Author.java @@ -1,7 +1,7 @@ package io.micronaut.validation.validator.map; import io.micronaut.core.annotation.Introspected; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Map; @Introspected diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/map/Book.java b/validation/src/test/groovy/io/micronaut/validation/validator/map/Book.java index ae947e80..f9b2a1af 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/map/Book.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/map/Book.java @@ -1,7 +1,7 @@ package io.micronaut.validation.validator.map; import io.micronaut.core.annotation.Introspected; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @Introspected record Book( diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/map/MapValidatorSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/map/MapValidatorSpec.groovy index 3ac1b2e3..f061bf18 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/map/MapValidatorSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/map/MapValidatorSpec.groovy @@ -1,15 +1,11 @@ package io.micronaut.validation.validator.map import io.micronaut.context.ApplicationContext -import io.micronaut.core.annotation.Introspected import io.micronaut.validation.validator.Validator import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.Valid -import javax.validation.constraints.NotBlank - class MapValidatorSpec extends Specification { @Shared @AutoCleanup @@ -29,7 +25,7 @@ class MapValidatorSpec extends Specification { then: constraintViolations.size() == 1 - constraintViolations.first().propertyPath.toString() == 'books[It].title' + constraintViolations.first().propertyPath.toString() == 'books[It].title' } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/FavoriteWebsSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/FavoriteWebsSpec.groovy index 7bbad3db..2f9d63e6 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/FavoriteWebsSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/FavoriteWebsSpec.groovy @@ -12,8 +12,8 @@ import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull import java.util.regex.Pattern class FavoriteWebsSpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/NameAndLastNameValidator.java b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/NameAndLastNameValidator.java index 0bd87ec2..3a00f94c 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/NameAndLastNameValidator.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/NameAndLastNameValidator.java @@ -15,7 +15,7 @@ */ package io.micronaut.validation.validator.pojo; -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoBodyParameterSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoBodyParameterSpec.groovy index ebd32aaa..2e71f8a7 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoBodyParameterSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoBodyParameterSpec.groovy @@ -32,15 +32,15 @@ import io.micronaut.http.client.HttpClient import io.micronaut.http.client.exceptions.HttpClientResponseException import io.micronaut.runtime.server.EmbeddedServer import io.micronaut.validation.Validated -import javax.validation.groups.Default +import jakarta.validation.groups.Default import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.ConstraintViolationException -import javax.validation.Valid -import javax.validation.constraints.NotEmpty -import javax.validation.constraints.NotNull +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid +import jakarta.validation.constraints.NotEmpty +import jakarta.validation.constraints.NotNull class PojoBodyParameterSpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigProps.java b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigProps.java index 4b92d2a4..3ba4f582 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigProps.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigProps.java @@ -3,7 +3,7 @@ import io.micronaut.context.annotation.ConfigurationProperties; import io.micronaut.validation.Pojo; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @ConfigurationProperties("test.valid") diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy index c043da6a..7d2c94ec 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoConfigurationPropertiesSpec.groovy @@ -7,8 +7,8 @@ import io.micronaut.validation.Pojo import jakarta.inject.Singleton import spock.lang.Specification -import javax.validation.ConstraintViolationException -import javax.validation.Valid +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid class PojoConfigurationPropertiesSpec extends Specification { @@ -39,7 +39,7 @@ class PojoConfigurationPropertiesSpec extends Specification { then: def ex = thrown(BeanInstantiationException) - ex.message.contains("List of constraint violations:[\n\tpojos[0].name - must not be blank\n]") + ex.message.contains("List of constraint violations:[\n\tpojos[0].name - must not be blank\n]") } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy index 64aa0826..a56d0fdc 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/PojoValidatorSpec.groovy @@ -29,9 +29,9 @@ import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.ConstraintViolationException -import javax.validation.Valid -import javax.validation.constraints.NotNull +import jakarta.validation.ConstraintViolationException +import jakarta.validation.Valid +import jakarta.validation.constraints.NotNull class PojoValidatorSpec extends Specification { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/ValidURLs.java b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/ValidURLs.java index c1aeaf2a..9ee9667a 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/pojo/ValidURLs.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/pojo/ValidURLs.java @@ -15,7 +15,7 @@ */ package io.micronaut.validation.validator.pojo; -import javax.validation.Constraint; +import jakarta.validation.Constraint; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/Book.java b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/Book.java index 75457b82..87ccf003 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/Book.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/Book.java @@ -2,7 +2,7 @@ import io.micronaut.core.annotation.Introspected; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; @Introspected public class Book { diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookService.java b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookService.java index 91310efc..d754d44a 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookService.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookService.java @@ -7,8 +7,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookServiceRxJava2.java b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookServiceRxJava2.java index 4793f9c0..50b2aa79 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookServiceRxJava2.java +++ b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/BookServiceRxJava2.java @@ -9,8 +9,8 @@ import jakarta.inject.Singleton; import org.reactivestreams.Publisher; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import java.util.List; @Singleton diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/ReactiveMethodValidationSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/ReactiveMethodValidationSpec.groovy index 77a0cc16..e2a5dd3d 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/ReactiveMethodValidationSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/ReactiveMethodValidationSpec.groovy @@ -2,16 +2,16 @@ package io.micronaut.validation.validator.reactive import io.micronaut.context.ApplicationContext import io.micronaut.validation.validator.Validator +import jakarta.validation.ConstraintViolationException +import org.reactivestreams.Publisher import reactor.core.publisher.Flux import reactor.core.publisher.Mono import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.ConstraintViolationException + import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException -import java.util.regex.Pattern -import org.reactivestreams.Publisher class ReactiveMethodValidationSpec extends Specification { @@ -29,8 +29,8 @@ class ReactiveMethodValidationSpec extends Specification { then: ConstraintViolationException e = thrown() - e.message == 'publisher[].title: must not be blank' - e.getConstraintViolations().first().propertyPath.toString() == 'publisher[].title' + e.message == '[].title: must not be blank' + e.getConstraintViolations().first().propertyPath.toString() == '[].title' } void "test reactive return type no validation"() { @@ -65,7 +65,7 @@ class ReactiveMethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxSimple.title\\[]]*String>: must not be blank', e.message) + e.message == "rxSimple.title[]: must not be blank" def path = e.getConstraintViolations().first().propertyPath.iterator() path.next().getName() == 'rxSimple' path.next().getName() == 'title' @@ -106,7 +106,7 @@ class ReactiveMethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxValid.book\\[].title: must not be blank', e.message) + e.message == "rxValid.book[].title: must not be blank" e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValid.book') } @@ -120,7 +120,7 @@ class ReactiveMethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxValidWithTypeParameter.books\\[]\\[1].title: must not be blank', e.message) + e.message == "rxValidWithTypeParameter.books[][1].title: must not be blank" e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValidWithTypeParameter.books') } @@ -134,8 +134,7 @@ class ReactiveMethodValidationSpec extends Specification { then: ExecutionException e = thrown() e.cause instanceof ConstraintViolationException - - Pattern.matches('futureSimple.title\\[]: must not be blank', e.cause.message) + e.cause.message == "futureSimple.title[]: must not be blank" e.cause.getConstraintViolations().first().propertyPath.toString().startsWith('futureSimple.title') } @@ -149,8 +148,7 @@ class ReactiveMethodValidationSpec extends Specification { then: ExecutionException e = thrown() e.cause instanceof ConstraintViolationException - - Pattern.matches('futureValid.book\\[].title: must not be blank', e.cause.message); + e.cause.message == "futureValid.book[].title: must not be blank" e.cause.getConstraintViolations().first().propertyPath.toString().startsWith('futureValid.book') } } diff --git a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/RxJava2MethodValidationSpec.groovy b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/RxJava2MethodValidationSpec.groovy index 35642dcb..1097ffa9 100644 --- a/validation/src/test/groovy/io/micronaut/validation/validator/reactive/RxJava2MethodValidationSpec.groovy +++ b/validation/src/test/groovy/io/micronaut/validation/validator/reactive/RxJava2MethodValidationSpec.groovy @@ -6,18 +6,12 @@ import io.reactivex.Flowable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single +import jakarta.validation.ConstraintViolationException import org.reactivestreams.Publisher -import reactor.core.publisher.Flux -import reactor.core.publisher.Mono import spock.lang.AutoCleanup import spock.lang.Shared import spock.lang.Specification -import javax.validation.ConstraintViolationException -import java.util.concurrent.CompletableFuture -import java.util.concurrent.ExecutionException -import java.util.regex.Pattern - class RxJava2MethodValidationSpec extends Specification { @Shared @@ -34,8 +28,8 @@ class RxJava2MethodValidationSpec extends Specification { then: ConstraintViolationException e = thrown() - e.message == 'publisher[].title: must not be blank' - e.getConstraintViolations().first().propertyPath.toString() == 'publisher[].title' + e.message == '[].title: must not be blank' + e.getConstraintViolations().first().propertyPath.toString() == '[].title' } void "test reactive return type no validation"() { @@ -70,12 +64,11 @@ class RxJava2MethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxSimple.title\\[]]*String>: must not be blank', e.message) + e.message == "rxSimple.title[]: must not be blank" def path = e.getConstraintViolations().first().propertyPath.iterator() path.next().getName() == 'rxSimple' path.next().getName() == 'title' path.next().isInIterable() - } void "test reactive validation with valid argument"() { @@ -112,7 +105,7 @@ class RxJava2MethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxValid.book\\[].title: must not be blank', e.message) + e.message == "rxValid.book[].title: must not be blank" e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValid.book') } @@ -126,7 +119,7 @@ class RxJava2MethodValidationSpec extends Specification { then: def e = thrown(ConstraintViolationException) - Pattern.matches('rxValidWithTypeParameter.books\\[]\\[1].title: must not be blank', e.message) + e.message == "rxValidWithTypeParameter.books[][1].title: must not be blank" e.getConstraintViolations().first().propertyPath.toString().startsWith('rxValidWithTypeParameter.books') }