Skip to content

Commit

Permalink
Improve dynamic resolution of MessageBodyWriter providers
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Apr 1, 2022
1 parent d32cfec commit 1a4963b
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
*/
public class DynamicEntityWriter implements EntityWriter {

private static final MessageBodyWriter<?>[] EMPTY_ARRAY = new MessageBodyWriter[0];

private final ServerSerialisers serialisers;

public DynamicEntityWriter(ServerSerialisers serialisers) {
Expand All @@ -36,15 +38,25 @@ public void write(ResteasyReactiveRequestContext context, Object entity) throws
ServerHttpRequest vertxRequest = context.serverRequest();
// first check and see if the resource method defined a media type and try to use it
if ((context.getTarget() != null) && (context.getTarget().getProduces() != null)) {
MediaType res = context.getTarget().getProduces()
MediaType negotiatedMediaType = context.getTarget().getProduces()
.negotiateProduces(vertxRequest.getRequestHeader(HttpHeaders.ACCEPT)).getKey();
List<MessageBodyWriter<?>> writersList = serialisers.findWriters(null, entity.getClass(), res,
List<MessageBodyWriter<?>> writersList = serialisers.findWriters(null, entity.getClass(), negotiatedMediaType,
RuntimeType.SERVER);
if (!writersList.isEmpty()) {
writers = writersList.toArray(new MessageBodyWriter[0]);
writers = writersList.toArray(EMPTY_ARRAY);
// use the actual type the method declares as this is what the spec expects despite the fact that we might
// have used the suffix of the subtype to determine a MessageBodyWriter
selectedMediaType = context.getTarget().getProduces().getSortedOriginalMediaTypes()[0];
MediaType[] sortedOriginalMediaTypes = context.getTarget().getProduces().getSortedOriginalMediaTypes();
for (MediaType methodMediaType : sortedOriginalMediaTypes) {
if (methodMediaType.isCompatible(negotiatedMediaType)) {
selectedMediaType = methodMediaType;
break;
}
}
if (selectedMediaType == null) {
// this should never happen
selectedMediaType = sortedOriginalMediaTypes[0];
}
}
} else if (vertxRequest.getRequestHeader(HttpHeaders.ACCEPT) != null
&& !MediaType.WILDCARD.equals(vertxRequest.getRequestHeader(HttpHeaders.ACCEPT))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package org.jboss.resteasy.reactive.server.vertx.test.matching;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.function.Supplier;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.reactive.server.vertx.test.framework.ResteasyReactiveUnitTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

public class MultipleProducesTest {

@RegisterExtension
static ResteasyReactiveUnitTest test = new ResteasyReactiveUnitTest()
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(Entity.class, FirstResource.class, SecondResource.class, ExcelMessageBodyWriter.class,
TextPlainMessageBodyWriter.class, ApplicationJsonMessageBodyWriter.class);
}
});

@Test
public void firstResourceShouldReturnJson() {
doTest("first", "application/json", "application/json");
}

@Test
public void firstResourceShouldReturnMsExcel() {
doTest("first", "application/vnd.ms-excel", "foo");
}

@Test
public void firstResourceShouldReturnTextPlain() {
doTest("first", "text/plain", "text/plain");
}

@Test
public void secondResourceShouldReturnJson() {
doTest("second", "application/json", "application/json");
}

@Test
public void secondResourceShouldReturnMsExcel() {
doTest("second", "application/vnd.ms-excel", "bar");
}

@Test
public void secondResourceShouldReturnTextPlain() {
doTest("second", "text/plain", "text/plain");
}

private void doTest(String path, String acceptType, String expectBody) {
given()
.accept(acceptType)
.when().get(path)
.then()
.statusCode(200)
.contentType(acceptType)
.body(is(expectBody));
}

public static class Entity {

public final String data;

public Entity(String data) {
this.data = data;
}
}

@Path("first")
public static class FirstResource {

@GET
@Produces({ "application/vnd.ms-excel", MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN })
public Response get() {
return Response.ok().entity(new Entity("foo")).build();
}
}

@Path("second")
public static class SecondResource {

@GET
@Produces({ MediaType.APPLICATION_JSON, "application/vnd.ms-excel", MediaType.TEXT_PLAIN })
public Response get() {
return Response.ok().entity(new Entity("bar")).build();
}
}

@Provider
public static class ExcelMessageBodyWriter implements MessageBodyWriter<Entity> {

@Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.equals(Entity.class) &&
mediaType.getType().equals("application") &&
mediaType.getSubtype().equals("vnd.ms-excel");
}

@Override
public void writeTo(Entity entity, Class<?> aClass, Type type, Annotation[] annotations,
MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap,
OutputStream outputStream) throws IOException, WebApplicationException {

outputStream.write(entity.data.getBytes(StandardCharsets.UTF_8));
}
}

@Provider
public static class TextPlainMessageBodyWriter implements MessageBodyWriter<Entity> {

@Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.equals(Entity.class) && mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE);
}

@Override
public void writeTo(Entity myResponseEntity, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream)
throws IOException, WebApplicationException {
outputStream.write("text/plain".getBytes());
}
}

@Provider
public static class ApplicationJsonMessageBodyWriter implements MessageBodyWriter<Entity> {

@Override
public boolean isWriteable(Class<?> clazz, Type type, Annotation[] annotations, MediaType mediaType) {
return clazz.equals(Entity.class) && mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE);
}

@Override
public void writeTo(Entity myResponseEntity, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream)
throws IOException, WebApplicationException {
outputStream.write("application/json".getBytes());
}
}
}

0 comments on commit 1a4963b

Please sign in to comment.