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

org.glassfish.jersey.client.JerseyInvocation Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.9
Show newest version
/*
 * Copyright (c) 2011, 2024 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.List;
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.function.BiFunction;
import java.util.logging.Logger;

import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.ClientErrorException;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.NotAcceptableException;
import jakarta.ws.rs.NotAllowedException;
import jakarta.ws.rs.NotAuthorizedException;
import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.NotSupportedException;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.RedirectionException;
import jakarta.ws.rs.ServerErrorException;
import jakarta.ws.rs.ServiceUnavailableException;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.client.CompletionStageRxInvoker;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.InvocationCallback;
import jakarta.ws.rs.client.ResponseProcessingException;
import jakarta.ws.rs.client.RxInvoker;
import jakarta.ws.rs.client.RxInvokerProvider;
import jakarta.ws.rs.client.SyncInvoker;
import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

import org.glassfish.jersey.client.internal.ClientResponseProcessingException;
import org.glassfish.jersey.client.internal.LocalizationMessages;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.inject.Bindings;
import org.glassfish.jersey.internal.inject.DisposableSupplier;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.inject.ServiceHolder;
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.ExecutorProviders;
import org.glassfish.jersey.process.internal.RequestScope;
import org.glassfish.jersey.spi.ExecutorServiceProvider;

/**
 * Jersey implementation of {@link jakarta.ws.rs.client.Invocation JAX-RS client-side
 * request invocation} contract.
 *
 * @author Marek Potociar
 */
public class JerseyInvocation implements jakarta.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 boolean ignoreResponseException;

    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;

        Object value = builder.requestContext.getConfiguration()
                .getProperty(ClientProperties.IGNORE_EXCEPTION_RESPONSE);
        if (value != null) {
            Boolean booleanValue = PropertiesHelper.convertValue(value, Boolean.class);
            if (booleanValue != null) {
                this.ignoreResponseException = booleanValue;
            }
        }
    }

    private enum EntityPresence {
        MUST_BE_NULL,
        MUST_BE_PRESENT,
        OPTIONAL
    }

    private static final Map METHODS = initializeMap();

    private static Map initializeMap() {
        final Map 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.OPTIONAL);
        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 {@link jakarta.ws.rs.client.Invocation.Builder client invocation builder}.
     */
    public static class Builder implements jakarta.ws.rs.client.Invocation.Builder {

        private final ClientRequest requestContext;

        /**
         * 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.
         *
         * @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 jakarta.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 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 headers) {
            requestContext.replaceHeaders(headers);
            return this;
        }

        @Override
        public Response get() throws ProcessingException {
            return method("GET");
        }

        @Override
        public  T get(final Class responseType) throws ProcessingException, WebApplicationException {
            return method("GET", responseType);
        }

        @Override
        public  T get(final GenericType responseType) throws ProcessingException, WebApplicationException {
            return method("GET", responseType);
        }

        @Override
        public Response put(final Entity entity) throws ProcessingException {
            return method("PUT", entity);
        }

        @Override
        public  T put(final Entity entity, final Class responseType)
                throws ProcessingException, WebApplicationException {
            return method("PUT", entity, responseType);
        }

        @Override
        public  T put(final Entity entity, final GenericType 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 post(final Entity entity, final Class responseType)
                throws ProcessingException, WebApplicationException {
            return method("POST", entity, responseType);
        }

        @Override
        public  T post(final Entity entity, final GenericType responseType)
                throws ProcessingException, WebApplicationException {
            return method("POST", entity, responseType);
        }

        @Override
        public Response delete() throws ProcessingException {
            return method("DELETE");
        }

        @Override
        public  T delete(final Class responseType) throws ProcessingException, WebApplicationException {
            return method("DELETE", responseType);
        }

        @Override
        public  T delete(final GenericType 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 options(final Class responseType) throws ProcessingException, WebApplicationException {
            return method("OPTIONS", responseType);
        }

        @Override
        public  T options(final GenericType responseType) throws ProcessingException, WebApplicationException {
            return method("OPTIONS", responseType);
        }

        @Override
        public Response trace() throws ProcessingException {
            return method("TRACE");
        }

        @Override
        public  T trace(final Class responseType) throws ProcessingException, WebApplicationException {
            return method("TRACE", responseType);
        }

        @Override
        public  T trace(final GenericType 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 method(final String name, final Class 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 method(final String name, final GenericType 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 method(final String name, final Entity entity, final Class 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 method(final String name, final Entity entity, final GenericType 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 rx(JerseyCompletionStageRxInvoker.class);
        }

        @Override
        public  T rx(Class clazz) {
            if (clazz == JerseyCompletionStageRxInvoker.class) {
                final ExecutorService configured = request().getClientConfig().getExecutorService();
                if (configured == null) {
                    final ExecutorService provided = executorService();
                    if (provided != null) {
                        ((ClientConfig) request().getConfiguration()).executorService(provided);
                    }
                }
                return (T) new JerseyCompletionStageRxInvoker(this);
            }
            return createRxInvoker(clazz, executorService());
        }

        private  T rx(Class 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;
            }

            final List> serviceHolders =
                    this.requestContext.getInjectionManager().getAllServiceHolders(ExecutorServiceProvider.class);

            BestServiceHolder best = serviceHolders.stream()
                    .map(BestServiceHolder::new).sorted((a, b) -> a.isBetterThen(b) ? -1 : 1).findFirst().get();

            return best.provider.getExecutorService();
        }

        /*
         * Priority goes to: 1) user async
         *                   2) user nonasync
         *                   3) default async
         */
        private static final class BestServiceHolder {
            private final ExecutorServiceProvider provider;
            private final int value;

            private BestServiceHolder(ServiceHolder holder) {
                provider = holder.getInstance();
                boolean isDefault = DefaultClientAsyncExecutorProvider.class.equals(holder.getImplementationClass())
                        || ClientExecutorProvidersConfigurator.ClientExecutorServiceProvider.class
                                .equals(holder.getImplementationClass());
                boolean isAsync = holder.getImplementationClass().getAnnotation(ClientAsyncExecutor.class) != null;
                value = 10 * (isDefault ? 0 : 1) + (isAsync ? 1 : 0);
            }

            public boolean isBetterThen(BestServiceHolder other) {
                return this.value > other.value;
            }
        }

        /**
         * Create {@link RxInvoker} from provided {@code RxInvoker} subclass.
         * 

* 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 {@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 createRxInvoker(Class clazz, ExecutorService executorService) { if (clazz == null) { throw new IllegalArgumentException(LocalizationMessages.NULL_INPUT_PARAMETER("clazz")); } Iterable 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())); } /** * Sets Future that backs {@link ClientRequest} {@link ClientRequest#isCancelled()} method. Can be used for instance * by {@link CompletionStageRxInvoker} to pass the created {@link CompletableFuture} to the provided {@link SyncInvoker}. * @param cancellable the {@link Future} whose result of {@link Future#cancel(boolean)} will be available by * {@link ClientRequest#isCancelled()}. * @return the updated builder. */ public Builder setCancellable(Future cancellable) { requestContext.setCancellable(cancellable); return this; } } /* package */ static class AsyncInvoker extends CompletableFutureAsyncInvoker implements jakarta.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 method(final String name) { builder.requestContext.setMethod(name); return (CompletableFuture) new JerseyInvocation(builder).submit(); } @Override public CompletableFuture method(final String name, final Class responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); return (CompletableFuture) new JerseyInvocation(builder).submit(responseType); } @Override public CompletableFuture method(final String name, final GenericType responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); return (CompletableFuture) new JerseyInvocation(builder).submit(responseType); } @Override public CompletableFuture method(final String name, final InvocationCallback callback) { builder.requestContext.setMethod(name); return (CompletableFuture) new JerseyInvocation(builder).submit(callback); } @Override public CompletableFuture method(final String name, final Entity entity) { builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture) new JerseyInvocation(builder).submit(); } @Override public CompletableFuture method(final String name, final Entity entity, final Class responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture) new JerseyInvocation(builder).submit(responseType); } @Override public CompletableFuture method(final String name, final Entity entity, final GenericType responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture) new JerseyInvocation(builder).submit(responseType); } @Override public CompletableFuture method(final String name, final Entity entity, final InvocationCallback callback) { builder.requestContext.setMethod(name); builder.storeEntity(entity); return (CompletableFuture) 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 runInScope(((Producer) () -> new InboundJaxrsResponse(runtime.invoke(requestForCall(requestContext)), requestScope)), requestScope); } @Override public T invoke(final Class 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(); return runInScope(() -> translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType), requestScope); } @Override public T invoke(final GenericType 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(); return runInScope(() -> translate(runtime.invoke(requestForCall(requestContext)), requestScope, responseType), requestScope); } private T runInScope(Producer producer, RequestScope scope) throws ProcessingException, WebApplicationException { return scope.runInScope(() -> call(producer, scope)); } private T call(Producer producer, RequestScope scope) throws ProcessingException, WebApplicationException { try { return producer.call(); } catch (final ClientResponseProcessingException crpe) { throw new ResponseProcessingException( translate(crpe.getClientResponse(), scope, Response.class), crpe.getCause() ); } catch (final ProcessingException ex) { if (WebApplicationException.class.isInstance(ex.getCause())) { throw (WebApplicationException) ex.getCause(); } throw ex; } } @Override public Future submit() { final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback<>(responseFuture, (request, scope) -> translate(request, scope, Response.class)))); return responseFuture; } @Override public Future submit(final Class responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); return responseFuture; } private T translate(final ClientResponse response, final RequestScope scope, final Class 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 Future submit(final GenericType responseType) { if (responseType == null) { throw new IllegalArgumentException(LocalizationMessages.RESPONSE_TYPE_IS_NULL()); } final CompletableFuture responseFuture = new CompletableFuture<>(); final ClientRuntime runtime = request().getClientRuntime(); requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), new InvocationResponseCallback(responseFuture, (request, scope) -> translate(request, scope, responseType)))); return responseFuture; } private T translate(final ClientResponse response, final RequestScope scope, final GenericType 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 Future submit(final InvocationCallback callback) { return submit(null, callback); } /** * Submit the request for an asynchronous invocation and register an * {@link InvocationCallback} to process the future result of the invocation. *

* 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<>() {...}}. *

* * @param 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 Future submit(final GenericType responseType, final InvocationCallback callback) { final CompletableFuture responseFuture = new CompletableFuture<>(); try { final ReflectionHelper.DeclaringClassInterfacePair pair = ReflectionHelper.getClass(callback.getClass(), InvocationCallback.class); final Type callbackParamType; final Class 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(callbackParamType)); responseFuture.complete(result); callback.completed(result); } else { failed(convertToException(new InboundJaxrsResponse(response, scope))); } } @Override public void failed(final ProcessingException error) { Exception called = null; try { if (error.getCause() instanceof WebApplicationException) { responseFuture.completeExceptionally(error.getCause()); } else if (!responseFuture.isCancelled()) { try { call(() -> { throw error; }, null); } catch (Exception ex) { called = ex; responseFuture.completeExceptionally(ex); } } } finally { callback.failed( error.getCause() instanceof CancellationException ? error.getCause() : called != null ? called : error ); } } }; final ClientRuntime runtime = request().getClientRuntime(); requestContext.setCancellable(responseFuture); runtime.submit(runtime.createRunnableForAsyncProcessing(requestForCall(requestContext), responseCallback)); } catch (final Throwable error) { final ProcessingException ce; //noinspection ChainOfInstanceofChecks if (error instanceof ClientResponseProcessingException) { ce = new ProcessingException(error.getCause()); responseFuture.completeExceptionally(ce); } else 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) { // Use an empty response if ignoring response in exception final int statusCode = response.getStatus(); final Response finalResponse = ignoreResponseException ? Response.status(statusCode).build() : response; try { // Buffer and close entity input stream (if any) to prevent // leaking connections (see JERSEY-2157). response.bufferEntity(); final WebApplicationException webAppException; final Response.Status status = Response.Status.fromStatusCode(statusCode); if (status == null) { final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily(); webAppException = createExceptionForFamily(finalResponse, statusFamily); } else { switch (status) { case BAD_REQUEST: webAppException = new BadRequestException(finalResponse); break; case UNAUTHORIZED: webAppException = new NotAuthorizedException(finalResponse); break; case FORBIDDEN: webAppException = new ForbiddenException(finalResponse); break; case NOT_FOUND: webAppException = new NotFoundException(finalResponse); break; case METHOD_NOT_ALLOWED: webAppException = new NotAllowedException(finalResponse); break; case NOT_ACCEPTABLE: webAppException = new NotAcceptableException(finalResponse); break; case UNSUPPORTED_MEDIA_TYPE: webAppException = new NotSupportedException(finalResponse); break; case INTERNAL_SERVER_ERROR: webAppException = new InternalServerErrorException(finalResponse); break; case SERVICE_UNAVAILABLE: webAppException = new ServiceUnavailableException(finalResponse); break; default: final Response.Status.Family statusFamily = finalResponse.getStatusInfo().getFamily(); webAppException = createExceptionForFamily(finalResponse, statusFamily); } } return new ResponseProcessingException(finalResponse, webAppException); } catch (final Throwable t) { return new ResponseProcessingException(finalResponse, 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. * * @return mutable request context to be invoked. */ ClientRequest request() { return requestContext; } @Override public String toString() { return "JerseyInvocation [" + request().getMethod() + ' ' + request().getUri() + "]"; } private class InvocationResponseCallback implements ResponseCallback { private final CompletableFuture responseFuture; private final BiFunction producer; private InvocationResponseCallback(CompletableFuture responseFuture, BiFunction producer) { this.responseFuture = responseFuture; this.producer = producer; } @Override public void completed(final ClientResponse response, final RequestScope scope) { if (responseFuture.isCancelled()) { response.close(); return; } try { responseFuture.complete(producer.apply(response, scope)); } catch (final ProcessingException ex) { failed(ex); } } @Override public void failed(final ProcessingException error) { if (responseFuture.isCancelled()) { return; } try { call(() -> { throw error; }, null); } catch (Exception exception) { responseFuture.completeExceptionally(exception); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy