From 6043c579cd81ff0b5e656623b270fc87a8b16df5 Mon Sep 17 00:00:00 2001 From: Jose Date: Fri, 9 Jun 2023 07:27:41 +0200 Subject: [PATCH] Support marshalling of JAXBElements in REST Client and Server Reactive I made also this change in the server, even though I think it's a niche use case here, but just to keep the server and client writers synced. Also, I discarded the idea of returning JAXBElements which would be a more complex scenario. So, I updated both readers in the client and in the server to not support the JAXBElement class. Fix https://github.com/quarkusio/quarkus/issues/33875 --- .../jaxb/deployment/test/SimpleXmlTest.java | 23 +++++++++ .../ServerJaxbMessageBodyReader.java | 3 ++ .../ServerJaxbMessageBodyWriter.java | 12 +++-- .../reactive/jaxb/test/SimpleJaxbTest.java | 50 ++++++++++++++++--- .../runtime/ClientJaxbMessageBodyReader.java | 3 ++ .../jaxb/runtime/ClientMessageBodyWriter.java | 10 ++-- 6 files changed, 87 insertions(+), 14 deletions(-) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/test/java/io/quarkus/resteasy/reactive/jaxb/deployment/test/SimpleXmlTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/test/java/io/quarkus/resteasy/reactive/jaxb/deployment/test/SimpleXmlTest.java index 214d074bad7ff2..4cfe409be76ff0 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/test/java/io/quarkus/resteasy/reactive/jaxb/deployment/test/SimpleXmlTest.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/deployment/src/test/java/io/quarkus/resteasy/reactive/jaxb/deployment/test/SimpleXmlTest.java @@ -7,6 +7,8 @@ import java.io.StringWriter; +import javax.xml.namespace.QName; + import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.ws.rs.Consumes; @@ -18,6 +20,7 @@ import jakarta.ws.rs.container.Suspended; import jakarta.ws.rs.core.MediaType; import jakarta.xml.bind.JAXB; +import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlRootElement; @@ -178,6 +181,19 @@ public void testResourceUsingModelWithSameName() { .body(is("bb")); } + @Test + public void testSupportReturningJaxbElement() { + Person response = RestAssured + .get("/simple/person-as-jaxb-element") + .then() + .statusCode(200) + .contentType(ContentType.XML) + .extract().as(Person.class); + + assertEquals("Bob", response.getFirst()); + assertEquals("Builder", response.getLast()); + } + private String toXml(Object person) { StringWriter sw = new StringWriter(); JAXB.marshal(person, sw); @@ -246,6 +262,13 @@ public Person getPerson() { return person; } + @GET + @Produces(MediaType.APPLICATION_XML) + @Path("/person-as-jaxb-element") + public JAXBElement getPersonAsJaxbElement() { + return new JAXBElement<>(new QName("person"), Person.class, getPerson()); + } + @POST @Path("/person") @Produces(MediaType.APPLICATION_XML) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java index 6a8226fba6d03d..496d553b092dbd 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyReader.java @@ -62,6 +62,9 @@ protected boolean isReadable(MediaType mediaType, Class type) { if (String.class.equals(type)) { // don't attempt to read plain strings return false; } + if (JAXBElement.class.equals(type)) { // don't attempt to read JAXB elements + return false; + } String subtype = mediaType.getSubtype(); boolean isCorrectMediaType = "application".equals(mediaType.getType()) || "text".equals(mediaType.getType()); return (isCorrectMediaType && "xml".equalsIgnoreCase(subtype) || subtype.endsWith("+xml")) diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyWriter.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyWriter.java index b5a91eec309f49..924e8113ec0b1b 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyWriter.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive-jaxb/runtime/src/main/java/io/quarkus/resteasy/reactive/jaxb/runtime/serialisers/ServerJaxbMessageBodyWriter.java @@ -55,11 +55,15 @@ public void writeResponse(Object o, Type genericType, ServerRequestContext conte protected void marshal(Object o, OutputStream outputStream) { try { - Object jaxbObject = o; Class clazz = o.getClass(); - XmlRootElement jaxbElement = clazz.getAnnotation(XmlRootElement.class); - if (jaxbElement == null) { - jaxbObject = new JAXBElement(new QName(Introspector.decapitalize(clazz.getSimpleName())), clazz, o); + Object jaxbObject = o; + if (o instanceof JAXBElement) { + clazz = ((JAXBElement) o).getDeclaredType(); + } else { + XmlRootElement jaxbElement = clazz.getAnnotation(XmlRootElement.class); + if (jaxbElement == null) { + jaxbObject = new JAXBElement(new QName(Introspector.decapitalize(clazz.getSimpleName())), clazz, o); + } } getMarshall(clazz).marshal(jaxbObject, outputStream); diff --git a/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java b/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java index 4efea63bf3fe6d..7ac9fa4dc71c28 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jaxb/deployment/src/test/java/io/quarkus/rest/client/reactive/jaxb/test/SimpleJaxbTest.java @@ -5,12 +5,18 @@ import java.net.URI; import java.util.Objects; +import javax.xml.namespace.QName; + +import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; +import jakarta.xml.bind.JAXBElement; import jakarta.xml.bind.annotation.XmlRootElement; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -26,18 +32,25 @@ public class SimpleJaxbTest { @TestHTTPResource URI uri; + XmlClient client; + + @BeforeEach + public void setup() { + client = QuarkusRestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class); + } + @Test void shouldConsumeXMLEntity() { - var dto = QuarkusRestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) - .dto(); - assertThat(dto).isEqualTo(new Dto("foo", "bar")); + // assertThat(client.dto()).isEqualTo(new Dto("foo", "bar")); + assertThat(client.dtoAsJaxbElement().getValue()).isEqualTo(new Dto("foo", "bar")); + assertThat(client.createDto(new Dto("foo", "bar"))).isEqualTo(new Dto("foo", "bar")); + assertThat(client.createDto(new JAXBElement<>(new QName("Dto"), Dto.class, new Dto("foo", "bar")))) + .isEqualTo(new Dto("foo", "bar")); } @Test void shouldConsumePlainXMLEntity() { - var dto = QuarkusRestClientBuilder.newBuilder().baseUri(uri).build(XmlClient.class) - .plain(); - assertThat(dto).isEqualTo(new Dto("foo", "bar")); + assertThat(client.plain()).isEqualTo(new Dto("foo", "bar")); } @Path("/xml") @@ -48,6 +61,23 @@ public interface XmlClient { @Produces(MediaType.APPLICATION_XML) Dto dto(); + @GET + @Path("/dto") + @Produces(MediaType.APPLICATION_XML) + JAXBElement dtoAsJaxbElement(); + + @POST + @Path("/dto") + @Consumes(MediaType.APPLICATION_XML) + @Produces(MediaType.APPLICATION_XML) + Dto createDto(Dto dto); + + @POST + @Path("/dto") + @Consumes(MediaType.APPLICATION_XML) + @Produces(MediaType.APPLICATION_XML) + Dto createDto(JAXBElement dto); + @GET @Path("/plain") @Produces(MediaType.TEXT_XML) @@ -67,6 +97,14 @@ public String dto() { return DTO_FOO_BAR; } + @POST + @Consumes(MediaType.APPLICATION_XML) + @Produces(MediaType.APPLICATION_XML) + @Path("/dto") + public String dto(String dto) { + return dto; + } + @GET @Produces(MediaType.TEXT_XML) @Path("/plain") diff --git a/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientJaxbMessageBodyReader.java b/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientJaxbMessageBodyReader.java index 8fc460434be087..7ef79510af8961 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientJaxbMessageBodyReader.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientJaxbMessageBodyReader.java @@ -43,6 +43,9 @@ protected boolean isReadable(MediaType mediaType, Class type) { if (String.class.equals(type)) { // don't attempt to read plain strings return false; } + if (JAXBElement.class.equals(type)) { // don't attempt to read JAXB elements + return false; + } String subtype = mediaType.getSubtype(); boolean isCorrectMediaType = "application".equals(mediaType.getType()) || "text".equals(mediaType.getType()); return (isCorrectMediaType && "xml".equalsIgnoreCase(subtype) || subtype.endsWith("+xml")) diff --git a/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientMessageBodyWriter.java b/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientMessageBodyWriter.java index 9d1b1f9043869b..5cb016a42b5b4f 100644 --- a/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientMessageBodyWriter.java +++ b/extensions/resteasy-reactive/rest-client-reactive-jaxb/runtime/src/main/java/io/quarkus/rest/client/reactive/jaxb/runtime/ClientMessageBodyWriter.java @@ -46,11 +46,13 @@ private void setContentTypeIfNecessary(MultivaluedMap httpHeader protected void marshal(Object o, OutputStream outputStream) { try { - Object jaxbObject = o; Class clazz = o.getClass(); - XmlRootElement jaxbElement = clazz.getAnnotation(XmlRootElement.class); - if (jaxbElement == null) { - jaxbObject = new JAXBElement(new QName(Introspector.decapitalize(clazz.getSimpleName())), clazz, o); + Object jaxbObject = o; + if (!(o instanceof JAXBElement)) { + XmlRootElement jaxbElement = clazz.getAnnotation(XmlRootElement.class); + if (jaxbElement == null) { + jaxbObject = new JAXBElement(new QName(Introspector.decapitalize(clazz.getSimpleName())), clazz, o); + } } marshaller.marshal(jaxbObject, outputStream);