/*
 * Copyright (c) 2011, 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.lang.reflect.Type;
import java.net.URI;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotAcceptableException;
import javax.ws.rs.NotAllowedException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.NotSupportedException;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.RedirectionException;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.ServiceUnavailableException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.client.CompletionStageRxInvoker;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.InvocationCallback;
import javax.ws.rs.client.ResponseProcessingException;
import javax.ws.rs.client.RxInvoker;
import javax.ws.rs.client.RxInvokerProvider;
import javax.ws.rs.client.SyncInvoker;
import javax.ws.rs.core.CacheControl;
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 org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.Producer;
import org.glassfish.jersey.internal.util.PropertiesHelper;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.spi.ExecutorServiceProvider;

Jersey implementation of JAX-RS client-side request invocation contract.
Author:Marek Potociar
/** * Jersey implementation of {@link javax.ws.rs.client.Invocation JAX-RS client-side * request invocation} contract. * * @author Marek Potociar */
public class JerseyInvocation implements javax.ws.rs.client.Invocation { private static final Logger LOGGER = Logger.getLogger(JerseyInvocation.class.getName()); private final ClientRequest requestContext; // Copy request context when invoke or submit methods are invoked. private final boolean copyRequestContext; private JerseyInvocation(final Builder builder) { this(builder, false); } private JerseyInvocation(final Builder builder, final boolean copyRequestContext) { validateHttpMethodAndEntity(builder.requestContext); this.requestContext = new ClientRequest(builder.requestContext); this.copyRequestContext = copyRequestContext; } private enum EntityPresence { MUST_BE_NULL, MUST_BE_PRESENT, OPTIONAL } private static final Map<String, EntityPresence> METHODS = initializeMap(); private static Map<String, EntityPresence> initializeMap() { final Map<String, EntityPresence> map = new HashMap<>(); map.put("DELETE", EntityPresence.MUST_BE_NULL); map.put("GET", EntityPresence.MUST_BE_NULL); map.put("HEAD", EntityPresence.MUST_BE_NULL); map.put("OPTIONS", EntityPresence.MUST_BE_NULL); map.put("PATCH", EntityPresence.MUST_BE_PRESENT); map.put("POST", EntityPresence.OPTIONAL); // we allow to post null instead of entity map.put("PUT", EntityPresence.MUST_BE_PRESENT); map.put("TRACE", EntityPresence.MUST_BE_NULL); return map; } private void validateHttpMethodAndEntity(final ClientRequest request) { boolean suppressExceptions; suppressExceptions = PropertiesHelper.isProperty( request.getConfiguration().getProperty(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION)); final Object shcvProperty = request.getProperty(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION); if (shcvProperty != null) { // override global configuration with request-specific suppressExceptions = PropertiesHelper.isProperty(shcvProperty); } final String method = request.getMethod(); final EntityPresence entityPresence = METHODS.get(method.toUpperCase(Locale.ROOT)); if (entityPresence == EntityPresence.MUST_BE_NULL && request.hasEntity()) { if (suppressExceptions) { LOGGER.warning(LocalizationMessages.ERROR_HTTP_METHOD_ENTITY_NOT_NULL(method)); } else { throw new IllegalStateException(LocalizationMessages.ERROR_HTTP_METHOD_ENTITY_NOT_NULL(method)); } } else if (entityPresence == EntityPresence.MUST_BE_PRESENT && !request.hasEntity()) { if (suppressExceptions) { LOGGER.warning(LocalizationMessages.ERROR_HTTP_METHOD_ENTITY_NULL(method)); } else { throw new IllegalStateException(LocalizationMessages.ERROR_HTTP_METHOD_ENTITY_NULL(method)); } } }
Jersey-specific client invocation builder.
/** * Jersey-specific {@link javax.ws.rs.client.Invocation.Builder client invocation builder}. */
public static class Builder implements javax.ws.rs.client.Invocation.Builder { private final ClientRequest requestContext;
Create new Jersey-specific client invocation builder.
Params:
  • uri – invoked request URI.
  • configuration – Jersey client configuration.
/** * Create new Jersey-specific client invocation builder. * * @param uri invoked request URI. * @param configuration Jersey client configuration. */
protected Builder(final URI uri, final ClientConfig configuration) { this.requestContext = new ClientRequest(uri, configuration, new MapPropertiesDelegate()); }
Returns a reference to the mutable request context to be invoked.
Returns:mutable request context to be invoked.
/** * Returns a reference to the mutable request context to be invoked. * * @return mutable request context to be invoked. */
ClientRequest request() { return requestContext; } private void storeEntity(final Entity<?> entity) { if (entity != null) { requestContext.variant(entity.getVariant()); requestContext.setEntity(entity.getEntity()); requestContext.setEntityAnnotations(entity.getAnnotations()); } } @Override public JerseyInvocation build(final String method) { requestContext.setMethod(method); return new JerseyInvocation(this, true); } @Override public JerseyInvocation build(final String method, final Entity<?> entity) { requestContext.setMethod(method); storeEntity(entity); return new JerseyInvocation(this, true); } @Override public JerseyInvocation buildGet() { requestContext.setMethod("GET"); return new JerseyInvocation(this, true); } @Override public JerseyInvocation buildDelete() { requestContext.setMethod("DELETE"); return new JerseyInvocation(this, true); } @Override public JerseyInvocation buildPost(final Entity<?> entity) { requestContext.setMethod("POST"); storeEntity(entity); return new JerseyInvocation(this, true); } @Override public JerseyInvocation buildPut(final Entity<?> entity) { requestContext.setMethod("PUT"); storeEntity(entity); return new JerseyInvocation(this, true); } @Override public javax.ws.rs.client.AsyncInvoker async() { return new AsyncInvoker(this); } @Override public Builder accept(final String... mediaTypes) { requestContext.accept(mediaTypes); return this; } @Override public Builder accept(final MediaType... mediaTypes) { requestContext.accept(mediaTypes); return this; } @Override public Invocation.Builder acceptEncoding(final String... encodings) { requestContext.getHeaders().addAll(HttpHeaders.ACCEPT_ENCODING, (Object[]) encodings); return this; } @Override public Builder acceptLanguage(final Locale... locales) { requestContext.acceptLanguage(locales); return this; } @Override public Builder acceptLanguage(final String... locales) { requestContext.acceptLanguage(locales); return this; } @Override public Builder cookie(final Cookie cookie) { requestContext.cookie(cookie); return this; } @Override public Builder cookie(final String name, final String value) { requestContext.cookie(new Cookie(name, value)); return this; } @Override public Builder cacheControl(final CacheControl cacheControl) { requestContext.cacheControl(cacheControl); return this; } @Override public Builder header(final String name, final Object value) { final MultivaluedMap<String, Object> headers = requestContext.getHeaders(); if (value == null) { headers.remove(name); } else { headers.add(name, value); } if (HttpHeaders.USER_AGENT.equalsIgnoreCase(name)) { requestContext.ignoreUserAgent(value == null); } return this; } @Override public Builder headers(final MultivaluedMap<String, Object> headers) { requestContext.replaceHeaders(headers); return this; } @Override public Response get() throws ProcessingException { return method("GET"); } @Override public <T> T get(final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("GET", responseType); } @Override public <T> T get(final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("GET", responseType); } @Override public Response put(final Entity<?> entity) throws ProcessingException { return method("PUT", entity); } @Override public <T> T put(final Entity<?> entity, final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("PUT", entity, responseType); } @Override public <T> T put(final Entity<?> entity, final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("PUT", entity, responseType); } @Override public Response post(final Entity<?> entity) throws ProcessingException { return method("POST", entity); } @Override public <T> T post(final Entity<?> entity, final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("POST", entity, responseType); } @Override public <T> T post(final Entity<?> entity, final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("POST", entity, responseType); } @Override public Response delete() throws ProcessingException { return method("DELETE"); } @Override public <T> T delete(final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("DELETE", responseType); } @Override public <T> T delete(final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("DELETE", responseType); } @Override public Response head() throws ProcessingException { return method("HEAD"); } @Override public Response options() throws ProcessingException { return method("OPTIONS"); } @Override public <T> T options(final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("OPTIONS", responseType); } @Override public <T> T options(final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("OPTIONS", responseType); } @Override public Response trace() throws ProcessingException { return method("TRACE"); } @Override public <T> T trace(final Class<T> responseType) throws ProcessingException, WebApplicationException { return method("TRACE", responseType); } @Override public <T> T trace(final GenericType<T> responseType) throws ProcessingException, WebApplicationException { return method("TRACE", responseType); } @Override public Response method(final String name) throws ProcessingException { requestContext.setMethod(name); return new JerseyInvocation(this).invoke(); } @Override public <T> T method(final String name, final Class<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } requestContext.setMethod(name); return new JerseyInvocation(this).invoke(responseType); } @Override public <T> T method(final String name, final GenericType<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } requestContext.setMethod(name); return new JerseyInvocation(this).invoke(responseType); } @Override public Response method(final String name, final Entity<?> entity) throws ProcessingException { requestContext.setMethod(name); storeEntity(entity); return new JerseyInvocation(this).invoke(); } @Override public <T> T method(final String name, final Entity<?> entity, final Class<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } requestContext.setMethod(name); storeEntity(entity); return new JerseyInvocation(this).invoke(responseType); } @Override public <T> T method(final String name, final Entity<?> entity, final GenericType<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } requestContext.setMethod(name); storeEntity(entity); return new JerseyInvocation(this).invoke(responseType); } @Override public Builder property(final String name, final Object value) { requestContext.setProperty(name, value); return this; } @Override public CompletionStageRxInvoker rx() { return new JerseyCompletionStageRxInvoker(this); } @Override public <T extends RxInvoker> T rx(Class<T> clazz) { if (clazz == JerseyCompletionStageRxInvoker.class) { return (T) new JerseyCompletionStageRxInvoker(this); } return createRxInvoker(clazz, executorService()); } private <T extends RxInvoker> T rx(Class<T> clazz, ExecutorService executorService) { if (executorService == null) { throw new IllegalArgumentException(LocalizationMessages.NULL_INPUT_PARAMETER("executorService")); } return createRxInvoker(clazz, executorService); } // get executor service from explicit configuration; if not available, get executor service from provider private ExecutorService executorService() { final ExecutorService result = request().getClientConfig().getExecutorService(); if (result != null) { return result; } return this.requestContext.getInjectionManager() .getInstance(ExecutorServiceProvider.class) .getExecutorService(); }
Create RxInvoker from provided RxInvoker subclass.

The method does a lookup for RxInvokerProvider, which provides given RxInvoker subclass and if found, calls RxInvokerProvider.getRxInvoker(SyncInvoker, ExecutorService)

Params:
  • clazz – RxInvoker subclass to be created.
  • executorService – to be passed to the factory method invocation.
Type parameters:
  • <T> – RxInvoker subclass to be returned.
Throws:
Returns:thread safe instance of RxInvoker subclass.
/** * Create {@link RxInvoker} from provided {@code RxInvoker} subclass. * <p> * The method does a lookup for {@link RxInvokerProvider}, which provides given {@code RxInvoker} subclass * and if found, calls {@link RxInvokerProvider#getRxInvoker(SyncInvoker, ExecutorService)} * * @param clazz {@code RxInvoker} subclass to be created. * @param executorService to be passed to the factory method invocation. * @param <T> {@code RxInvoker} subclass to be returned. * @return thread safe instance of {@code RxInvoker} subclass. * @throws IllegalStateException when provider for given class is not registered. */
private <T extends RxInvoker> T createRxInvoker(Class<? extends RxInvoker> clazz, ExecutorService executorService) { if (clazz == null) { throw new IllegalArgumentException(LocalizationMessages.NULL_INPUT_PARAMETER("clazz")); } Iterable<RxInvokerProvider> allProviders = Providers.getAllProviders( this.requestContext.getInjectionManager(), RxInvokerProvider.class); for (RxInvokerProvider invokerProvider : allProviders) { if (invokerProvider.isProviderFor(clazz)) { RxInvoker rxInvoker = invokerProvider.getRxInvoker(this, executorService); if (rxInvoker == null) { throw new IllegalStateException(LocalizationMessages.CLIENT_RX_PROVIDER_NULL()); } return (T) rxInvoker; } } throw new IllegalStateException( LocalizationMessages.CLIENT_RX_PROVIDER_NOT_REGISTERED(clazz.getSimpleName())); } } /* package */ static class AsyncInvoker extends CompletableFutureAsyncInvoker implements javax.ws.rs.client.AsyncInvoker { private final JerseyInvocation.Builder builder; /* package */ AsyncInvoker(final JerseyInvocation.Builder request) { this.builder = request; this.builder.requestContext.setAsynchronous(true); } @Override public CompletableFuture<Response> method(final String name) { builder.requestContext.setMethod(name); return (CompletableFuture<Response>) new JerseyInvocation(builder).submit(); } @Override public <T> CompletableFuture<T> method(final String name, final Class<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(responseType); } @Override public <T> CompletableFuture<T> method(final String name, final GenericType<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(responseType); } @Override public <T> CompletableFuture<T> method(final String name, final InvocationCallback<T> callback) { builder.requestContext.setMethod(name); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(callback); } @Override public CompletableFuture<Response> method(final String name, final Entity<?> entity) { builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture<Response>) new JerseyInvocation(builder).submit(); } @Override public <T> CompletableFuture<T> method(final String name, final Entity<?> entity, final Class<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(responseType); } @Override public <T> CompletableFuture<T> method(final String name, final Entity<?> entity, final GenericType<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(responseType); } @Override public <T> CompletableFuture<T> method(final String name, final Entity<?> entity, final InvocationCallback<T> callback) { builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture<T>) new JerseyInvocation(builder).submit(callback); } } private ClientRequest requestForCall(final ClientRequest requestContext) { return copyRequestContext ? new ClientRequest(requestContext) : requestContext; } @Override public Response invoke() throws ProcessingException, WebApplicationException { final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); return requestScope.runInScope( (Producer<Response>) () -> new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)), requestScope)); } @Override public <T> T invoke(final Class<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); //noinspection Duplicates return requestScope.runInScope(() -> { try { return translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType); } catch (final ProcessingException ex) { if (ex.getCause() instanceof WebApplicationException) { throw (WebApplicationException) ex.getCause(); } throw ex; } }); } @Override public <T> T invoke(final GenericType<T> responseType) throws ProcessingException, WebApplicationException { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final ClientRuntime runtime = request().getClientRuntime(); final RequestScope requestScope = runtime.getRequestScope(); //noinspection Duplicates return requestScope.runInScope(() -> { try { return translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType); } catch (final ProcessingException ex) { if (ex.getCause() instanceof WebApplicationException) { throw (WebApplicationException) ex.getCause(); } throw ex; } }); } @Override public Future<Response> submit() { final CompletableFuture<Response> responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { @Override public void completed(final ClientResponse response, final RequestScope scope) { if (!responseFuture.isCancelled()) { responseFuture.complete(new InboundJaxrsResponse(response, scope)); } else { response.close(); } } @Override public void failed(final ProcessingException error) { if (!responseFuture.isCancelled()) { responseFuture.completeExceptionally(error); } } })); return responseFuture; } @Override public <T> Future<T> submit(final Class<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture<T> responseFuture = new CompletableFuture<>(); //noinspection Duplicates final ClientRuntime runtime = request().getClientRuntime(); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { @Override public void completed(final ClientResponse response, final RequestScope scope) { if (responseFuture.isCancelled()) { response.close(); return; } try { responseFuture.complete(translate(response, scope, responseType)); } catch (final ProcessingException ex) { failed(ex); } } @Override public void failed(final ProcessingException error) { if (responseFuture.isCancelled()) { return; } if (error.getCause() instanceof WebApplicationException) { responseFuture.completeExceptionally(error.getCause()); } else { responseFuture.completeExceptionally(error); } } })); return responseFuture; } private <T> T translate(final ClientResponse response, final RequestScope scope, final Class<T> responseType) throws ProcessingException { if (responseType == Response.class) { return responseType.cast(new InboundJaxrsResponse(response, scope)); } if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { try { return response.readEntity(responseType); } catch (final ProcessingException ex) { if (ex.getClass() == ProcessingException.class) { throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), ex.getCause()); } throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), ex); } catch (final WebApplicationException ex) { throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), ex); } catch (final Exception ex) { throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), LocalizationMessages.UNEXPECTED_ERROR_RESPONSE_PROCESSING(), ex); } } else { throw convertToException(new InboundJaxrsResponse(response, scope)); } } @Override public <T> Future<T> submit(final GenericType<T> responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture<T> responseFuture = new CompletableFuture<>(); //noinspection Duplicates final ClientRuntime runtime = request().getClientRuntime(); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new ResponseCallback() { @Override public void completed(final ClientResponse response, final RequestScope scope) { if (responseFuture.isCancelled()) { response.close(); return; } try { responseFuture.complete(translate(response, scope, responseType)); } catch (final ProcessingException ex) { failed(ex); } } @Override public void failed(final ProcessingException error) { if (responseFuture.isCancelled()) { return; } if (error.getCause() instanceof WebApplicationException) { responseFuture.completeExceptionally(error.getCause()); } else { responseFuture.completeExceptionally(error); } } })); return responseFuture; } private <T> T translate(final ClientResponse response, final RequestScope scope, final GenericType<T> responseType) throws ProcessingException { if (responseType.getRawType() == Response.class) { //noinspection unchecked return (T) new InboundJaxrsResponse(response, scope); } if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { try { return response.readEntity(responseType); } catch (final ProcessingException ex) { throw new ResponseProcessingException( new InboundJaxrsResponse(response, scope), ex.getCause() != null ? ex.getCause() : ex); } catch (final WebApplicationException ex) { throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), ex); } catch (final Exception ex) { throw new ResponseProcessingException(new InboundJaxrsResponse(response, scope), LocalizationMessages.UNEXPECTED_ERROR_RESPONSE_PROCESSING(), ex); } } else { throw convertToException(new InboundJaxrsResponse(response, scope)); } } @Override public <T> Future<T> submit(final InvocationCallback<T> callback) { return submit(null, callback); }
Submit the request for an asynchronous invocation and register an InvocationCallback to process the future result of the invocation.

Response type in this case is taken from responseType param (if not null) rather than from callback. This allows to pass callbacks like new InvocationCallback&lt;&gt() {...}.

Params:
  • responseType – response type that is used instead of obtaining types from callback.
  • callback – invocation callback for asynchronous processing of the request invocation result.
Type parameters:
  • <T> – response type
Returns:future response object of the specified type as a result of the request invocation.
/** * Submit the request for an asynchronous invocation and register an * {@link InvocationCallback} to process the future result of the invocation. * <p> * Response type in this case is taken from {@code responseType} param (if not {@code null}) rather * than from {@code callback}. This allows to pass callbacks like {@code new InvocationCallback&lt;&gt() {...}}. * </p> * * @param <T> response type * @param responseType response type that is used instead of obtaining types from {@code callback}. * @param callback invocation callback for asynchronous processing of the * request invocation result. * @return future response object of the specified type as a result of the * request invocation. */
public <T> Future<T> submit(final GenericType<T> responseType, final InvocationCallback<T> callback) { final CompletableFuture<T> responseFuture = new CompletableFuture<>(); try { final ReflectionHelper.DeclaringClassInterfacePair pair = ReflectionHelper.getClass(callback.getClass(), InvocationCallback.class); final Type callbackParamType; final Class<T> callbackParamClass; if (responseType == null) { // If we don't have response use callback to obtain param types. final Type[] typeArguments = ReflectionHelper.getParameterizedTypeArguments(pair); if (typeArguments == null || typeArguments.length == 0) { callbackParamType = Object.class; } else { callbackParamType = typeArguments[0]; } callbackParamClass = ReflectionHelper.erasure(callbackParamType); } else { callbackParamType = responseType.getType(); callbackParamClass = ReflectionHelper.erasure(responseType.getRawType()); } final ResponseCallback responseCallback = new ResponseCallback() { @Override public void completed(final ClientResponse response, final RequestScope scope) { if (responseFuture.isCancelled()) { response.close(); failed(new ProcessingException( new CancellationException(LocalizationMessages.ERROR_REQUEST_CANCELLED()))); return; } final T result; if (callbackParamClass == Response.class) { result = callbackParamClass.cast(new InboundJaxrsResponse(response, scope)); responseFuture.complete(result); callback.completed(result); } else if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { result = response.readEntity(new GenericType<T>(callbackParamType)); responseFuture.complete(result); callback.completed(result); } else { failed(convertToException(new InboundJaxrsResponse(response, scope))); } } @Override public void failed(final ProcessingException error) { try { if (error.getCause() instanceof WebApplicationException) { responseFuture.completeExceptionally(error.getCause()); } else if (!responseFuture.isCancelled()) { responseFuture.completeExceptionally(error); } } finally { callback.failed(error.getCause() instanceof CancellationException ? error.getCause() : error); } } }; final ClientRuntime runtime = request().getClientRuntime(); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), responseCallback)); } catch (final Throwable error) { final ProcessingException ce; //noinspection ChainOfInstanceofChecks if (error instanceof ProcessingException) { ce = (ProcessingException) error; responseFuture.completeExceptionally(ce); } else if (error instanceof WebApplicationException) { ce = new ProcessingException(error); responseFuture.completeExceptionally(error); } else { ce = new ProcessingException(error); responseFuture.completeExceptionally(ce); } callback.failed(ce); } return responseFuture; } @Override public JerseyInvocation property(final String name, final Object value) { requestContext.setProperty(name, value); return this; } private ProcessingException convertToException(final Response response) { try { // Buffer and close entity input stream (if any) to prevent // leaking connections (see JERSEY-2157). response.bufferEntity(); final WebApplicationException webAppException; final int statusCode = response.getStatus(); final Response.Status status = Response.Status.fromStatusCode(statusCode); if (status == null) { final Response.Status.Family statusFamily = response.getStatusInfo().getFamily(); webAppException = createExceptionForFamily(response, statusFamily); } else { switch (status) { case BAD_REQUEST: webAppException = new BadRequestException(response); break; case UNAUTHORIZED: webAppException = new NotAuthorizedException(response); break; case FORBIDDEN: webAppException = new ForbiddenException(response); break; case NOT_FOUND: webAppException = new NotFoundException(response); break; case METHOD_NOT_ALLOWED: webAppException = new NotAllowedException(response); break; case NOT_ACCEPTABLE: webAppException = new NotAcceptableException(response); break; case UNSUPPORTED_MEDIA_TYPE: webAppException = new NotSupportedException(response); break; case INTERNAL_SERVER_ERROR: webAppException = new InternalServerErrorException(response); break; case SERVICE_UNAVAILABLE: webAppException = new ServiceUnavailableException(response); break; default: final Response.Status.Family statusFamily = response.getStatusInfo().getFamily(); webAppException = createExceptionForFamily(response, statusFamily); } } return new ResponseProcessingException(response, webAppException); } catch (final Throwable t) { return new ResponseProcessingException(response, LocalizationMessages.RESPONSE_TO_EXCEPTION_CONVERSION_FAILED(), t); } } private WebApplicationException createExceptionForFamily(final Response response, final Response.Status.Family statusFamily) { final WebApplicationException webAppException; switch (statusFamily) { case REDIRECTION: webAppException = new RedirectionException(response); break; case CLIENT_ERROR: webAppException = new ClientErrorException(response); break; case SERVER_ERROR: webAppException = new ServerErrorException(response); break; default: webAppException = new WebApplicationException(response); } return webAppException; }
Returns a reference to the mutable request context to be invoked.
Returns:mutable request context to be invoked.
/** * Returns a reference to the mutable request context to be invoked. * * @return mutable request context to be invoked. */
ClientRequest request() { return requestContext; } }