/*
 * Copyright (c) 2012, 2019 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.client;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.net.URI;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;

import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
import org.glassfish.jersey.message.internal.InboundMessageContext;
import org.glassfish.jersey.message.internal.OutboundJaxrsResponse;
import org.glassfish.jersey.message.internal.Statuses;

Jersey client response context.
Author:Marek Potociar
/** * Jersey client response context. * * @author Marek Potociar */
public class ClientResponse extends InboundMessageContext implements ClientResponseContext, InjectionManagerSupplier { private Response.StatusType status; private final ClientRequest requestContext; private URI resolvedUri;
Create new Jersey client response context initialized from a JAX-RS response.
Params:
  • requestContext – associated request context.
  • response – JAX-RS response to be used to initialize the response context.
/** * Create new Jersey client response context initialized from a JAX-RS {@link Response response}. * * @param requestContext associated request context. * @param response JAX-RS response to be used to initialize the response context. */
public ClientResponse(final ClientRequest requestContext, final Response response) { this(response.getStatusInfo(), requestContext); this.headers(OutboundJaxrsResponse.from(response, requestContext.getConfiguration()).getContext().getStringHeaders()); final Object entity = response.getEntity(); if (entity != null) { InputStream entityStream = new InputStream() { private ByteArrayInputStream byteArrayInputStream = null; @Override public int read() throws IOException { if (byteArrayInputStream == null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream stream = null; try { try { stream = requestContext.getWorkers().writeTo( entity, entity.getClass(), null, null, response.getMediaType(), response.getMetadata(), requestContext.getPropertiesDelegate(), baos, Collections.<WriterInterceptor>emptyList()); } finally { if (stream != null) { stream.close(); } } } catch (IOException e) { // ignore } byteArrayInputStream = new ByteArrayInputStream(baos.toByteArray()); } return byteArrayInputStream.read(); } }; setEntityStream(entityStream); } }
Create a new Jersey client response context.
Params:
  • status – response status.
  • requestContext – associated client request context.
/** * Create a new Jersey client response context. * * @param status response status. * @param requestContext associated client request context. */
public ClientResponse(Response.StatusType status, ClientRequest requestContext) { this(status, requestContext, requestContext.getUri()); }
Create a new Jersey client response context.
Params:
  • status – response status.
  • requestContext – associated client request context.
  • resolvedRequestUri – resolved request URI (see getResolvedRequestUri()).
/** * Create a new Jersey client response context. * * @param status response status. * @param requestContext associated client request context. * @param resolvedRequestUri resolved request URI (see {@link #getResolvedRequestUri()}). */
public ClientResponse(Response.StatusType status, ClientRequest requestContext, URI resolvedRequestUri) { super(requestContext.getConfiguration()); this.status = status; this.resolvedUri = resolvedRequestUri; this.requestContext = requestContext; setWorkers(requestContext.getWorkers()); } @Override public int getStatus() { return status.getStatusCode(); } @Override public void setStatus(int code) { this.status = Statuses.from(code); } @Override public void setStatusInfo(Response.StatusType status) { if (status == null) { throw new NullPointerException(LocalizationMessages.CLIENT_RESPONSE_STATUS_NULL()); } this.status = status; } @Override public Response.StatusType getStatusInfo() { return status; }
Get the absolute URI of the ultimate request made to receive this response.

The returned URI points to the ultimate location of the requested resource that provided the data represented by this response instance. Because Jersey client connectors may be configured to automatically follow redirect responses, the value of the URI returned by this method may be different from the value of the original request URI that can be retrieved using response.getRequestContext().getUri() chain of method calls.

See Also:
Returns:absolute URI of the ultimate request made to receive this response.
Since:2.6
/** * Get the absolute URI of the ultimate request made to receive this response. * <p> * The returned URI points to the ultimate location of the requested resource that * provided the data represented by this response instance. Because Jersey client connectors * may be configured to {@link ClientProperties#FOLLOW_REDIRECTS * automatically follow redirect responses}, the value of the URI returned by this method may * be different from the value of the {@link javax.ws.rs.client.ClientRequestContext#getUri() * original request URI} that can be retrieved using {@code response.getRequestContext().getUri()} * chain of method calls. * </p> * * @return absolute URI of the ultimate request made to receive this response. * * @see ClientProperties#FOLLOW_REDIRECTS * @see #setResolvedRequestUri(java.net.URI) * @since 2.6 */
public URI getResolvedRequestUri() { return resolvedUri; }
Set the absolute URI of the ultimate request that was made to receive this response.

If the original request URI has been modified (e.g. due to redirections), the absolute URI of the ultimate request being made to receive the response should be set by the caller on the response instance using this method.

Params:
  • uri – absolute URI of the ultimate request made to receive this response. Must not be null.
Throws:
See Also:
Since:2.6
/** * Set the absolute URI of the ultimate request that was made to receive this response. * <p> * If the original request URI has been modified (e.g. due to redirections), the absolute URI of * the ultimate request being made to receive the response should be set by the caller * on the response instance using this method. * </p> * * @param uri absolute URI of the ultimate request made to receive this response. Must not be {@code null}. * @throws java.lang.NullPointerException in case the passed {@code uri} parameter is null. * @throws java.lang.IllegalArgumentException in case the passed {@code uri} parameter does * not represent an absolute URI. * @see ClientProperties#FOLLOW_REDIRECTS * @see #getResolvedRequestUri() * @since 2.6 */
public void setResolvedRequestUri(final URI uri) { if (uri == null) { throw new NullPointerException(LocalizationMessages.CLIENT_RESPONSE_RESOLVED_URI_NULL()); } if (!uri.isAbsolute()) { throw new IllegalArgumentException(LocalizationMessages.CLIENT_RESPONSE_RESOLVED_URI_NOT_ABSOLUTE()); } this.resolvedUri = uri; }
Get the associated client request context paired with this response context.
Returns:associated client request context.
/** * Get the associated client request context paired with this response context. * * @return associated client request context. */
public ClientRequest getRequestContext() { return requestContext; } @Override public Map<String, NewCookie> getCookies() { return super.getResponseCookies(); } @Override public Set<Link> getLinks() { return super.getLinks() .stream() .map(link -> { if (link.getUri().isAbsolute()) { return link; } return Link.fromLink(link).baseUri(getResolvedRequestUri()).build(); }) .collect(Collectors.toSet()); } @Override public String toString() { return "ClientResponse{" + "method=" + requestContext.getMethod() + ", uri=" + requestContext.getUri() + ", status=" + status.getStatusCode() + ", reason=" + status.getReasonPhrase() + "}"; }
Get the message entity Java instance. Returns null if the message does not contain an entity body.

If the entity is represented by an un-consumed input stream the method will return the input stream.

Throws:
See Also:
Returns:the message entity or null if message does not contain an entity body (i.e. when InboundMessageContext.hasEntity() returns false).
Since:2.5
/** * Get the message entity Java instance. Returns {@code null} if the message * does not contain an entity body. * <p> * If the entity is represented by an un-consumed {@link InputStream input stream} * the method will return the input stream. * </p> * * @return the message entity or {@code null} if message does not contain an * entity body (i.e. when {@link #hasEntity()} returns {@code false}). * @throws IllegalStateException if the entity was previously fully consumed * as an {@link InputStream input stream}, or * if the response has been {@link #close() closed}. * @see javax.ws.rs.core.Response#getEntity() * @since 2.5 */
public Object getEntity() throws IllegalStateException { // TODO implement some advanced caching support? return getEntityStream(); }
Read the message entity input stream as an instance of specified Java type using a MessageBodyReader that supports mapping the message entity stream onto the requested type.

Method throws an ProcessingException if the content of the message cannot be mapped to an entity of the requested type and IllegalStateException in case the entity is not backed by an input stream or if the original entity input stream has already been consumed without buffering the entity data prior consuming.

A message instance returned from this method will be cached for subsequent retrievals via getEntity(). Unless the supplied entity type is an input stream, this method automatically closes the an unconsumed original response entity data stream if open. In case the entity data has been buffered, the buffer will be reset prior consuming the buffered data to enable subsequent invocations of readEntity(...) methods on this response.

Params:
  • entityType – the type of entity.
Type parameters:
  • <T> – entity instance Java type.
Throws:
  • ProcessingException – if the content of the message cannot be mapped to an entity of the requested type.
  • IllegalStateException – if the entity is not backed by an input stream, the response has been closed already, or if the entity input stream has been fully consumed already and has not been buffered prior consuming.
See Also:
Returns:the message entity; for a zero-length response entities returns a corresponding Java object that represents zero-length data. In case no zero-length representation is defined for the Java type, a ProcessingException wrapping the underlying NoContentException is thrown.
Since:2.5
/** * Read the message entity input stream as an instance of specified Java type * using a {@link javax.ws.rs.ext.MessageBodyReader} that supports mapping the * message entity stream onto the requested type. * <p> * Method throws an {@link ProcessingException} if the content of the * message cannot be mapped to an entity of the requested type and * {@link IllegalStateException} in case the entity is not backed by an input * stream or if the original entity input stream has already been consumed * without {@link #bufferEntity() buffering} the entity data prior consuming. * </p> * <p> * A message instance returned from this method will be cached for * subsequent retrievals via {@link #getEntity()}. Unless the supplied entity * type is an {@link java.io.InputStream input stream}, this method automatically * {@link #close() closes} the an unconsumed original response entity data stream * if open. In case the entity data has been buffered, the buffer will be reset * prior consuming the buffered data to enable subsequent invocations of * {@code readEntity(...)} methods on this response. * </p> * * @param <T> entity instance Java type. * @param entityType the type of entity. * @return the message entity; for a zero-length response entities returns a corresponding * Java object that represents zero-length data. In case no zero-length representation * is defined for the Java type, a {@link ProcessingException} wrapping the * underlying {@link javax.ws.rs.core.NoContentException} is thrown. * @throws ProcessingException if the content of the message cannot be * mapped to an entity of the requested type. * @throws IllegalStateException if the entity is not backed by an input stream, * the response has been {@link #close() closed} already, * or if the entity input stream has been fully consumed already and has * not been buffered prior consuming. * @see javax.ws.rs.ext.MessageBodyReader * @see javax.ws.rs.core.Response#readEntity(Class) * @since 2.5 */
public <T> T readEntity(Class<T> entityType) throws ProcessingException, IllegalStateException { return readEntity(entityType, requestContext.getPropertiesDelegate()); }
Read the message entity input stream as an instance of specified Java type using a MessageBodyReader that supports mapping the message entity stream onto the requested type.

Method throws an ProcessingException if the content of the message cannot be mapped to an entity of the requested type and IllegalStateException in case the entity is not backed by an input stream or if the original entity input stream has already been consumed without buffering the entity data prior consuming.

A message instance returned from this method will be cached for subsequent retrievals via getEntity(). Unless the supplied entity type is an input stream, this method automatically closes the an unconsumed original response entity data stream if open. In case the entity data has been buffered, the buffer will be reset prior consuming the buffered data to enable subsequent invocations of readEntity(...) methods on this response.

Params:
  • entityType – the type of entity; may be generic.
Type parameters:
  • <T> – entity instance Java type.
Throws:
  • ProcessingException – if the content of the message cannot be mapped to an entity of the requested type.
  • IllegalStateException – if the entity is not backed by an input stream, the response has been closed already, or if the entity input stream has been fully consumed already and has not been buffered prior consuming.
See Also:
Returns:the message entity; for a zero-length response entities returns a corresponding Java object that represents zero-length data. In case no zero-length representation is defined for the Java type, a ProcessingException wrapping the underlying NoContentException is thrown.
Since:2.5
/** * Read the message entity input stream as an instance of specified Java type * using a {@link javax.ws.rs.ext.MessageBodyReader} that supports mapping the * message entity stream onto the requested type. * <p> * Method throws an {@link ProcessingException} if the content of the * message cannot be mapped to an entity of the requested type and * {@link IllegalStateException} in case the entity is not backed by an input * stream or if the original entity input stream has already been consumed * without {@link #bufferEntity() buffering} the entity data prior consuming. * </p> * <p> * A message instance returned from this method will be cached for * subsequent retrievals via {@link #getEntity()}. Unless the supplied entity * type is an {@link java.io.InputStream input stream}, this method automatically * {@link #close() closes} the an unconsumed original response entity data stream * if open. In case the entity data has been buffered, the buffer will be reset * prior consuming the buffered data to enable subsequent invocations of * {@code readEntity(...)} methods on this response. * </p> * * @param <T> entity instance Java type. * @param entityType the type of entity; may be generic. * @return the message entity; for a zero-length response entities returns a corresponding * Java object that represents zero-length data. In case no zero-length representation * is defined for the Java type, a {@link ProcessingException} wrapping the * underlying {@link javax.ws.rs.core.NoContentException} is thrown. * @throws ProcessingException if the content of the message cannot be * mapped to an entity of the requested type. * @throws IllegalStateException if the entity is not backed by an input stream, * the response has been {@link #close() closed} already, * or if the entity input stream has been fully consumed already and has * not been buffered prior consuming. * @see javax.ws.rs.ext.MessageBodyReader * @see javax.ws.rs.core.Response#readEntity(javax.ws.rs.core.GenericType) * @since 2.5 */
@SuppressWarnings("unchecked") public <T> T readEntity(GenericType<T> entityType) throws ProcessingException, IllegalStateException { return (T) readEntity(entityType.getRawType(), entityType.getType(), requestContext.getPropertiesDelegate()); }
Read the message entity input stream as an instance of specified Java type using a MessageBodyReader that supports mapping the message entity stream onto the requested type.

Method throws an ProcessingException if the content of the message cannot be mapped to an entity of the requested type and IllegalStateException in case the entity is not backed by an input stream or if the original entity input stream has already been consumed without buffering the entity data prior consuming.

A message instance returned from this method will be cached for subsequent retrievals via getEntity(). Unless the supplied entity type is an input stream, this method automatically closes the an unconsumed original response entity data stream if open. In case the entity data has been buffered, the buffer will be reset prior consuming the buffered data to enable subsequent invocations of readEntity(...) methods on this response.

Params:
  • entityType – the type of entity.
  • annotations – annotations that will be passed to the MessageBodyReader.
Type parameters:
  • <T> – entity instance Java type.
Throws:
  • ProcessingException – if the content of the message cannot be mapped to an entity of the requested type.
  • IllegalStateException – if the entity is not backed by an input stream, the response has been closed already, or if the entity input stream has been fully consumed already and has not been buffered prior consuming.
See Also:
Returns:the message entity; for a zero-length response entities returns a corresponding Java object that represents zero-length data. In case no zero-length representation is defined for the Java type, a ProcessingException wrapping the underlying NoContentException is thrown.
Since:2.5
/** * Read the message entity input stream as an instance of specified Java type * using a {@link javax.ws.rs.ext.MessageBodyReader} that supports mapping the * message entity stream onto the requested type. * <p> * Method throws an {@link ProcessingException} if the content of the * message cannot be mapped to an entity of the requested type and * {@link IllegalStateException} in case the entity is not backed by an input * stream or if the original entity input stream has already been consumed * without {@link #bufferEntity() buffering} the entity data prior consuming. * </p> * <p> * A message instance returned from this method will be cached for * subsequent retrievals via {@link #getEntity()}. Unless the supplied entity * type is an {@link java.io.InputStream input stream}, this method automatically * {@link #close() closes} the an unconsumed original response entity data stream * if open. In case the entity data has been buffered, the buffer will be reset * prior consuming the buffered data to enable subsequent invocations of * {@code readEntity(...)} methods on this response. * </p> * * @param <T> entity instance Java type. * @param entityType the type of entity. * @param annotations annotations that will be passed to the {@link javax.ws.rs.ext.MessageBodyReader}. * @return the message entity; for a zero-length response entities returns a corresponding * Java object that represents zero-length data. In case no zero-length representation * is defined for the Java type, a {@link ProcessingException} wrapping the * underlying {@link javax.ws.rs.core.NoContentException} is thrown. * @throws ProcessingException if the content of the message cannot be * mapped to an entity of the requested type. * @throws IllegalStateException if the entity is not backed by an input stream, * the response has been {@link #close() closed} already, * or if the entity input stream has been fully consumed already and has * not been buffered prior consuming. * @see javax.ws.rs.ext.MessageBodyReader * @see javax.ws.rs.core.Response#readEntity(Class, java.lang.annotation.Annotation[]) * @since 2.5 */
public <T> T readEntity(Class<T> entityType, Annotation[] annotations) throws ProcessingException, IllegalStateException { return readEntity(entityType, annotations, requestContext.getPropertiesDelegate()); }
Read the message entity input stream as an instance of specified Java type using a MessageBodyReader that supports mapping the message entity stream onto the requested type.

Method throws an ProcessingException if the content of the message cannot be mapped to an entity of the requested type and IllegalStateException in case the entity is not backed by an input stream or if the original entity input stream has already been consumed without buffering the entity data prior consuming.

A message instance returned from this method will be cached for subsequent retrievals via getEntity(). Unless the supplied entity type is an input stream, this method automatically closes the an unconsumed original response entity data stream if open. In case the entity data has been buffered, the buffer will be reset prior consuming the buffered data to enable subsequent invocations of readEntity(...) methods on this response.

Params:
  • entityType – the type of entity; may be generic.
  • annotations – annotations that will be passed to the MessageBodyReader.
Type parameters:
  • <T> – entity instance Java type.
Throws:
  • ProcessingException – if the content of the message cannot be mapped to an entity of the requested type.
  • IllegalStateException – if the entity is not backed by an input stream, the response has been closed already, or if the entity input stream has been fully consumed already and has not been buffered prior consuming.
See Also:
Returns:the message entity; for a zero-length response entities returns a corresponding Java object that represents zero-length data. In case no zero-length representation is defined for the Java type, a ProcessingException wrapping the underlying NoContentException is thrown.
Since:2.5
/** * Read the message entity input stream as an instance of specified Java type * using a {@link javax.ws.rs.ext.MessageBodyReader} that supports mapping the * message entity stream onto the requested type. * <p> * Method throws an {@link ProcessingException} if the content of the * message cannot be mapped to an entity of the requested type and * {@link IllegalStateException} in case the entity is not backed by an input * stream or if the original entity input stream has already been consumed * without {@link #bufferEntity() buffering} the entity data prior consuming. * </p> * <p> * A message instance returned from this method will be cached for * subsequent retrievals via {@link #getEntity()}. Unless the supplied entity * type is an {@link java.io.InputStream input stream}, this method automatically * {@link #close() closes} the an unconsumed original response entity data stream * if open. In case the entity data has been buffered, the buffer will be reset * prior consuming the buffered data to enable subsequent invocations of * {@code readEntity(...)} methods on this response. * </p> * * @param <T> entity instance Java type. * @param entityType the type of entity; may be generic. * @param annotations annotations that will be passed to the {@link javax.ws.rs.ext.MessageBodyReader}. * @return the message entity; for a zero-length response entities returns a corresponding * Java object that represents zero-length data. In case no zero-length representation * is defined for the Java type, a {@link ProcessingException} wrapping the * underlying {@link javax.ws.rs.core.NoContentException} is thrown. * @throws ProcessingException if the content of the message cannot be * mapped to an entity of the requested type. * @throws IllegalStateException if the entity is not backed by an input stream, * the response has been {@link #close() closed} already, * or if the entity input stream has been fully consumed already and has * not been buffered prior consuming. * @see javax.ws.rs.ext.MessageBodyReader * @see javax.ws.rs.core.Response#readEntity(javax.ws.rs.core.GenericType, java.lang.annotation.Annotation[]) * @since 2.5 */
@SuppressWarnings("unchecked") public <T> T readEntity(GenericType<T> entityType, Annotation[] annotations) throws ProcessingException, IllegalStateException { return (T) readEntity(entityType.getRawType(), entityType.getType(), annotations, requestContext.getPropertiesDelegate()); } @Override public InjectionManager getInjectionManager() { return getRequestContext().getInjectionManager(); } @Override protected Iterable<ReaderInterceptor> getReaderInterceptors() { return requestContext.getReaderInterceptors(); } }