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 extends ConstraintViolation>> 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 extends Payload>[] 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].