From 337c3d7267ef0b9dd3ffc467b14b998d09882fee Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 21 Apr 2023 13:38:48 +0300 Subject: [PATCH] Properly pass annotation to Writers when streaming data Closes: #31587 --- .../reactive/server/core/SseUtil.java | 6 +-- .../reactive/server/core/StreamingUtil.java | 6 +-- .../hibernate-orm-envers/pom.xml | 17 ++++++ .../io/quarkus/it/envers/CustomOutput.java | 13 +++++ .../io/quarkus/it/envers/MessageProvider.java | 11 +++- .../io/quarkus/it/envers/OutputResource.java | 21 ++++++++ .../quarkus/it/envers/OutputResourceTest.java | 54 ++++++++++++++++++- 7 files changed, 118 insertions(+), 10 deletions(-) create mode 100644 integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java index f0a3824f25c42..edef2f560405d 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/SseUtil.java @@ -16,7 +16,6 @@ import jakarta.ws.rs.sse.OutboundSseEvent; import jakarta.ws.rs.sse.SseEvent; -import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.util.CommonSseUtil; import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; import org.jboss.resteasy.reactive.server.handlers.PublisherResponseHandler; @@ -135,10 +134,9 @@ private static String serialiseDataToString(ResteasyReactiveRequestContext conte ByteArrayOutputStream baos = new ByteArrayOutputStream(); boolean wrote = false; for (MessageBodyWriter writer : writers) { - // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations - if (writer.isWriteable(entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType)) { + if (writer.isWriteable(entityClass, entityType, context.getAllAnnotations(), mediaType)) { // FIXME: spec doesn't really say what headers we should use here - writer.writeTo(entity, entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType, + writer.writeTo(entity, entityClass, entityType, context.getAllAnnotations(), mediaType, new QuarkusMultivaluedHashMap<>(), baos); wrote = true; break; diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java index 0903202a53402..8140b79ca2f39 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/StreamingUtil.java @@ -13,7 +13,6 @@ import jakarta.ws.rs.core.Response; import jakarta.ws.rs.ext.MessageBodyWriter; -import org.jboss.resteasy.reactive.common.core.Serialisers; import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap; import org.jboss.resteasy.reactive.server.StreamingOutputStream; import org.jboss.resteasy.reactive.server.handlers.PublisherResponseHandler; @@ -63,10 +62,9 @@ private static byte[] serialiseEntity(ResteasyReactiveRequestContext context, Ob StreamingOutputStream baos = new StreamingOutputStream(); boolean wrote = false; for (MessageBodyWriter writer : writers) { - // Spec(API) says we should use class/type/mediaType but doesn't talk about annotations - if (writer.isWriteable(entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType)) { + if (writer.isWriteable(entityClass, entityType, context.getAllAnnotations(), mediaType)) { // FIXME: spec doesn't really say what headers we should use here - writer.writeTo(entity, entityClass, entityType, Serialisers.NO_ANNOTATION, mediaType, + writer.writeTo(entity, entityClass, entityType, context.getAllAnnotations(), mediaType, new QuarkusMultivaluedHashMap<>(), baos); wrote = true; break; diff --git a/integration-tests/hibernate-orm-envers/pom.xml b/integration-tests/hibernate-orm-envers/pom.xml index 2728418387329..ee23574489316 100644 --- a/integration-tests/hibernate-orm-envers/pom.xml +++ b/integration-tests/hibernate-orm-envers/pom.xml @@ -28,6 +28,10 @@ io.quarkus quarkus-jdbc-h2 + + io.quarkus + quarkus-jaxrs-client-reactive + @@ -99,6 +103,19 @@ + + io.quarkus + quarkus-jaxrs-client-reactive-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java new file mode 100644 index 0000000000000..503abf7959c71 --- /dev/null +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/CustomOutput.java @@ -0,0 +1,13 @@ +package io.quarkus.it.envers; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CustomOutput { + + String value(); +} diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java index f09fbac07220e..3bbff668bba35 100644 --- a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/MessageProvider.java @@ -40,6 +40,15 @@ public boolean isWriteable(Class type, Type genericType, Annotation[] annotat @Override public void writeTo(Message event, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { - entityStream.write("{\"data\": \"out\"}".getBytes(StandardCharsets.UTF_8)); + String data = "out"; + if (annotations != null) { + for (Annotation annotation : annotations) { + if (annotation.annotationType().equals(CustomOutput.class)) { + data = ((CustomOutput) annotation).value(); + break; + } + } + } + entityStream.write(String.format("{\"data\": \"%s\"}", data).getBytes(StandardCharsets.UTF_8)); } } diff --git a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java index a3882b22dbc41..4376c1378611f 100644 --- a/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java +++ b/integration-tests/hibernate-orm-envers/src/main/java/io/quarkus/it/envers/OutputResource.java @@ -1,10 +1,16 @@ package io.quarkus.it.envers; +import java.util.List; + import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import org.jboss.resteasy.reactive.RestStreamElementType; + +import io.smallrye.mutiny.Multi; + @Path("output") public class OutputResource { @@ -13,4 +19,19 @@ public class OutputResource { public Message out() { return new Message("test"); } + + @GET + @RestStreamElementType(MediaType.APPLICATION_JSON) + @CustomOutput("dummy") + @Path("annotation") + public Multi sseOut() { + return Multi.createFrom().iterable(List.of(new Message("test"), new Message("test"))); + } + + @GET + @RestStreamElementType(MediaType.APPLICATION_JSON) + @Path("no-annotation") + public Multi sseOut2() { + return Multi.createFrom().iterable(List.of(new Message("test"), new Message("test"))); + } } diff --git a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java index a1a5d6efe039b..74b6aa4ffa9bb 100644 --- a/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java +++ b/integration-tests/hibernate-orm-envers/src/test/java/io/quarkus/it/envers/OutputResourceTest.java @@ -3,21 +3,73 @@ import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.equalTo; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.sse.SseEventSource; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.http.ContentType; @QuarkusTest class OutputResourceTest { + private static final String RESOURCE_PATH = "/jpa-envers-test/output"; + @TestHTTPEndpoint(OutputResource.class) + @TestHTTPResource + URL url; + @Test void test() { given().accept(ContentType.JSON) .when() - .get("/jpa-envers-test/output") + .get(RESOURCE_PATH) .then() .statusCode(200) .body("data", equalTo("out")); } + + @Test + public void testSseWithAnnotation() throws InterruptedException, URISyntaxException, MalformedURLException { + doTestSee("annotation", "dummy"); + } + + @Test + public void testSseWithoutAnnotation() throws InterruptedException, URISyntaxException, MalformedURLException { + doTestSee("no-annotation", "out"); + } + + private void doTestSee(String path, String expectedDataValue) + throws URISyntaxException, InterruptedException, MalformedURLException { + Client client = ClientBuilder.newBuilder().build(); + WebTarget target = client.target(new URL(ConfigProvider.getConfig().getValue("test.url", String.class)).toURI()) + .path(RESOURCE_PATH).path(path); + try (SseEventSource sse = SseEventSource.target(target).build()) { + CountDownLatch latch = new CountDownLatch(1); + List errors = new CopyOnWriteArrayList<>(); + List results = new CopyOnWriteArrayList<>(); + sse.register(event -> results.add(event.readData()), errors::add, latch::countDown); + sse.open(); + Assertions.assertTrue(latch.await(20, TimeUnit.SECONDS)); + + String json = String.format("{\"data\": \"%s\"}", expectedDataValue); + Assertions.assertEquals(Arrays.asList(json, json), results); + Assertions.assertEquals(0, errors.size()); + } + } }