/******************************************************************************* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *******************************************************************************/ package org.apache.wink.server.internal.handlers; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.wink.common.internal.i18n.Messages; import org.apache.wink.common.internal.registry.ProvidersRegistry; import org.apache.wink.common.internal.registry.metadata.MethodMetadata; import org.apache.wink.common.internal.utils.MediaTypeUtils; import org.apache.wink.server.handlers.AbstractHandler; import org.apache.wink.server.handlers.MessageContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class PopulateResponseMediaTypeHandler extends AbstractHandler { private static final Logger logger = LoggerFactory.getLogger(PopulateResponseMediaTypeHandler.class); private static final MediaType APPLICATION_TYPE = new MediaType("application", "*"); //$NON-NLS-1$ //$NON-NLS-2$ private boolean errorFlow = false; public void handleResponse(MessageContext context) throws Throwable { MediaType responseMediaType = null; Object result = context.getResponseEntity(); boolean debug = logger.isDebugEnabled(); if (result == null) { if (debug) { logger.trace("No entity so no Content-Type needs to be set"); //$NON-NLS-1$ } return; } if (result instanceof Response) { Response response = (Response)result; if (response.getEntity() == null) { if (debug) { logger.trace("No entity so no Content-Type needs to be set"); //$NON-NLS-1$ } return; } Object first = response.getMetadata().getFirst(HttpHeaders.CONTENT_TYPE); if (first != null) { if (first instanceof MediaType) { responseMediaType = (MediaType)first; } else { responseMediaType = MediaType.valueOf(first.toString()); } } if (debug) { logger.trace("Content-Type was set by application to {}", responseMediaType); //$NON-NLS-1$ } } if (responseMediaType == null) { Set<MediaType> producedMime = null; SearchResult searchResult = context.getAttribute(SearchResult.class); if (searchResult != null && searchResult.isFound()) { MethodMetadata methodMetadata = searchResult.getMethod().getMetadata(); producedMime = methodMetadata.getProduces(); if (debug) { logger.trace("Determining Content-Type from @Produces on method: {}", producedMime); //$NON-NLS-1$ } } if (producedMime == null || producedMime.isEmpty()) { if (result instanceof Response) { Response response = (Response)result; producedMime = context.getAttribute(ProvidersRegistry.class).getMessageBodyWriterMediaTypes(response.getEntity().getClass()); if (debug) { logger.trace("Determining Content-Type from compatible generic type to {} from MessageBodyWriters: {}", //$NON-NLS-1$ response.getEntity().getClass(), producedMime); } } else { producedMime = context.getAttribute(ProvidersRegistry.class).getMessageBodyWriterMediaTypes(result.getClass()); if (debug) { logger.trace("Determining Content-Type from compatible generic type to {} from MessageBodyWriters: {}", //$NON-NLS-1$ result.getClass(), producedMime); } } /* * This is to inform the application developer that they should * specify the Content-Type. */ if (debug) { logger.debug(Messages.getMessage("populateResponseMediaTypeHandlerFromCompatibleMessageBodyWriters")); //$NON-NLS-1$ } } if (producedMime.isEmpty()) { producedMime.add(MediaType.WILDCARD_TYPE); } List<MediaType> acceptableMediaTypes = context.getHttpHeaders().getAcceptableMediaTypes(); // collect all candidates List<CandidateMediaType> candidates = new LinkedList<CandidateMediaType>(); for (MediaType acceptableMediaType : acceptableMediaTypes) { for (MediaType mediaType : producedMime) { if (debug) { logger.trace("Comparing {} to {}", acceptableMediaType, mediaType); //$NON-NLS-1$ } if (mediaType.isCompatible(acceptableMediaType)) { MediaType candidateMediaType = null; if (MediaTypeUtils.compareTo(mediaType, acceptableMediaType) > 0) { candidateMediaType = mediaType; } else { candidateMediaType = acceptableMediaType; } if (debug) { logger.trace("MediaType compatible so using candidate type {}", candidateMediaType); //$NON-NLS-1$ } String q = acceptableMediaType.getParameters().get("q"); //$NON-NLS-1$ CandidateMediaType candidate = new CandidateMediaType(candidateMediaType, q); if (Double.compare(candidate.q, 0.0) != 0) { if (debug) { logger.trace("Candidate {} has q value {} so adding to possible candidates", candidate.getMediaType(), q); //$NON-NLS-1$ } candidates.add(candidate); } } } } // there are no candidates if (candidates.isEmpty()) { if (isErrorFlow()) { if (debug) { logger.trace("Error flow and no candidates so not going to set a Content-Type"); //$NON-NLS-1$ } return; } logger.info(Messages.getMessage("populateResponseMediaTypeHandlerNoAcceptableResponse")); //$NON-NLS-1$ throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE); } // select the best candidate. // we don't need to sort the whole thing, just to select the best // one CandidateMediaType max = null; boolean useOctetStream = false; for (CandidateMediaType candidate : candidates) { if (max == null) { max = candidate; if (debug) { logger.trace("No previous best candidate so using candidate {}", max.getMediaType()); //$NON-NLS-1$ } } else { // select the more specific media type before a media type // that has a wildcard in it // even if its q value is greater int comparison = MediaTypeUtils.compareTo(candidate.getMediaType(), max.getMediaType()); if (comparison > 0) { max = candidate; if (debug) { logger.trace("Best candidate is now {} because it was a more specific media type", max.getMediaType()); //$NON-NLS-1$ } } else if (comparison == 0 && candidate.getQ() > max.getQ()) { max = candidate; if (debug) { logger.trace("Best candidate is now {} because it had a higher quality value {} compared to {} with quality value {}", //$NON-NLS-1$ new Object[] {max.getMediaType(), max.getQ(), candidate, candidate.getQ()}); } } } if (!useOctetStream && (candidate.getMediaType().equals(MediaType.WILDCARD_TYPE) || candidate.getMediaType().equals(APPLICATION_TYPE))) { if (debug) { logger.trace("If necessary, use an application/octet-stream because there is a wildcard", candidate.getMediaType()); //$NON-NLS-1$ } useOctetStream = true; } } if (max.getMediaType().isWildcardSubtype() == false) { responseMediaType = max.getMediaType(); } else if (useOctetStream) { if (debug) { logger.trace("Content-Type was reset to application/octet-stream because it was either */* or was application/*"); //$NON-NLS-1$ } responseMediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE; } else { if (isErrorFlow()) { if (debug) { logger.trace("Error flow so not going to set a response Content-Type"); //$NON-NLS-1$ } return; } logger.info(Messages.getMessage("populateResponseMediaTypeHandlerNoAcceptableResponse")); //$NON-NLS-1$ throw new WebApplicationException(Response.Status.NOT_ACCEPTABLE); } } if (debug) { logger.trace("Response Content-Type will be set to {}", responseMediaType); //$NON-NLS-1$ } context.setResponseMediaType(responseMediaType); } public void setErrorFlow(boolean errorFlow) { this.errorFlow = errorFlow; } public boolean isErrorFlow() { return errorFlow; } private static class CandidateMediaType { private MediaType mediaType; private double q; public CandidateMediaType(MediaType mediaType, String q) { this.mediaType = mediaType; if (q != null) { this.q = Double.parseDouble(q); } else { this.q = 1.0d; } } public MediaType getMediaType() { return mediaType; } public double getQ() { return q; } } }