From a60aca2662694b31c97cd630dc90ce125b8c226a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Tue, 29 Oct 2024 15:15:19 +0100 Subject: [PATCH 1/2] Use proper media type when executing writer interceptors The one from the response can be null if we return a partial response but the type is defined by @Produces. We need to consider the one from the context. Fixes #34839 --- ...terInterceptorContentTypeOverrideTest.java | 59 +++++++++++++++++++ .../WriterInterceptorContentTypeTest.java | 59 +++++++++++++++++++ .../server/core/ServerSerialisers.java | 4 +- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeOverrideTest.java create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeTest.java diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeOverrideTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeOverrideTest.java new file mode 100644 index 0000000000000..dcafea1f92bf9 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeOverrideTest.java @@ -0,0 +1,59 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.WriterInterceptorContext; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class WriterInterceptorContentTypeOverrideTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot( + jar -> jar.addClasses(MediaTypeResponseInterceptor.class, TestResource.class)); + + @Test + public void producesMediaTypePresentInWriterInterceptor() { + RestAssured.when().get("/test").then().body(Matchers.containsString("Hello")); + } + + @Path("/test") + public static class TestResource { + + @GET + @Produces(MediaType.TEXT_XML) + public Response hello() { + Greeting greeting = new Greeting("Hello"); + return Response.ok(greeting).type(MediaType.TEXT_PLAIN).build(); + } + } + + public record Greeting(String message) { + } + + @Priority(5000) + @Provider + public static class MediaTypeResponseInterceptor implements jakarta.ws.rs.ext.WriterInterceptor { + + @Override + public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { + if (!context.getMediaType().isCompatible(MediaType.TEXT_PLAIN_TYPE)) { + throw new IllegalStateException("MediaType was not overridden by Response, got: " + context.getMediaType() + + " instead of expected: " + MediaType.TEXT_PLAIN); + } + context.proceed(); + } + } +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeTest.java new file mode 100644 index 0000000000000..ce889e9488b03 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/WriterInterceptorContentTypeTest.java @@ -0,0 +1,59 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; +import jakarta.ws.rs.ext.WriterInterceptorContext; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class WriterInterceptorContentTypeTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot( + jar -> jar.addClasses(MediaTypeResponseInterceptor.class, TestResource.class)); + + @Test + public void producesMediaTypePresentInWriterInterceptor() { + RestAssured.when().get("/test").then().body(Matchers.containsString("Hello")); + } + + @Path("/test") + public static class TestResource { + + @GET + @Produces(MediaType.TEXT_XML) + public Response hello() { + Greeting greeting = new Greeting("Hello"); + return Response.ok(greeting).build(); + } + } + + public record Greeting(String message) { + } + + @Priority(5000) + @Provider + public static class MediaTypeResponseInterceptor implements jakarta.ws.rs.ext.WriterInterceptor { + + @Override + public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { + if (!context.getMediaType().isCompatible(MediaType.TEXT_XML_TYPE)) { + throw new IllegalStateException("MediaType was not provided, got: " + context.getMediaType() + + " instead of expected: " + MediaType.TEXT_XML); + } + context.proceed(); + } + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java index e5b51941aa3ff..a52550eb5aabc 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/ServerSerialisers.java @@ -262,8 +262,8 @@ public static boolean invokeWriter(ResteasyReactiveRequestContext context, Objec public static void runWriterInterceptors(ResteasyReactiveRequestContext context, Object entity, MessageBodyWriter writer, Response response, WriterInterceptor[] writerInterceptor, ServerSerialisers serialisers) throws IOException { WriterInterceptorContextImpl wc = new WriterInterceptorContextImpl(context, writerInterceptor, writer, - context.getAllAnnotations(), entity.getClass(), context.getGenericReturnType(), entity, response.getMediaType(), - response.getHeaders(), serialisers); + context.getAllAnnotations(), entity.getClass(), context.getGenericReturnType(), entity, + context.getResponseContentType().getMediaType(), response.getHeaders(), serialisers); wc.proceed(); } From 27381e30047040c4b2fb779a64ef6b7e99cb4367 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 31 Oct 2024 10:04:33 +0100 Subject: [PATCH 2/2] Populate the default content type even when using Response Even when we use Response, it's interesting to push the default content type to the context as it might get used by ContainerResponseFilters. If the Response actually overrides it, it's all taken care of. Fixes #34839 --- ...onseFilterContentTypeNoResolutionTest.java | 60 ++++++++++++++++++ ...ResponseFilterContentTypeOverrideTest.java | 61 +++++++++++++++++++ ...ontainerResponseFilterContentTypeTest.java | 60 ++++++++++++++++++ .../startup/RuntimeResourceDeployment.java | 14 +++++ ...sSetDefaultContentTypeResponseHandler.java | 57 +++++++++++++++++ ...sSetDefaultContentTypeResponseHandler.java | 49 +++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeNoResolutionTest.java create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeOverrideTest.java create mode 100644 extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeTest.java create mode 100644 independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesSetDefaultContentTypeResponseHandler.java create mode 100644 independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesSetDefaultContentTypeResponseHandler.java diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeNoResolutionTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeNoResolutionTest.java new file mode 100644 index 0000000000000..c1b1e1e2f0e87 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeNoResolutionTest.java @@ -0,0 +1,60 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ContainerResponseFilterContentTypeNoResolutionTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot( + jar -> jar.addClasses(MediaTypeContainerResponseFilter.class, TestResource.class)); + + @Test + public void producesMediaTypePresentInWriterInterceptor() { + RestAssured + .given().accept("text/*") + .when().get("/test").then().statusCode(406); + } + + @Path("/test") + public static class TestResource { + + @GET + @Produces("text/*") + public Response hello() { + Greeting greeting = new Greeting("Hello"); + return Response.ok(greeting).build(); + } + } + + public record Greeting(String message) { + } + + @Priority(5000) + @Provider + public static class MediaTypeContainerResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + if (responseContext.getMediaType() != null) { + throw new IllegalStateException( + "MediaType shouldn't have been resolved but got: " + responseContext.getMediaType()); + } + } + } +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeOverrideTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeOverrideTest.java new file mode 100644 index 0000000000000..627246b0e45d1 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeOverrideTest.java @@ -0,0 +1,61 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ContainerResponseFilterContentTypeOverrideTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot( + jar -> jar.addClasses(MediaTypeContainerResponseFilter.class, TestResource.class)); + + @Test + public void producesMediaTypePresentInWriterInterceptor() { + RestAssured.when().get("/test").then().body(Matchers.containsString("Hello")); + } + + @Path("/test") + public static class TestResource { + + @GET + @Produces(MediaType.TEXT_XML) + public Response hello() { + Greeting greeting = new Greeting("Hello"); + return Response.ok(greeting).type(MediaType.TEXT_PLAIN).build(); + } + } + + public record Greeting(String message) { + } + + @Priority(5000) + @Provider + public static class MediaTypeContainerResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + if (!responseContext.getMediaType().isCompatible(MediaType.TEXT_PLAIN_TYPE)) { + throw new IllegalStateException( + "MediaType was not overridden by Response, got: " + responseContext.getMediaType() + + " instead of expected: " + MediaType.TEXT_PLAIN); + } + } + } +} diff --git a/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeTest.java b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeTest.java new file mode 100644 index 0000000000000..7c290278784e4 --- /dev/null +++ b/extensions/resteasy-reactive/rest/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/providers/ContainerResponseFilterContentTypeTest.java @@ -0,0 +1,60 @@ +package io.quarkus.resteasy.reactive.server.test.providers; + +import java.io.IOException; + +import jakarta.annotation.Priority; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.Provider; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ContainerResponseFilterContentTypeTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest().withApplicationRoot( + jar -> jar.addClasses(MediaTypeContainerResponseFilter.class, TestResource.class)); + + @Test + public void producesMediaTypePresentInWriterInterceptor() { + RestAssured.when().get("/test").then().body(Matchers.containsString("Hello")); + } + + @Path("/test") + public static class TestResource { + + @GET + @Produces(MediaType.TEXT_XML) + public Response hello() { + Greeting greeting = new Greeting("Hello"); + return Response.ok(greeting).build(); + } + } + + public record Greeting(String message) { + } + + @Priority(5000) + @Provider + public static class MediaTypeContainerResponseFilter implements ContainerResponseFilter { + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) + throws IOException { + if (!responseContext.getMediaType().isCompatible(MediaType.TEXT_XML_TYPE)) { + throw new IllegalStateException("MediaType was not provided, got: " + responseContext.getMediaType() + + " instead of expected: " + MediaType.TEXT_XML); + } + } + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 2bc5999117537..ebb44b796a686 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -70,6 +70,7 @@ import org.jboss.resteasy.reactive.server.handlers.BlockingHandler; import org.jboss.resteasy.reactive.server.handlers.ExceptionHandler; import org.jboss.resteasy.reactive.server.handlers.FixedProducesHandler; +import org.jboss.resteasy.reactive.server.handlers.FixedProducesSetDefaultContentTypeResponseHandler; import org.jboss.resteasy.reactive.server.handlers.FormBodyHandler; import org.jboss.resteasy.reactive.server.handlers.InputHandler; import org.jboss.resteasy.reactive.server.handlers.InstanceHandler; @@ -85,6 +86,7 @@ import org.jboss.resteasy.reactive.server.handlers.ResponseWriterHandler; import org.jboss.resteasy.reactive.server.handlers.SseResponseWriterHandler; import org.jboss.resteasy.reactive.server.handlers.VariableProducesHandler; +import org.jboss.resteasy.reactive.server.handlers.VariableProducesSetDefaultContentTypeResponseHandler; import org.jboss.resteasy.reactive.server.mapping.RuntimeResource; import org.jboss.resteasy.reactive.server.mapping.URITemplate; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; @@ -472,6 +474,18 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, : ScoreSystem.Diagnostic.WriterNotRequired); } } else { + if (method.getProduces() != null && method.getProduces().length > 0) { + if (method.getProduces().length == 1) { + MediaType mediaType = MediaType.valueOf(method.getProduces()[0]); + if (mediaType.isWildcardType() || mediaType.isWildcardSubtype()) { + handlers.add(new VariableProducesSetDefaultContentTypeResponseHandler(serverMediaType)); + } else { + handlers.add(new FixedProducesSetDefaultContentTypeResponseHandler(mediaType)); + } + } else { + handlers.add(new VariableProducesSetDefaultContentTypeResponseHandler(serverMediaType)); + } + } score.add(ScoreSystem.Category.Writer, ScoreSystem.Diagnostic.WriterRunTime); } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesSetDefaultContentTypeResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesSetDefaultContentTypeResponseHandler.java new file mode 100644 index 0000000000000..7f2ace85ffb6e --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesSetDefaultContentTypeResponseHandler.java @@ -0,0 +1,57 @@ +package org.jboss.resteasy.reactive.server.handlers; + +import java.util.List; +import java.util.Locale; + +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + +import org.jboss.resteasy.reactive.server.core.EncodedMediaType; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +/** + * Handler that defines the default content type when a Response is returned. + * While it might not be the final content type, we still need to make sure + * the default content type is provided to {@code ContainerResponseFilter}. + *

+ * This particular one is for endpoints that only produce one content type. + */ +@SuppressWarnings("ForLoopReplaceableByForEach") +public class FixedProducesSetDefaultContentTypeResponseHandler implements ServerRestHandler { + + private final EncodedMediaType mediaType; + private final String mediaTypeString; + private final String mediaTypeSubstring; + + public FixedProducesSetDefaultContentTypeResponseHandler(MediaType mediaType) { + this.mediaType = new EncodedMediaType(mediaType); + this.mediaTypeString = mediaType.getType() + "/" + mediaType.getSubtype(); + this.mediaTypeSubstring = mediaType.getType() + "/*"; + } + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + List acceptValues; + if (requestContext.isProducesChecked() || + (acceptValues = (List) requestContext.getHeader(HttpHeaders.ACCEPT, false)).isEmpty()) { + requestContext.setResponseContentType(mediaType); + } else { + for (int i = 0; i < acceptValues.size(); i++) { + String accept = acceptValues.get(i); + //TODO: this needs to be optimized + if (accept.contains(mediaTypeString) || accept.contains("*/*") || accept.contains(mediaTypeSubstring)) { + requestContext.setResponseContentType(mediaType); + break; + } else { + // some clients might be sending the header with incorrect casing... + String lowercaseAccept = accept.toLowerCase(Locale.ROOT); + if (lowercaseAccept.contains(mediaTypeString) || lowercaseAccept.contains(mediaTypeSubstring)) { + requestContext.setResponseContentType(mediaType); + break; + } + } + } + } + } +} diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesSetDefaultContentTypeResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesSetDefaultContentTypeResponseHandler.java new file mode 100644 index 0000000000000..8143387e8d710 --- /dev/null +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/VariableProducesSetDefaultContentTypeResponseHandler.java @@ -0,0 +1,49 @@ +package org.jboss.resteasy.reactive.server.handlers; + +import java.util.List; + +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.MediaType; + +import org.jboss.resteasy.reactive.common.util.MediaTypeHelper; +import org.jboss.resteasy.reactive.common.util.ServerMediaType; +import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext; +import org.jboss.resteasy.reactive.server.spi.ServerRestHandler; + +/** + * Handler that defines the default content type when a Response is returned. + * While it might not be the final content type, we still need to make sure + * the default content type is provided to {@code ContainerResponseFilter}. + *

+ * This particular one negotiates the content type when there are multiple ones defined. + */ +public class VariableProducesSetDefaultContentTypeResponseHandler implements ServerRestHandler { + + private final ServerMediaType mediaTypeList; + + public VariableProducesSetDefaultContentTypeResponseHandler(ServerMediaType mediaTypeList) { + this.mediaTypeList = mediaTypeList; + } + + @Override + public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { + MediaType res = null; + List accepts = requestContext.getHttpHeaders().getRequestHeader(HttpHeaders.ACCEPT); + for (String accept : accepts) { + res = mediaTypeList.negotiateProduces(accept).getKey(); + if (res != null) { + break; + } + } + if (res == null) { // fallback to ensure that MessageBodyWriter is passed the proper media type + res = mediaTypeList.negotiateProduces(requestContext.serverRequest().getRequestHeader(HttpHeaders.ACCEPT)) + .getKey(); + } + + if (res == null || MediaTypeHelper.isUnsupportedWildcardSubtype(res)) { + return; + } + + requestContext.setResponseContentType(res); + } +}