diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java index 38366401d34e9..9f4023f718699 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java @@ -18,6 +18,11 @@ public ResteasyReactiveContextLocaleResolver(HttpHeaders headers) { @Override protected HttpHeaders getHeaders() { - return headers; + try { + headers.getLength(); // this forces the creation of the actual object which will fail if there is no request in flight + return headers; + } catch (IllegalStateException e) { + return null; + } } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java index 06318c42c1c2d..74c67f3822420 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/injection/ContextProducers.java @@ -133,6 +133,10 @@ SecurityContext securityContext() { } private ResteasyReactiveRequestContext getContext() { - return CurrentRequestManager.get(); + ResteasyReactiveRequestContext context = CurrentRequestManager.get(); + if (context == null) { + throw new IllegalStateException("No RESTEasy Reactive request in progress"); + } + return context; } } diff --git a/integration-tests/hibernate-validator-resteasy-reactive/pom.xml b/integration-tests/hibernate-validator-resteasy-reactive/pom.xml new file mode 100644 index 0000000000000..9862dac97f3d3 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/pom.xml @@ -0,0 +1,182 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-integration-test-hibernate-validator-resteasy-reactive + Quarkus - Integration Tests - Hibernate Validator + Module that contains Hibernate Validator/Bean Validation related tests using RESTEasy Reactive + + + + io.quarkus + quarkus-resteasy-reactive-jsonb + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-arc + + + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-jdbc-h2 + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-h2 + test + + + + io.quarkus + quarkus-arc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-orm-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-validator-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jsonb-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + src/main/resources + true + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + en + + + + + + + + + native-image + + + native + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + en + + + + + + + + + diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java new file mode 100644 index 0000000000000..cc7f0929c2e14 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java @@ -0,0 +1,167 @@ +package io.quarkus.it.hibernate.validator; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.validation.ConstraintViolation; +import javax.validation.Valid; +import javax.validation.Validator; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.Email; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.hibernate.validator.constraints.Length; + +import io.quarkus.it.hibernate.validator.custom.MyOtherBean; +import io.quarkus.runtime.StartupEvent; + +@Path("/hibernate-validator/test") +public class HibernateValidatorTestResource { + + @Inject + Validator validator; + + public void testValidationOutsideOfResteasyContext(@Observes StartupEvent startupEvent) { + validator.validate(new MyOtherBean(null)); + } + + @GET + @Path("/basic-features") + @Produces(MediaType.TEXT_PLAIN) + public String testBasicFeatures() { + ResultBuilder result = new ResultBuilder(); + + Map> invalidCategorizedEmails = new HashMap<>(); + invalidCategorizedEmails.put("a", Collections.singletonList("b")); + + result.append(formatViolations(validator.validate(new MyBean( + "Bill Jones", + "b", + Collections.singletonList("c"), + -4d, + invalidCategorizedEmails)))); + + Map> validCategorizedEmails = new HashMap<>(); + validCategorizedEmails.put("Professional", Collections.singletonList("bill.jones@example.com")); + + result.append(formatViolations(validator.validate(new MyBean( + "Bill Jones", + "bill.jones@example.com", + Collections.singletonList("biji@example.com"), + 5d, + validCategorizedEmails)))); + + return result.build(); + } + + private String formatViolations(Set> violations) { + if (violations.isEmpty()) { + return "passed"; + } + + return "failed: " + violations.stream() + .map(v -> v.getPropertyPath().toString() + " (" + v.getMessage() + ")") + .sorted() + .collect(Collectors.joining(", ")); + } + + public static class MyBean { + + private String name; + + @Email + private String email; + + private List<@Email String> additionalEmails; + + @DecimalMin("0") + private Double score; + + private Map<@Length(min = 3) String, List<@Email String>> categorizedEmails; + + @Valid + private NestedBeanWithoutConstraints nestedBeanWithoutConstraints; + + public MyBean(String name, String email, List additionalEmails, Double score, + Map> categorizedEmails) { + this.name = name; + this.email = email; + this.additionalEmails = additionalEmails; + this.score = score; + this.categorizedEmails = categorizedEmails; + this.nestedBeanWithoutConstraints = new NestedBeanWithoutConstraints(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public List getAdditionalEmails() { + return additionalEmails; + } + + public void setAdditionalEmails(List additionalEmails) { + this.additionalEmails = additionalEmails; + } + + public Double getScore() { + return score; + } + + public void setScore(Double score) { + this.score = score; + } + + public Map> getCategorizedEmails() { + return categorizedEmails; + } + + public void setCategorizedEmails(Map> categorizedEmails) { + this.categorizedEmails = categorizedEmails; + } + } + + private static class ResultBuilder { + + private StringBuilder builder = new StringBuilder(); + + public ResultBuilder append(String element) { + if (builder.length() > 0) { + builder.append("\n"); + } + builder.append(element); + return this; + } + + public String build() { + return builder.toString(); + } + } + + private static class NestedBeanWithoutConstraints { + + @SuppressWarnings("unused") + private String property; + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java new file mode 100644 index 0000000000000..d2dfd4aa2222f --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceGenericInterface.java @@ -0,0 +1,9 @@ +package io.quarkus.it.hibernate.validator; + +import javax.validation.constraints.Digits; + +public interface HibernateValidatorTestResourceGenericInterface { + + T testRestEndpointGenericMethodValidation(@Digits(integer = 5, fraction = 0) T id); + +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java new file mode 100644 index 0000000000000..fe76470c7c252 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResourceInterface.java @@ -0,0 +1,22 @@ +package io.quarkus.it.hibernate.validator; + +import javax.validation.constraints.Digits; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +public interface HibernateValidatorTestResourceInterface { + + @GET + @Path("/rest-end-point-interface-validation/{id}/") + @Produces(MediaType.TEXT_PLAIN) + public String testRestEndPointInterfaceValidation(@Digits(integer = 5, fraction = 0) @PathParam("id") String id); + + @GET + @Path("/rest-end-point-interface-validation-annotation-on-impl-method/{id}/") + @Produces(MediaType.TEXT_PLAIN) + public String testRestEndPointInterfaceValidationWithAnnotationOnImplMethod( + @Digits(integer = 5, fraction = 0) @PathParam("id") String id); +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java new file mode 100644 index 0000000000000..ad412dea04eb5 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java @@ -0,0 +1,31 @@ +package io.quarkus.it.hibernate.validator.custom; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.validation.Constraint; +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import javax.validation.Payload; + +@Target({ ElementType.TYPE_USE, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = MyCustomConstraint.Validator.class) +public @interface MyCustomConstraint { + + String message() default "{MyCustomConstraint.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + class Validator implements ConstraintValidator { + + @Override + public boolean isValid(MyOtherBean value, ConstraintValidatorContext context) { + return value.getName() != null; + } + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java new file mode 100644 index 0000000000000..4961a6d0103b0 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java @@ -0,0 +1,19 @@ +package io.quarkus.it.hibernate.validator.custom; + +@MyCustomConstraint +public class MyOtherBean { + + private String name; + + public MyOtherBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java new file mode 100644 index 0000000000000..433213be9fad8 --- /dev/null +++ b/integration-tests/hibernate-validator-resteasy-reactive/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java @@ -0,0 +1,31 @@ +package io.quarkus.it.hibernate.validator; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +@QuarkusTestResource(H2DatabaseTestResource.class) +public class HibernateValidatorFunctionalityTest { + + @Test + public void testBasicFeatures() throws Exception { + StringBuilder expected = new StringBuilder(); + expected.append("failed: additionalEmails[0]. (must be a well-formed email address)").append(", ") + .append("categorizedEmails[a]. (length must be between 3 and 2147483647)").append(", ") + .append("categorizedEmails[a].[0]. (must be a well-formed email address)").append(", ") + .append("email (must be a well-formed email address)").append(", ") + .append("score (must be greater than or equal to 0)").append("\n"); + expected.append("passed"); + + RestAssured.when() + .get("/hibernate-validator/test/basic-features") + .then() + .body(is(expected.toString())); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c7cadd1fe67ba..d79c46ec6122e 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -123,6 +123,7 @@ class-transformer shared-library hibernate-validator + hibernate-validator-resteasy-reactive common-jpa-entities infinispan-client devtools