package glaze.client.handlers; import glaze.GlazeException; import glaze.client.Response; import glaze.mime.MimeResolver; import glaze.spi.Registry; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.ResponseHandler; import org.apache.http.entity.ContentType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.base.Preconditions; /** * Response handler that maps the content of the response to a bean according to * the content-type header. [TODO Explain content-type lookup strategy] * * @param <T> * The mapper return type */ public class MapperResponseHandler<T> implements ResponseHandler<T> { private static final Logger LOGGER = LoggerFactory.getLogger(MapperResponseHandler.class); private final Class<T> type; private final ContentType override; private final ErrorHandler errorHandler; private final String namespace; public MapperResponseHandler(Class<T> type) { this(Registry.NS_DEFAULT, type, CroakErrorHandler.instance(), null); } public MapperResponseHandler(Class<T> type, ContentType override) { this(Registry.NS_DEFAULT, type, CroakErrorHandler.instance(), override); } public MapperResponseHandler(Class<T> type, ErrorHandler errorHandler) { this(Registry.NS_DEFAULT, type, errorHandler, null); } public MapperResponseHandler(String namespace, Class<T> type) { this(namespace, type, CroakErrorHandler.instance(), null); } public MapperResponseHandler(String namespace, Class<T> type, ContentType override) { this(namespace, type, CroakErrorHandler.instance(), override); } public MapperResponseHandler(String namespace, Class<T> type, ErrorHandler errorHandler) { this(namespace, type, errorHandler, null); } public MapperResponseHandler(String namespace, Class<T> type, ErrorHandler errorHandler, ContentType override) { Preconditions.checkNotNull(type, "Type must not be null"); this.type = type; this.override = override; this.errorHandler = errorHandler == null ? CroakErrorHandler.instance() : errorHandler; this.namespace = Optional.fromNullable(namespace).or(Registry.NS_DEFAULT); } @Override public T handleResponse(HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); return statusLine.getStatusCode() >= 300 ? error(response) : ok(response); } protected T error(HttpResponse httpResponse) { LOGGER.error("Got error {}", httpResponse); Response response = new Response(httpResponse); try { errorHandler.onError(response); } finally { response.discardContent(); } return null; } protected boolean isOverriden() { return override != null; } protected T ok(HttpResponse response) throws IOException { InputStream is = null; try { String contentType = isOverriden() ? override.getMimeType() : MimeResolver.resolve(response); ObjectMapper mapper = Registry.lookupMapper(namespace, contentType); HttpEntity entity = response.getEntity(); is = entity.getContent(); if (mapper == null) { throw new GlazeException(String.format("Unable to resolve mapper for type '%s' in namespace '%s'", contentType, namespace)); } return mapper.readValue(is, type); } finally { if (is != null) is.close(); } } }