diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java index 54599e8d94..479b1fb038 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/ExecutorServiceWrapper.java @@ -35,13 +35,12 @@ */ class ExecutorServiceWrapper implements ExecutorService { + static final ThreadLocal> asyncInterceptors = new ThreadLocal<>(); + private final ExecutorService wrapped; - private final List asyncInterceptors; - ExecutorServiceWrapper(ExecutorService wrapped, - List asyncInterceptors) { + ExecutorServiceWrapper(ExecutorService wrapped) { this.wrapped = wrapped; - this.asyncInterceptors = asyncInterceptors; } @Override @@ -111,24 +110,36 @@ public void execute(Runnable command) { wrapped.execute(wrap(command)); } - private Callable wrap(Callable task) { + private static Callable wrap(Callable task) { + List asyncInvocationInterceptors = asyncInterceptors.get(); + asyncInterceptors.remove(); return () -> { - asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext); + applyContextOnInterceptors(asyncInvocationInterceptors); return task.call(); }; } - private Runnable wrap(Runnable task) { + private static Runnable wrap(Runnable task) { + List asyncInvocationInterceptors = asyncInterceptors.get(); + asyncInterceptors.remove(); return () -> { - asyncInterceptors.forEach(AsyncInvocationInterceptor::applyContext); + applyContextOnInterceptors(asyncInvocationInterceptors); task.run(); }; } + private static void applyContextOnInterceptors(List asyncInvocationInterceptors) { + if (asyncInvocationInterceptors != null) { + //applyContext methods need to be called in reverse ordering of priority + for (int i = asyncInvocationInterceptors.size(); i-- > 0; ) { + asyncInvocationInterceptors.get(i).applyContext(); + } + } + } - private Collection> wrap(Collection> tasks) { + private static Collection> wrap(Collection> tasks) { return tasks.stream() - .map(this::wrap) + .map(ExecutorServiceWrapper::wrap) .collect(Collectors.toList()); } } diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java index 1a045eede4..6bd0e9531a 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/InterfaceModel.java @@ -38,6 +38,7 @@ import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor; +import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory; import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import org.glassfish.jersey.client.inject.ParameterUpdater; @@ -64,7 +65,7 @@ class InterfaceModel { private final CreationalContext creationalContext; private final List clientHeaders; - private final List asyncInterceptors; + private final List asyncInterceptorFactories; private final Set responseExceptionMappers; private final Set paramConverterProviders; private final Set interceptorAnnotations; @@ -73,23 +74,23 @@ class InterfaceModel { /** * Creates new model based on interface class. Interface is parsed according to specific annotations. * - * @param restClientClass interface class - * @param responseExceptionMappers registered exception mappers - * @param paramConverterProviders registered parameter providers - * @param asyncInterceptors async interceptors + * @param restClientClass interface class + * @param responseExceptionMappers registered exception mappers + * @param paramConverterProviders registered parameter providers + * @param asyncInterceptorFactories async interceptor factories * @param injectionManager * @return new model instance */ static InterfaceModel from(Class restClientClass, Set responseExceptionMappers, Set paramConverterProviders, - List asyncInterceptors, + List asyncInterceptorFactories, InjectionManager injectionManager, BeanManager beanManager) { return new Builder(restClientClass, responseExceptionMappers, paramConverterProviders, - asyncInterceptors, + asyncInterceptorFactories, injectionManager, beanManager).build(); } @@ -106,7 +107,7 @@ private InterfaceModel(Builder builder) { this.paramConverterProviders = builder.paramConverterProviders; this.interceptorAnnotations = builder.interceptorAnnotations; this.creationalContext = builder.creationalContext; - this.asyncInterceptors = builder.asyncInterceptors; + this.asyncInterceptorFactories = builder.asyncInterceptorFactories; this.beanManager = builder.beanManager; } @@ -169,8 +170,8 @@ List getClientHeaders() { * * @return registered async interceptors */ - List getAsyncInterceptors() { - return asyncInterceptors; + List getAsyncInterceptorFactories() { + return asyncInterceptorFactories; } /** @@ -251,7 +252,7 @@ private static class Builder { private ClientHeadersFactory clientHeadersFactory; private CreationalContext creationalContext; private List clientHeaders; - private List asyncInterceptors; + private List asyncInterceptorFactories; private Set responseExceptionMappers; private Set paramConverterProviders; private Set interceptorAnnotations; @@ -259,14 +260,14 @@ private static class Builder { private Builder(Class restClientClass, Set responseExceptionMappers, Set paramConverterProviders, - List asyncInterceptors, + List asyncInterceptorFactories, InjectionManager injectionManager, BeanManager beanManager) { this.injectionManager = injectionManager; this.restClientClass = restClientClass; this.responseExceptionMappers = responseExceptionMappers; this.paramConverterProviders = paramConverterProviders; - this.asyncInterceptors = asyncInterceptors; + this.asyncInterceptorFactories = asyncInterceptorFactories; this.beanManager = beanManager; filterAllInterceptorAnnotations(); } diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java index 4e738a0342..d106f0cfc5 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/MethodModel.java @@ -69,6 +69,7 @@ import org.eclipse.microprofile.rest.client.RestClientDefinitionException; import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam; import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor; +import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; /** @@ -121,7 +122,7 @@ private MethodModel(Builder builder) { subResourceModel = RestClientModel.from(returnType.getRawType(), interfaceModel.getResponseExceptionMappers(), interfaceModel.getParamConverterProviders(), - interfaceModel.getAsyncInterceptors(), + interfaceModel.getAsyncInterceptorFactories(), interfaceModel.getInjectionManager(), interfaceModel.getBeanManager()); } else { @@ -243,6 +244,14 @@ private CompletableFuture asynchronousCall(Invocation.Builder builder, Object entity, Method method, MultivaluedMap customHeaders) { + + //AsyncInterceptors initialization + List asyncInterceptors = interfaceModel.getAsyncInterceptorFactories().stream() + .map(AsyncInvocationInterceptorFactory::newInterceptor) + .collect(Collectors.toList()); + asyncInterceptors.forEach(AsyncInvocationInterceptor::prepareContext); + ExecutorServiceWrapper.asyncInterceptors.set(asyncInterceptors); + CompletableFuture result = new CompletableFuture<>(); Future theFuture; if (entity != null @@ -255,7 +264,7 @@ private CompletableFuture asynchronousCall(Invocation.Builder builder, CompletableFuture completableFuture = (CompletableFuture) theFuture; completableFuture.thenAccept(response -> { - interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext); + asyncInterceptors.forEach(AsyncInvocationInterceptor::removeContext); try { evaluateResponse(response, method); if (returnType.getType().equals(Void.class)) { @@ -269,7 +278,7 @@ private CompletableFuture asynchronousCall(Invocation.Builder builder, result.completeExceptionally(e); } }).exceptionally(throwable -> { - interfaceModel.getAsyncInterceptors().forEach(AsyncInvocationInterceptor::removeContext); + asyncInterceptors.forEach(AsyncInvocationInterceptor::removeContext); result.completeExceptionally(throwable); return null; }); diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java index ded8ec5e0c..e963850edb 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientBuilderImpl.java @@ -25,6 +25,7 @@ import java.security.AccessController; import java.security.KeyStore; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -36,7 +37,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.annotation.Priority; import javax.net.ssl.HostnameVerifier; @@ -81,7 +81,7 @@ class RestClientBuilderImpl implements RestClientBuilder { private final Set responseExceptionMappers; private final Set paramConverterProviders; - private final List asyncInterceptorFactories; + private final List asyncInterceptorFactories; private final Config config; private final ConfigWrapper configWrapper; private URI uri; @@ -153,14 +153,10 @@ public T build(Class interfaceClass) throws IllegalStateException, RestCl //We need to check first if default exception mapper was not disabled by property on builder. registerExceptionMapper(); + //sort all AsyncInvocationInterceptorFactory by priority + asyncInterceptorFactories.sort(Comparator.comparingInt(AsyncInvocationInterceptorFactoryPriorityWrapper::getPriority)); - //AsyncInterceptors initialization - List asyncInterceptors = asyncInterceptorFactories.stream() - .map(AsyncInvocationInterceptorFactory::newInterceptor) - .collect(Collectors.toList()); - asyncInterceptors.forEach(AsyncInvocationInterceptor::prepareContext); - - clientBuilder.executorService(new ExecutorServiceWrapper(executorService.get(), asyncInterceptors)); + clientBuilder.executorService(new ExecutorServiceWrapper(executorService.get())); if (null != sslContext) { clientBuilder.sslContext(sslContext); @@ -187,7 +183,7 @@ public T build(Class interfaceClass) throws IllegalStateException, RestCl RestClientModel restClientModel = RestClientModel.from(interfaceClass, responseExceptionMappers, paramConverterProviders, - asyncInterceptors, + new ArrayList<>(asyncInterceptorFactories), injectionManagerExposer.injectionManager, CdiUtil.getBeanManager()); @@ -338,11 +334,11 @@ public RestClientBuilder register(Class componentClass, Map, Integer public RestClientBuilder register(Object component) { if (component instanceof ResponseExceptionMapper) { ResponseExceptionMapper mapper = (ResponseExceptionMapper) component; - registerCustomProvider(component, -1); + registerCustomProvider(component, mapper.getPriority()); clientBuilder.register(mapper, mapper.getPriority()); } else { clientBuilder.register(component); - registerCustomProvider(component, -1); + registerCustomProvider(component, null); } return this; } @@ -384,7 +380,7 @@ private boolean isSupportedCustomProvider(Class providerClass) { || AsyncInvocationInterceptorFactory.class.isAssignableFrom(providerClass); } - private void registerCustomProvider(Object instance, int priority) { + private void registerCustomProvider(Object instance, Integer priority) { if (!isSupportedCustomProvider(instance.getClass())) { return; } @@ -399,7 +395,9 @@ private void registerCustomProvider(Object instance, int priority) { paramConverterProviders.add((ParamConverterProvider) instance); } if (instance instanceof AsyncInvocationInterceptorFactory) { - asyncInterceptorFactories.add((AsyncInvocationInterceptorFactory) instance); + asyncInterceptorFactories + .add(new AsyncInvocationInterceptorFactoryPriorityWrapper((AsyncInvocationInterceptorFactory) instance, + priority)); } } @@ -417,4 +415,30 @@ public boolean configure(FeatureContext context) { } } + private static class AsyncInvocationInterceptorFactoryPriorityWrapper + implements AsyncInvocationInterceptorFactory { + + private AsyncInvocationInterceptorFactory factory; + private Integer priority; + + AsyncInvocationInterceptorFactoryPriorityWrapper(AsyncInvocationInterceptorFactory factory, Integer priority) { + this.factory = factory; + this.priority = priority; + } + + @Override + public AsyncInvocationInterceptor newInterceptor() { + return factory.newInterceptor(); + } + + Integer getPriority() { + if (priority == null) { + priority = Optional.ofNullable(factory.getClass().getAnnotation(Priority.class)) + .map(Priority::value) + .orElse(Priorities.USER); + } + return priority; + } + } + } diff --git a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java index 4e81ca3833..e0ff634191 100644 --- a/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java +++ b/ext/microprofile/mp-rest-client/src/main/java/org/glassfish/jersey/microprofile/restclient/RestClientModel.java @@ -28,7 +28,7 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.ext.ParamConverterProvider; -import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptor; +import org.eclipse.microprofile.rest.client.ext.AsyncInvocationInterceptorFactory; import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper; import org.glassfish.jersey.internal.inject.InjectionManager; @@ -56,13 +56,13 @@ class RestClientModel { static RestClientModel from(Class restClientClass, Set responseExceptionMappers, Set paramConverterProviders, - List asyncInterceptors, + List asyncInterceptorFactories, InjectionManager injectionManager, BeanManager beanManager) { InterfaceModel interfaceModel = InterfaceModel.from(restClientClass, responseExceptionMappers, paramConverterProviders, - asyncInterceptors, + asyncInterceptorFactories, injectionManager, beanManager); return new Builder()