/*
 * 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.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Variant;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;

import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.PropertiesDelegate;
import org.glassfish.jersey.internal.guava.Preconditions;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.InjectionManagerSupplier;
import org.glassfish.jersey.internal.util.ExceptionUtils;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.message.MessageBodyWorkers;
import org.glassfish.jersey.message.internal.HeaderUtils;
import org.glassfish.jersey.message.internal.OutboundMessageContext;

Jersey client request context.
Author:Marek Potociar
/** * Jersey client request context. * * @author Marek Potociar */
public class ClientRequest extends OutboundMessageContext implements ClientRequestContext, HttpHeaders, InjectionManagerSupplier { // Request-scoped configuration instance private final ClientConfig clientConfig; // Request-scoped properties delegate private final PropertiesDelegate propertiesDelegate; // Absolute request URI private URI requestUri; // Request method private String httpMethod; // Request filter chain execution aborting response private Response abortResponse; // Entity providers private MessageBodyWorkers workers; // Flag indicating whether the request is asynchronous private boolean asynchronous; // true if writeEntity() was already called private boolean entityWritten; // writer interceptors used to write the request private Iterable<WriterInterceptor> writerInterceptors; // reader interceptors used to write the request private Iterable<ReaderInterceptor> readerInterceptors; // do not add user-agent header (if not directly set) to the request. private boolean ignoreUserAgent; private static final Logger LOGGER = Logger.getLogger(ClientRequest.class.getName());
Create new Jersey client request context.
Params:
  • requestUri – request Uri.
  • clientConfig – request configuration.
  • propertiesDelegate – properties delegate.
/** * Create new Jersey client request context. * * @param requestUri request Uri. * @param clientConfig request configuration. * @param propertiesDelegate properties delegate. */
protected ClientRequest( final URI requestUri, final ClientConfig clientConfig, final PropertiesDelegate propertiesDelegate) { super(clientConfig.getConfiguration()); clientConfig.checkClient(); this.requestUri = requestUri; this.clientConfig = clientConfig; this.propertiesDelegate = propertiesDelegate; }
Copy constructor.
Params:
  • original – original instance.
/** * Copy constructor. * * @param original original instance. */
public ClientRequest(final ClientRequest original) { super(original); this.requestUri = original.requestUri; this.httpMethod = original.httpMethod; this.workers = original.workers; this.clientConfig = original.clientConfig.snapshot(); this.asynchronous = original.isAsynchronous(); this.readerInterceptors = original.readerInterceptors; this.writerInterceptors = original.writerInterceptors; this.propertiesDelegate = new MapPropertiesDelegate(original.propertiesDelegate); this.ignoreUserAgent = original.ignoreUserAgent; }
Resolve a property value for the specified property name.

The method returns the value of the property registered in the request-specific property bag, if available. If no property for the given property name is found in the request-specific property bag, the method looks at the properties stored in the global client-runtime configuration this request belongs to. If there is a value defined in the client-runtime configuration, it is returned, otherwise the method returns null if no such property is registered neither in the client runtime nor in the request-specific property bag.

Params:
  • name – property name.
  • type – expected property class type.
Type parameters:
  • <T> – property Java type.
Returns:resolved property value or null if no such property is registered.
/** * Resolve a property value for the specified property {@code name}. * * <p> * The method returns the value of the property registered in the request-specific * property bag, if available. If no property for the given property name is found * in the request-specific property bag, the method looks at the properties stored * in the {@link #getConfiguration() global client-runtime configuration} this request * belongs to. If there is a value defined in the client-runtime configuration, * it is returned, otherwise the method returns {@code null} if no such property is * registered neither in the client runtime nor in the request-specific property bag. * </p> * * @param name property name. * @param type expected property class type. * @param <T> property Java type. * @return resolved property value or {@code null} if no such property is registered. */
public <T> T resolveProperty(final String name, final Class<T> type) { return resolveProperty(name, null, type); }
Resolve a property value for the specified property name.

The method returns the value of the property registered in the request-specific property bag, if available. If no property for the given property name is found in the request-specific property bag, the method looks at the properties stored in the global client-runtime configuration this request belongs to. If there is a value defined in the client-runtime configuration, it is returned, otherwise the method returns defaultValue if no such property is registered neither in the client runtime nor in the request-specific property bag.

Params:
  • name – property name.
  • defaultValue – default value to return if the property is not registered.
Type parameters:
  • <T> – property Java type.
Returns:resolved property value or defaultValue if no such property is registered.
/** * Resolve a property value for the specified property {@code name}. * * <p> * The method returns the value of the property registered in the request-specific * property bag, if available. If no property for the given property name is found * in the request-specific property bag, the method looks at the properties stored * in the {@link #getConfiguration() global client-runtime configuration} this request * belongs to. If there is a value defined in the client-runtime configuration, * it is returned, otherwise the method returns {@code defaultValue} if no such property is * registered neither in the client runtime nor in the request-specific property bag. * </p> * * @param name property name. * @param defaultValue default value to return if the property is not registered. * @param <T> property Java type. * @return resolved property value or {@code defaultValue} if no such property is registered. */
@SuppressWarnings("unchecked") public <T> T resolveProperty(final String name, final T defaultValue) { return resolveProperty(name, defaultValue, (Class<T>) defaultValue.getClass()); } private <T> T resolveProperty(final String name, Object defaultValue, final Class<T> type) { // Check runtime configuration first Object result = clientConfig.getProperty(name); if (result != null) { defaultValue = result; } // Check request properties next result = propertiesDelegate.getProperty(name); if (result == null) { result = defaultValue; } return (result == null) ? null : PropertiesHelper.convertValue(result, type); } @Override public Object getProperty(final String name) { return propertiesDelegate.getProperty(name); } @Override public Collection<String> getPropertyNames() { return propertiesDelegate.getPropertyNames(); } @Override public void setProperty(final String name, final Object object) { propertiesDelegate.setProperty(name, object); } @Override public void removeProperty(final String name) { propertiesDelegate.removeProperty(name); }
Get the underlying properties delegate.
Returns:underlying properties delegate.
/** * Get the underlying properties delegate. * * @return underlying properties delegate. */
PropertiesDelegate getPropertiesDelegate() { return propertiesDelegate; }
Get the underlying client runtime.
Returns:underlying client runtime.
/** * Get the underlying client runtime. * * @return underlying client runtime. */
ClientRuntime getClientRuntime() { return clientConfig.getRuntime(); } @Override public URI getUri() { return requestUri; } @Override public void setUri(final URI uri) { this.requestUri = uri; } @Override public String getMethod() { return httpMethod; } @Override public void setMethod(final String method) { this.httpMethod = method; } @Override public JerseyClient getClient() { return clientConfig.getClient(); } @Override public void abortWith(final Response response) { this.abortResponse = response; }
Get the request filter chain aborting response if set, or null otherwise.
Returns:request filter chain aborting response if set, or null otherwise.
/** * Get the request filter chain aborting response if set, or {@code null} otherwise. * * @return request filter chain aborting response if set, or {@code null} otherwise. */
public Response getAbortResponse() { return abortResponse; } @Override public Configuration getConfiguration() { return clientConfig.getRuntime().getConfig(); }
Get internal client configuration state.
Returns:internal client configuration state.
/** * Get internal client configuration state. * * @return internal client configuration state. */
ClientConfig getClientConfig() { return clientConfig; } @Override public List<String> getRequestHeader(String name) { return HeaderUtils.asStringList(getHeaders().get(name), clientConfig.getConfiguration()); } @Override public MultivaluedMap<String, String> getRequestHeaders() { return HeaderUtils.asStringHeaders(getHeaders(), clientConfig.getConfiguration()); } @Override public Map<String, Cookie> getCookies() { return super.getRequestCookies(); }
Get the message body workers associated with the request.
Returns:message body workers.
/** * Get the message body workers associated with the request. * * @return message body workers. */
public MessageBodyWorkers getWorkers() { return workers; }
Set the message body workers associated with the request.
Params:
  • workers – message body workers.
/** * Set the message body workers associated with the request. * * @param workers message body workers. */
public void setWorkers(final MessageBodyWorkers workers) { this.workers = workers; }
Add new accepted types to the message headers.
Params:
  • types – accepted types to be added.
/** * Add new accepted types to the message headers. * * @param types accepted types to be added. */
public void accept(final MediaType... types) { getHeaders().addAll(HttpHeaders.ACCEPT, (Object[]) types); }
Add new accepted types to the message headers.
Params:
  • types – accepted types to be added.
/** * Add new accepted types to the message headers. * * @param types accepted types to be added. */
public void accept(final String... types) { getHeaders().addAll(HttpHeaders.ACCEPT, (Object[]) types); }
Add new accepted languages to the message headers.
Params:
  • locales – accepted languages to be added.
/** * Add new accepted languages to the message headers. * * @param locales accepted languages to be added. */
public void acceptLanguage(final Locale... locales) { getHeaders().addAll(HttpHeaders.ACCEPT_LANGUAGE, (Object[]) locales); }
Add new accepted languages to the message headers.
Params:
  • locales – accepted languages to be added.
/** * Add new accepted languages to the message headers. * * @param locales accepted languages to be added. */
public void acceptLanguage(final String... locales) { getHeaders().addAll(HttpHeaders.ACCEPT_LANGUAGE, (Object[]) locales); }
Add new cookie to the message headers.
Params:
  • cookie – cookie to be added.
/** * Add new cookie to the message headers. * * @param cookie cookie to be added. */
public void cookie(final Cookie cookie) { getHeaders().add(HttpHeaders.COOKIE, cookie); }
Add new cache control entry to the message headers.
Params:
  • cacheControl – cache control entry to be added.
/** * Add new cache control entry to the message headers. * * @param cacheControl cache control entry to be added. */
public void cacheControl(final CacheControl cacheControl) { getHeaders().add(HttpHeaders.CACHE_CONTROL, cacheControl); }
Set message encoding.
Params:
  • encoding – message encoding to be set.
/** * Set message encoding. * * @param encoding message encoding to be set. */
public void encoding(final String encoding) { if (encoding == null) { getHeaders().remove(HttpHeaders.CONTENT_ENCODING); } else { getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, encoding); } }
Set message language.
Params:
  • language – message language to be set.
/** * Set message language. * * @param language message language to be set. */
public void language(final String language) { if (language == null) { getHeaders().remove(HttpHeaders.CONTENT_LANGUAGE); } else { getHeaders().putSingle(HttpHeaders.CONTENT_LANGUAGE, language); } }
Set message language.
Params:
  • language – message language to be set.
/** * Set message language. * * @param language message language to be set. */
public void language(final Locale language) { if (language == null) { getHeaders().remove(HttpHeaders.CONTENT_LANGUAGE); } else { getHeaders().putSingle(HttpHeaders.CONTENT_LANGUAGE, language); } }
Set message content type.
Params:
  • type – message content type to be set.
/** * Set message content type. * * @param type message content type to be set. */
public void type(final MediaType type) { setMediaType(type); }
Set message content type.
Params:
  • type – message content type to be set.
/** * Set message content type. * * @param type message content type to be set. */
public void type(final String type) { type(type == null ? null : MediaType.valueOf(type)); }
Set message content variant (type, language and encoding).
Params:
  • variant – message content content variant (type, language and encoding) to be set.
/** * Set message content variant (type, language and encoding). * * @param variant message content content variant (type, language and encoding) * to be set. */
public void variant(final Variant variant) { if (variant == null) { type((MediaType) null); language((String) null); encoding(null); } else { type(variant.getMediaType()); language(variant.getLanguage()); encoding(variant.getEncoding()); } }
Returns true if the request is called asynchronously using AsyncInvoker
Returns:True if the request is asynchronous; false otherwise.
/** * Returns true if the request is called asynchronously using {@link javax.ws.rs.client.AsyncInvoker} * * @return True if the request is asynchronous; false otherwise. */
public boolean isAsynchronous() { return asynchronous; }
Sets the flag indicating whether the request is called asynchronously using AsyncInvoker.
Params:
  • async – True if the request is asynchronous; false otherwise.
/** * Sets the flag indicating whether the request is called asynchronously using {@link javax.ws.rs.client.AsyncInvoker}. * * @param async True if the request is asynchronous; false otherwise. */
void setAsynchronous(final boolean async) { asynchronous = async; }
Enable a buffering of serialized entity. The buffering will be configured from runtime configuration associated with this request. The property determining the size of the buffer is CommonProperties.OUTBOUND_CONTENT_LENGTH_BUFFER.

The buffering functionality is by default disabled and could be enabled by calling this method. In this case this method must be called before first bytes are written to the entity stream.
/** * Enable a buffering of serialized entity. The buffering will be configured from runtime configuration * associated with this request. The property determining the size of the buffer * is {@link org.glassfish.jersey.CommonProperties#OUTBOUND_CONTENT_LENGTH_BUFFER}. * <p/> * The buffering functionality is by default disabled and could be enabled by calling this method. In this case * this method must be called before first bytes are written to the {@link #getEntityStream() entity stream}. * */
public void enableBuffering() { enableBuffering(getConfiguration()); }
Write (serialize) the entity set in this request into the entity stream. The method use writer interceptors and message body writer.

This method modifies the state of this request and therefore it can be called only once per request life cycle otherwise IllegalStateException is thrown.

Note that OutboundMessageContext.setStreamProvider(StreamProvider) and optionally enableBuffering() must be called before calling this method.
Throws:
/** * Write (serialize) the entity set in this request into the {@link #getEntityStream() entity stream}. The method * use {@link javax.ws.rs.ext.WriterInterceptor writer interceptors} and {@link javax.ws.rs.ext.MessageBodyWriter * message body writer}. * <p/> * This method modifies the state of this request and therefore it can be called only once per request life cycle otherwise * IllegalStateException is thrown. * <p/> * Note that {@link #setStreamProvider(org.glassfish.jersey.message.internal.OutboundMessageContext.StreamProvider)} * and optionally {@link #enableBuffering()} must be called before calling this method. * * @throws IOException In the case of IO error. */
public void writeEntity() throws IOException { Preconditions.checkState(!entityWritten, LocalizationMessages.REQUEST_ENTITY_ALREADY_WRITTEN()); entityWritten = true; ensureMediaType(); final GenericType<?> entityType = new GenericType(getEntityType()); doWriteEntity(workers, entityType); }
Added only to make the code testable.
Params:
  • writeWorkers – Message body workers instance used to write the entity.
  • entityType – entity type.
Throws:
/** * Added only to make the code testable. * * @param writeWorkers Message body workers instance used to write the entity. * @param entityType entity type. * @throws IOException when {@link MessageBodyWorkers#writeTo(Object, Class, Type, Annotation[], MediaType, * MultivaluedMap, PropertiesDelegate, OutputStream, Iterable)} throws an {@link IOException}. * This state is always regarded as connection failure. */
/* package */ void doWriteEntity(final MessageBodyWorkers writeWorkers, final GenericType<?> entityType) throws IOException { OutputStream entityStream = null; boolean connectionFailed = false; boolean runtimeException = false; try { try { entityStream = writeWorkers.writeTo( getEntity(), entityType.getRawType(), entityType.getType(), getEntityAnnotations(), getMediaType(), getHeaders(), getPropertiesDelegate(), getEntityStream(), writerInterceptors); setEntityStream(entityStream); } catch (final IOException e) { // JERSEY-2728 - treat SSLException as connection failure connectionFailed = true; throw e; } catch (final RuntimeException e) { runtimeException = true; throw e; } } finally { // in case we've seen the ConnectException, we won't try to close/commit stream as this would produce just // another instance of ConnectException (which would be logged even if the previously thrown one is propagated) // However, if another failure occurred, we still have to try to close and commit the stream - and if we experience // another failure, there is a valid reason to log it if (!connectionFailed) { if (entityStream != null) { try { entityStream.close(); } catch (final IOException e) { ExceptionUtils.conditionallyReThrow(e, !runtimeException, LOGGER, LocalizationMessages.ERROR_CLOSING_OUTPUT_STREAM(), Level.FINE); } catch (final RuntimeException e) { ExceptionUtils.conditionallyReThrow(e, !runtimeException, LOGGER, LocalizationMessages.ERROR_CLOSING_OUTPUT_STREAM(), Level.FINE); } } try { commitStream(); } catch (final IOException e) { ExceptionUtils.conditionallyReThrow(e, !runtimeException, LOGGER, LocalizationMessages.ERROR_COMMITTING_OUTPUT_STREAM(), Level.FINE); } catch (final RuntimeException e) { ExceptionUtils.conditionallyReThrow(e, !runtimeException, LOGGER, LocalizationMessages.ERROR_COMMITTING_OUTPUT_STREAM(), Level.FINE); } } } } private void ensureMediaType() { if (getMediaType() == null) { // Content-Type is not present choose a default type final GenericType<?> entityType = new GenericType(getEntityType()); final List<MediaType> mediaTypes = workers.getMessageBodyWriterMediaTypes( entityType.getRawType(), entityType.getType(), getEntityAnnotations()); setMediaType(getMediaType(mediaTypes)); } } private MediaType getMediaType(final List<MediaType> mediaTypes) { if (mediaTypes.isEmpty()) { return MediaType.APPLICATION_OCTET_STREAM_TYPE; } else { MediaType mediaType = mediaTypes.get(0); if (mediaType.isWildcardType() || mediaType.isWildcardSubtype()) { mediaType = MediaType.APPLICATION_OCTET_STREAM_TYPE; } return mediaType; } }
Set writer interceptors for this request.
Params:
  • writerInterceptors – Writer interceptors in the interceptor execution order.
/** * Set writer interceptors for this request. * @param writerInterceptors Writer interceptors in the interceptor execution order. */
void setWriterInterceptors(final Iterable<WriterInterceptor> writerInterceptors) { this.writerInterceptors = writerInterceptors; }
Get writer interceptors of this request.
Returns:Writer interceptors in the interceptor execution order.
/** * Get writer interceptors of this request. * @return Writer interceptors in the interceptor execution order. */
public Iterable<WriterInterceptor> getWriterInterceptors() { return writerInterceptors; }
Get reader interceptors of this request.
Returns:Reader interceptors in the interceptor execution order.
/** * Get reader interceptors of this request. * @return Reader interceptors in the interceptor execution order. */
public Iterable<ReaderInterceptor> getReaderInterceptors() { return readerInterceptors; }
Set reader interceptors for this request.
Params:
  • readerInterceptors – Reader interceptors in the interceptor execution order.
/** * Set reader interceptors for this request. * @param readerInterceptors Reader interceptors in the interceptor execution order. */
void setReaderInterceptors(final Iterable<ReaderInterceptor> readerInterceptors) { this.readerInterceptors = readerInterceptors; } @Override public InjectionManager getInjectionManager() { return getClientRuntime().getInjectionManager(); }
Indicates whether the User-Agent header should be omitted if not directly set to the map of headers.
Returns:true if the header should be omitted, false otherwise.
/** * Indicates whether the User-Agent header should be omitted if not directly set to the map of headers. * * @return {@code true} if the header should be omitted, {@code false} otherwise. */
public boolean ignoreUserAgent() { return ignoreUserAgent; }
Indicates whether the User-Agent header should be omitted if not directly set to the map of headers.
Params:
  • ignore – true if the header should be omitted, false otherwise.
/** * Indicates whether the User-Agent header should be omitted if not directly set to the map of headers. * * @param ignore {@code true} if the header should be omitted, {@code false} otherwise. */
public void ignoreUserAgent(final boolean ignore) { this.ignoreUserAgent = ignore; } }