All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.jersey.api.client.ClientResponse Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2010-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.jersey.api.client;

import com.sun.jersey.core.header.InBoundHeaders;
import com.sun.jersey.core.provider.CompletableReader;
import com.sun.jersey.core.util.ReaderWriter;
import com.sun.jersey.spi.MessageBodyWorkers;

import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
import javax.ws.rs.core.Response.StatusType;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.RuntimeDelegate;
import javax.ws.rs.ext.RuntimeDelegate.HeaderDelegate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.logging.Logger;

/**
 * A client (in-bound) HTTP response.
 *
 * @author [email protected]
 */
public class ClientResponse {
    private static final Logger LOGGER = Logger.getLogger(ClientResponse.class.getName());

    /**
     * Status codes defined by HTTP, see
     * {@link HTTP/1.1 documentation}.
     * Additional status codes can be added by applications by creating an implementation of {@link StatusType}.
     */
    public enum Status implements StatusType {
        /**
         * 200 OK, see {@link HTTP/1.1 documentation}.
         */
        OK(200, "OK"),
        /**
         * 201 Created, see {@link HTTP/1.1 documentation}.
         */
        CREATED(201, "Created"),
        /**
         * 202 Accepted, see {@link HTTP/1.1 documentation}.
         */
        ACCEPTED(202, "Accepted"),
        /**
         * 202 Accepted, see {@link HTTP/1.1 documentation}.
         */
        NON_AUTHORITIVE_INFORMATION(203, "Non-Authoritative Information"),
        /**
         * 204 No Content, see {@link HTTP/1.1 documentation}.
         */
        NO_CONTENT(204, "No Content"),
        /**
         * 205 Reset Content, see {@link HTTP/1.1 documentation}.
         */
        RESET_CONTENT(205, "Reset Content"),
        /**
         * 206 Reset Content, see {@link HTTP/1.1 documentation}.
         */
        PARTIAL_CONTENT(206, "Partial Content"),


        /**
         * 301 Moved Permantely, see {@link HTTP/1.1 documentation}.
         */
        MOVED_PERMANENTLY(301, "Moved Permanently"),
        /**
         * 302 Found, see {@link HTTP/1.1 documentation}.
         */
        FOUND(302, "Found"),
        /**
         * 303 See Other, see {@link HTTP/1.1 documentation}.
         */
        SEE_OTHER(303, "See Other"),
        /**
         * 304 Not Modified, see {@link HTTP/1.1 documentation}.
         */
        NOT_MODIFIED(304, "Not Modified"),
        /**
         * 305 Use Proxy, see {@link HTTP/1.1 documentation}.
         */
        USE_PROXY(305, "Use Proxy"),
        /**
         * 307 Temporary Redirect, see {@link HTTP/1.1 documentation}.
         */
        TEMPORARY_REDIRECT(307, "Temporary Redirect"),


        /**
         * 400 Bad Request, see {@link HTTP/1.1 documentation}.
         */
        BAD_REQUEST(400, "Bad Request"),
        /**
         * 401 Unauthorized, see {@link HTTP/1.1 documentation}.
         */
        UNAUTHORIZED(401, "Unauthorized"),
        /**
         * 402 Payment Required, see {@link HTTP/1.1 documentation}.
         */
        PAYMENT_REQUIRED(402, "Payment Required"),
        /**
         * 403 Forbidden, see {@link HTTP/1.1 documentation}.
         */
        FORBIDDEN(403, "Forbidden"),
        /**
         * 404 Not Found, see {@link HTTP/1.1 documentation}.
         */
        NOT_FOUND(404, "Not Found"),
        /**
         * 405 Method Not Allowed, see {@link HTTP/1.1 documentation}.
         */
        METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
        /**
         * 406 Not Acceptable, see {@link HTTP/1.1 documentation}.
         */
        NOT_ACCEPTABLE(406, "Not Acceptable"),
        /**
         * 407 Proxy Authentication Required, see {@link HTTP/1.1 documentation}.
         */
        PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
        /**
         * 408 Request Timeout, see {@link HTTP/1.1 documentation}.
         */
        REQUEST_TIMEOUT(408, "Request Timeout"),
        /**
         * 409 Conflict, see {@link HTTP/1.1 documentation}.
         */
        CONFLICT(409, "Conflict"),
        /**
         * 410 Gone, see {@link HTTP/1.1 documentation}.
         */
        GONE(410, "Gone"),
        /**
         * 411 Length Required, see {@link HTTP/1.1 documentation}.
         */
        LENGTH_REQUIRED(411, "Length Required"),
        /**
         * 412 Precondition Failed, see {@link HTTP/1.1 documentation}.
         */
        PRECONDITION_FAILED(412, "Precondition Failed"),
        /**
         * 413 Request Entity Too Large, see {@link HTTP/1.1 documentation}.
         */
        REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
        /**
         * 414 Request-URI Too Long, see {@link HTTP/1.1 documentation}.
         */
        REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
        /**
         * 415 Unsupported Media Type, see {@link HTTP/1.1 documentation}.
         */
        UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
        /**
         * 416 Requested Range Not Satisfiable, see {@link HTTP/1.1 documentation}.
         */
        REQUESTED_RANGE_NOT_SATIFIABLE(416, "Requested Range Not Satisfiable"),
        /**
         * 417 Expectation Failed, see {@link HTTP/1.1 documentation}.
         */
        EXPECTATION_FAILED(417, "Expectation Failed"),


        /**
         * 500 Internal Server Error, see {@link HTTP/1.1 documentation}.
         */
        INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
        /**
         * 501 Not Implemented, see {@link HTTP/1.1 documentation}.
         */
        NOT_IMPLEMENTED(501, "Not Implemented"),
        /**
         * 502 Bad Gateway, see {@link HTTP/1.1 documentation}.
         */
        BAD_GATEWAY(502, "Bad Gateway"),
        /**
         * 503 Service Unavailable, see {@link HTTP/1.1 documentation}.
         */
        SERVICE_UNAVAILABLE(503, "Service Unavailable"),
        /**
         * 504 Gateway Timeout, see {@link HTTP/1.1 documentation}.
         */
        GATEWAY_TIMEOUT(504, "Gateway Timeout"),
        /**
         * 505 HTTP Version Not Supported, see {@link HTTP/1.1 documentation}.
         */
        HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported");


        private final int code;
        private final String reason;
        private Family family;

        /**
         * Get the family from the response status code.
         *
         * @param statusCode Response status code.
         * @return Response status code family.
         */
        public static Family getFamilyByStatusCode(int statusCode) {
            switch(statusCode/100) {
                case 1: return Family.INFORMATIONAL;
                case 2: return Family.SUCCESSFUL;
                case 3: return Family.REDIRECTION;
                case 4: return Family.CLIENT_ERROR;
                case 5: return Family.SERVER_ERROR;
                default: return Family.OTHER;
            }
        }

        /**
         * Create a new status from status code and reason phrase.
         *
         * @param statusCode Response status code.
         * @param reasonPhrase Reason phrase.
         */
        Status(final int statusCode, final String reasonPhrase) {
            this.code = statusCode;
            this.reason = reasonPhrase;
            this.family = getFamilyByStatusCode(code);
        }

        /**
         * Get the class of status code.
         *
         * @return the class of status code.
         */
        public Family getFamily() {
            return family;
        }

        /**
         * Get the associated status code.
         *
         * @return the status code
         */
        public int getStatusCode() {
            return code;
        }

        /**
         * Get the reason phrase.
         *
         * @return the reason phrase.
         */
        public String getReasonPhrase() {
            return toString();
        }

        /**
         * Get the reason phrase.
         *
         * @return the reason phrase.
         */
        @Override
        public String toString() {
            return reason;
        }

        /**
         * Convert a numerical status code into the corresponding Status.
         *
         * @param statusCode the numerical status code.
         * @return the matching Status or null is no matching Status is defined.
         */
        public static Status fromStatusCode(final int statusCode) {
            for (Status s : Status.values()) {
                if (s.code == statusCode) {
                    return s;
                }
            }
            return null;
        }
    }

    private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];

    protected static final HeaderDelegate entityTagDelegate =
            RuntimeDelegate.getInstance().createHeaderDelegate(EntityTag.class);

    protected static final HeaderDelegate dateDelegate =
            RuntimeDelegate.getInstance().createHeaderDelegate(Date.class);

    private Map properties;

    private StatusType statusType;

    private InBoundHeaders headers;

    private boolean isEntityBuffered;

    private InputStream entity;

    private MessageBodyWorkers workers;

    /**
     * Create a new instance initialized form {@code statusType}, {@code headers}, {@code entity},
     * {@code workers}.
     *
     * @param statusType Status type.
     * @param headers HTTP headers.
     * @param entity Entity input stream.
     * @param workers Message body workers.
     */
    public ClientResponse(StatusType statusType, InBoundHeaders headers, InputStream entity,
                          MessageBodyWorkers workers) {
        this.statusType = statusType;
        this.headers = headers;
        this.entity = entity;
        this.workers = workers;
    }

    /**
     * Create a new instance initialized form {@code statusCode}, {@code headers}, {@code entity},
     * {@code workers}.
     *
     * @param headers HTTP headers.
     * @param entity Entity input stream.
     * @param workers Message body workers.
     */
    public ClientResponse(int statusCode, InBoundHeaders headers, InputStream entity,
                          MessageBodyWorkers workers) {
        this(Statuses.from(statusCode), headers, entity, workers);
    }

    /**
     * Get the client.
     *
     * @return the client.
     */
    public Client getClient() {
        return (Client)getProperties().get(Client.class.getName());
    }

    /**
     * Get the map of response properties.
     * 

* A response property is an application-defined property that may be * added by the user, a filter, or the handler that is managing the * connection. * * @return the map of response properties. */ public Map getProperties() { if (properties != null) return properties; return properties = new HashMap(); } /** * Get the status code. * * @return the status code. */ public int getStatus() { return statusType.getStatusCode(); } /** * Set the status code. * * @param status the status code. */ public void setStatus(int status) { this.statusType = Statuses.from(status); } /** * Set the {@link StatusType status type}. * * @param statusType the status type code. */ public void setStatus(Response.StatusType statusType) { this.statusType = statusType; } /** * Get the status code. * * @return the status code, or null if the underlying status code was set * using the method {@link #setStatus(int)} and there is no * mapping between the integer value and the Response.Status * enumeration value. * @deprecated Deprecated since 1.18. Use {@link #getStatusInfo()} which can * return {@link StatusType status type} even for status codes which * do not have mapping defined in {@link Response.Status}. */ @Deprecated public Status getClientResponseStatus() { return Status.fromStatusCode(statusType.getStatusCode()); } /** * Get the {@link StatusType status type}. Status type can be describe a standard response status * defined by the HTTP specification and defined in {@link Response.Status} enumeration * but also can contain status codes which are not defined by {@code Response.Status} * enumeration (for example custom status code returned from a resource method). * * @return Non-null value of the status type. */ public StatusType getStatusInfo() { return statusType; } /** * Get the status code. * * @return the status code, or null if the underlying status code was set * using the method {@link #setStatus(int)} and there is no * mapping between the integer value and the Response.Status * enumeration value. * @deprecated use {@link #getClientResponseStatus() } */ @Deprecated public Response.Status getResponseStatus() { return Response.Status.fromStatusCode(statusType.getStatusCode()); } /** * Set the status code. * * @param status the status code. * @deprecated see {@link #setStatus(javax.ws.rs.core.Response.StatusType) } */ @Deprecated public void setResponseStatus(Response.StatusType status) { setStatus(status); } /** * Get the HTTP headers of the response. * * @return the HTTP headers of the response. * @deprecated */ @Deprecated public MultivaluedMap getMetadata() { return getHeaders(); } /** * Get the HTTP headers of the response. * * @return the HTTP headers of the response. */ public MultivaluedMap getHeaders() { return headers; } /** * Checks if there is an entity available. * * @return true if there is an entity present in the response. */ public boolean hasEntity() { try { try { if (entity.available() > 0) { return true; } } catch (IOException ioe) { // NOOP. Try other approaches as this can fail on WLS when "chunked" transfer encoding is used. } if (entity.markSupported()) { entity.mark(1); int i = entity.read(); if (i == -1) { return false; } else { entity.reset(); return true; } } else { int b = entity.read(); if (b == -1) { return false; } if (!(entity instanceof PushbackInputStream)) { entity = new PushbackInputStream(entity, 1); } ((PushbackInputStream) entity).unread(b); return true; } } catch (IOException ex) { throw new ClientHandlerException(ex); } } /** * Get the input stream of the response. * * @return the input stream of the response. */ public InputStream getEntityInputStream() { return entity; } /** * Set the input stream of the response. * * @param entity the input stream of the response. */ public void setEntityInputStream(InputStream entity) { this.isEntityBuffered = false; this.entity = entity; } /** * Get the entity of the response. *

* If the entity is not an instance of Closeable then the entity * input stream is closed. * * @param the type of the response. * @param c the type of the entity. * @return an instance of the type c. * * @throws ClientHandlerException if there is an error processing the response. * @throws UniformInterfaceException if the response status is 204 (No Content). */ public T getEntity(Class c) throws ClientHandlerException, UniformInterfaceException { return getEntity(c, c); } /** * Get the entity of the response. *

* If the entity is not an instance of Closeable then this response * is closed (you cannot read it more than once, any subsequent * call will produce {@link ClientHandlerException}). * * @param the type of the response. * @param gt the generic type of the entity. * @return an instance of the type represented by the generic type. * * @throws ClientHandlerException if there is an error processing the response. * @throws UniformInterfaceException if the response status is 204 (No Content). */ public T getEntity(GenericType gt) throws ClientHandlerException, UniformInterfaceException { return getEntity(gt.getRawClass(), gt.getType()); } private T getEntity(Class c, Type type) { if (getStatus() == 204) { throw new UniformInterfaceException(this); } MediaType mediaType = getType(); if (mediaType == null) { mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE; } final MessageBodyReader br = workers.getMessageBodyReader( c, type, EMPTY_ANNOTATIONS, mediaType); if (br == null) { close(); String message = "A message body reader for Java class " + c.getName() + ", and Java type " + type + ", and MIME media type " + mediaType + " was not found"; LOGGER.severe(message); Map> m = workers.getReaders(mediaType); LOGGER.severe("The registered message body readers compatible with the MIME media type are:\n" + workers.readersToString(m)); throw new ClientHandlerException(message); } try { T t = br.readFrom(c, type, EMPTY_ANNOTATIONS, mediaType, headers, entity); if (br instanceof CompletableReader) { t = ((CompletableReader)br).complete(t); } if (!(t instanceof Closeable)) { close(); } return t; } catch (IOException ex) { close(); throw new ClientHandlerException(ex); } } /** * Buffer the entity. *

* All the bytes of the original entity input stream will be read * and stored in memory. The original entity input stream will * then be closed. * @throws ClientHandlerException if there is an error processing the response. */ public void bufferEntity() throws ClientHandlerException { if (isEntityBuffered) return; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { ReaderWriter.writeTo(entity, baos); } catch(IOException ex) { throw new ClientHandlerException(ex); } finally { close(); } entity = new ByteArrayInputStream(baos.toByteArray()); isEntityBuffered = true; } /** * Close the response. *

* The entity input stream is closed. * * @throws ClientHandlerException if there is an error closing the response. */ public void close() throws ClientHandlerException { try { entity.close(); } catch (IOException e) { throw new ClientHandlerException(e); } } /** * Get the media type of the response. * * @return the media type. */ public MediaType getType() { String ct = getHeaders().getFirst("Content-Type"); return (ct != null) ? MediaType.valueOf(ct) : null; } /** * Get the location. * * @return the location, otherwise null if not present. */ public URI getLocation() { String l = getHeaders().getFirst("Location"); return (l != null) ? URI.create(l) : null; } /** * Get the entity tag. * * @return the entity tag, otherwise null if not present. */ public EntityTag getEntityTag() { String t = getHeaders().getFirst("ETag"); return (t != null) ? entityTagDelegate.fromString(t) : null; } /** * Get the last modified date. * * @return the last modified date, otherwise null if not present. */ public Date getLastModified() { String d = getHeaders().getFirst("Last-Modified"); return (d != null) ? dateDelegate.fromString(d) : null; } /** * Get response date (server side). * * @return the server side response date, otherwise null if not present. */ public Date getResponseDate() { String d = getHeaders().getFirst("Date"); return (d != null) ? dateDelegate.fromString(d) : null; } /** * Get the language. * * @return the language, otherwise null if not present. */ public String getLanguage() { return getHeaders().getFirst("Content-Language"); } /** * Get Content-Length. * * @return Content-Length as integer if present and valid number. In other * cases returns -1. */ public int getLength() { int size = -1; String sizeStr = getHeaders().getFirst("Content-Length"); if (sizeStr == null) return -1; try { size = Integer.parseInt(sizeStr); } catch (NumberFormatException nfe) { // do nothing } return size; } /** * Get the list of cookies. * * @return the cookies. */ public List getCookies() { List hs = getHeaders().get("Set-Cookie"); if (hs == null) return Collections.emptyList(); List cs = new ArrayList(); for (String h : hs) { cs.add(NewCookie.valueOf(h)); } return cs; } /** * Get the allowed HTTP methods from the Allow HTTP header. *

* Note that the Allow HTTP header will be returned from an OPTIONS * request. * * @return the allowed HTTP methods, all methods will returned as * upper case strings. */ public Set getAllow() { String allow = headers.getFirst("Allow"); if (allow == null) return Collections.emptySet(); Set allowedMethods = new HashSet(); StringTokenizer tokenizer = new StringTokenizer(allow, ","); while (tokenizer.hasMoreTokens()) { String m = tokenizer.nextToken().trim(); if (m.length() > 0) allowedMethods.add(m.toUpperCase()); } return allowedMethods; } public WebResourceLinkHeaders getLinks() { return new WebResourceLinkHeaders(getClient(), getHeaders()); } @Override public String toString() { return "Client response status: " + statusType.getStatusCode(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy