package org.jboss.resteasy.core; import org.jboss.resteasy.core.interception.jaxrs.AbstractWriterInterceptorContext; import org.jboss.resteasy.core.interception.jaxrs.ContainerResponseContextImpl; import org.jboss.resteasy.core.interception.jaxrs.ResponseContainerRequestContext; import org.jboss.resteasy.core.interception.jaxrs.ServerWriterInterceptorContext; import org.jboss.resteasy.core.registry.SegmentNode; import org.jboss.resteasy.resteasy_jaxrs.i18n.*; import org.jboss.resteasy.specimpl.BuiltResponse; import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.ResteasyDeployment; import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.util.CommitHeaderOutputStream; import org.jboss.resteasy.util.HttpHeaderNames; import org.jboss.resteasy.util.MediaTypeHelper; import javax.ws.rs.NotAcceptableException; import javax.ws.rs.Produces; import javax.ws.rs.container.ContainerResponseFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.NewCookie; import javax.ws.rs.core.Response; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.WriterInterceptor; 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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $ */ public class ServerResponseWriter { public static void writeNomapResponse(BuiltResponse jaxrsResponse, final HttpRequest request, final HttpResponse response, final ResteasyProviderFactory providerFactory) throws IOException { ResourceMethodInvoker method =(ResourceMethodInvoker) request.getAttribute(ResourceMethodInvoker.class.getName()); if (jaxrsResponse.getEntity() != null) { if (jaxrsResponse.getMediaType() == null) { setDefaultContentType(request, jaxrsResponse, providerFactory, method); } boolean addCharset = true; ResteasyDeployment deployment = ResteasyProviderFactory.getContextData(ResteasyDeployment.class); if (deployment != null) { addCharset = deployment.isAddCharset(); } if (addCharset) { MediaType mt = null; Object o = jaxrsResponse.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); if (o instanceof MediaType) { mt = (MediaType) o; } else { mt = MediaType.valueOf(o.toString()); } if (!mt.getParameters().containsKey(MediaType.CHARSET_PARAMETER)) { if (MediaTypeHelper.isTextLike(mt)) { jaxrsResponse.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, mt.withCharset(StandardCharsets.UTF_8.toString()).toString()); } } } } executeFilters(jaxrsResponse, request, response, providerFactory, method); //[RESTEASY-1627] check on response.getOutputStream() to avoid resteasy-netty4 trying building a chunked response body for HEAD requests if (jaxrsResponse.getEntity() == null || response.getOutputStream() == null) { response.setStatus(jaxrsResponse.getStatus()); commitHeaders(jaxrsResponse, response); return; } Class type = jaxrsResponse.getEntityClass(); Object ent = jaxrsResponse.getEntity(); Type generic = jaxrsResponse.getGenericType(); Annotation[] annotations = jaxrsResponse.getAnnotations(); @SuppressWarnings(value = "unchecked") MessageBodyWriter writer = providerFactory.getMessageBodyWriter( type, generic, annotations, jaxrsResponse.getMediaType()); if (writer!=null) LogMessages.LOGGER.debugf("MessageBodyWriter: %s", writer.getClass().getName()); if (writer == null) { throw new NoMessageBodyWriterFoundFailure(type, jaxrsResponse.getMediaType()); } response.setStatus(jaxrsResponse.getStatus()); final BuiltResponse built = jaxrsResponse; CommitHeaderOutputStream.CommitCallback callback = new CommitHeaderOutputStream.CommitCallback() { private boolean committed; @Override public void commit() { if (committed) return; committed = true; commitHeaders(built, response); } }; OutputStream os = new CommitHeaderOutputStream(response.getOutputStream(), callback); WriterInterceptor[] writerInterceptors = null; if (method != null) { writerInterceptors = method.getWriterInterceptors(); } else { writerInterceptors = providerFactory.getServerWriterInterceptorRegistry().postMatch(null, null); } AbstractWriterInterceptorContext writerContext = new ServerWriterInterceptorContext(writerInterceptors, providerFactory, ent, type, generic, annotations, jaxrsResponse.getMediaType(), jaxrsResponse.getMetadata(), os, request); writerContext.proceed(); callback.commit(); // just in case the output stream is never used } private static void executeFilters(BuiltResponse jaxrsResponse, HttpRequest request, HttpResponse response, ResteasyProviderFactory providerFactory, ResourceMethodInvoker method) throws IOException { ContainerResponseFilter[] responseFilters = null; if (method != null) { responseFilters = method.getResponseFilters(); } else { responseFilters = providerFactory.getContainerResponseFilterRegistry().postMatch(null, null); } if (responseFilters != null) { ResponseContainerRequestContext requestContext = new ResponseContainerRequestContext(request); ContainerResponseContextImpl responseContext = new ContainerResponseContextImpl(request, response, jaxrsResponse); for (ContainerResponseFilter filter : responseFilters) { filter.filter(requestContext, responseContext); } } } protected static void setDefaultContentType(HttpRequest request, BuiltResponse jaxrsResponse, ResteasyProviderFactory providerFactory, ResourceMethodInvoker method) { // Note. If we get here before the request is executed, e.g., if a ContainerRequestFilter aborts, // chosen and method can be null. MediaType chosen = (MediaType)request.getAttribute(SegmentNode.RESTEASY_CHOSEN_ACCEPT); boolean hasProduces = chosen != null && Boolean.valueOf(chosen.getParameters().get(SegmentNode.RESTEASY_SERVER_HAS_PRODUCES)); hasProduces |= method != null && method.getProduces() != null && method.getProduces().length > 0; hasProduces |= method != null && method.getMethod().getClass().getAnnotation(Produces.class) != null; if (hasProduces) { if (chosen == null || chosen.isWildcardSubtype()) { if (method != null) { // pick most specific if (method.getProduces().length > 0) { for (MediaType produce : method.getProduces()) { if (!produce.isWildcardType()) { chosen = produce; if (!produce.isWildcardSubtype()) { break; } } } } else { method.getMethod().getClass().getAnnotation(Produces.class); for (String produceString : method.getMethod().getClass().getAnnotation(Produces.class).value()) { MediaType produce = MediaType.valueOf(produceString); if (!produce.isWildcardType()) { chosen = produce; if (!produce.isWildcardSubtype()) { break; } } } } } } } else { if (chosen == null) { chosen = MediaType.WILDCARD_TYPE; } Class type = jaxrsResponse.getEntityClass(); Object ent = jaxrsResponse.getEntity(); Type generic = jaxrsResponse.getGenericType(); if (generic == null) { if (method != null && !Response.class.isAssignableFrom(method.getMethod().getReturnType())) generic = method.getGenericReturnType(); else generic = type; } Annotation[] annotations = jaxrsResponse.getAnnotations(); if (annotations == null && method != null) { annotations = method.getMethodAnnotations(); } List<MessageBodyWriter<?>> mbrs = providerFactory.getPossibleMessageBodyWriters(type, generic, annotations); List<MediaType> accepts = request.getHttpHeaders().getAcceptableMediaTypes(); List<SortableMediaType> M = new ArrayList<SortableMediaType>(); for (MediaType accept : accepts) { for (MessageBodyWriter<?> mbr : mbrs) { Produces produces = mbr.getClass().getAnnotation(Produces.class); for (String producesString : produces.value()) { MediaType produce = MediaType.valueOf(producesString); if (produce.isCompatible(accept)) { M.add(mostSpecific(produce, accept)); } } } } if (M.size() > 0) { Collections.sort(M); chosen = M.get(M.size() - 1); } } if (chosen.isWildcardType()) { chosen = MediaType.APPLICATION_OCTET_STREAM_TYPE; } else if (chosen.isWildcardSubtype() && chosen.getSubtype().equals("application")) { chosen = MediaType.APPLICATION_OCTET_STREAM_TYPE; } else if (chosen.isWildcardSubtype()) { throw new NotAcceptableException(Messages.MESSAGES.illegalResponseMediaType(chosen.toString())); } if (chosen.getParameters().containsKey(SegmentNode.RESTEASY_SERVER_HAS_PRODUCES)) { Map<String, String> map = new HashMap<String, String>(chosen.getParameters()); map.remove(SegmentNode.RESTEASY_SERVER_HAS_PRODUCES); map.remove(SegmentNode.RESTEASY_SERVER_HAS_PRODUCES.toLowerCase()); chosen = new MediaType(chosen.getType(), chosen.getSubtype(), map); } jaxrsResponse.getHeaders().putSingle(HttpHeaders.CONTENT_TYPE, chosen); } public static MediaType resolveContentType(BuiltResponse response) { MediaType responseContentType = null; Object type = response.getMetadata().getFirst(HttpHeaderNames.CONTENT_TYPE); if (type == null) { return MediaType.WILDCARD_TYPE; } if (type instanceof MediaType) { responseContentType = (MediaType) type; } else { responseContentType = MediaType.valueOf(type.toString()); } return responseContentType; } public static void commitHeaders(BuiltResponse jaxrsResponse, HttpResponse response) { if (jaxrsResponse.getMetadata() != null) { List<Object> cookies = jaxrsResponse.getMetadata().get( HttpHeaderNames.SET_COOKIE); if (cookies != null) { Iterator<Object> it = cookies.iterator(); while (it.hasNext()) { Object next = it.next(); if (next instanceof NewCookie) { NewCookie cookie = (NewCookie) next; response.addNewCookie(cookie); it.remove(); } } if (cookies.size() < 1) jaxrsResponse.getMetadata().remove(HttpHeaderNames.SET_COOKIE); } } if (jaxrsResponse.getMetadata() != null && jaxrsResponse.getMetadata().size() > 0) { response.getOutputHeaders().putAll(jaxrsResponse.getMetadata()); } } private static class SortableMediaType extends MediaType implements Comparable<SortableMediaType> { double q = -1; double qs = -1; public SortableMediaType(MediaType m) { this(m.getType(), m.getSubtype(), m.getParameters()); } public SortableMediaType(String type, String subtype, Map<String, String> parameters) { super(type, subtype, parameters); String qString = parameters.get("q"); if (qString != null) { try { q = Double.valueOf(qString); } catch (NumberFormatException e) { // skip } } if (q < 0) { String qsString = parameters.get("qs"); if (qsString != null) { try { qs = Double.valueOf(qsString); } catch (NumberFormatException e) { // skip } } } } @Override public int compareTo(SortableMediaType o) { if (this.isCompatible(o)) { if (this.equals(o)) { return 0; } MediaType mostSpecific = mostSpecific(this, o); return this.equals(mostSpecific(this, o)) ? 1 : -1; } if (this.q < o.q) { return -1; } if (this.q > o.q) { return 1; } // this.q == o.q if (this.qs < o.qs) { return -1; } if (this.qs > o.qs) { return 1; } return 0; } } /** * m1, m2 are compatible */ private static SortableMediaType mostSpecific(MediaType m1, MediaType m2) { MediaType m = null; if (m1.getType().equals("*")) { if (m2.getType().equals("*")) { if (m1.getSubtype().equals("*")) { return new SortableMediaType(m2); // */* <= */? } else { return new SortableMediaType(m1); // */st > */? } } else { return new SortableMediaType(m2); // */? < t/? } } else { if (m2.getType().equals("*")) { return new SortableMediaType(m1); // t/? > */? } else { if (m1.getSubtype().equals("*")) { return new SortableMediaType(m2); // t/* <= t/? } else { return new SortableMediaType(m1); // t/st >= t/? } } } } }