/*
* Copyright © 2012 The Feign Authors (feign@commonhaus.dev)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package feign;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Logger.Level;
import feign.Request.Options;
import feign.Target.HardCodedTarget;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.interceptor.MethodInterceptor;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Supplier;
/**
* Enhances {@link Feign} to provide support for asynchronous clients. Context (for example for
* session cookies or tokens) is explicit, as calls for the same session may be done across several
* threads.
*
* {@link Retryer} is not supported in this model, as that is a blocking API. {@link
* ExceptionPropagationPolicy} is made redundant as {@link RetryableException} is never thrown.
* Alternative approaches to retrying can be handled through {@link AsyncClient clients}.
*
* Target interface methods must return {@link CompletableFuture} with a non-wildcard type. As the
* completion is done by the {@link AsyncClient}, it is important that any subsequent processing on
* the thread be short - generally, this should involve notifying some other thread of the work to
* be done (for example, creating and submitting a task to an {@link ExecutorService}).
*/
@Experimental
public final class AsyncFeign {
public static AsyncBuilder builder() {
return new AsyncBuilder<>();
}
/**
* @deprecated use {@link #builder()} instead.
*/
@Deprecated()
public static AsyncBuilder asyncBuilder() {
return builder();
}
/**
* Creates the default {@link ExecutorService} used when neither a custom {@link AsyncClient} nor
* a custom executor is provided. The executor is created per built client (instance scoped)
* rather than being held in a static singleton, so that it - together with its daemon threads -
* becomes eligible for garbage collection once the owning client is discarded. This avoids the
* {@code ClassLoader} leak that a static singleton causes on application redeployments inside
* servlet containers (see gh-3178).
*/
private static ExecutorService defaultExecutorService() {
return Executors.newCachedThreadPool(
r -> {
final Thread result = new Thread(r);
result.setDaemon(true);
return result;
});
}
public static class AsyncBuilder extends BaseBuilder, AsyncFeign> {
private AsyncContextSupplier defaultContextSupplier = () -> null;
private AsyncClient client;
private ExecutorService executorService;
private MethodInfoResolver methodInfoResolver = MethodInfo::new;
@Deprecated
public AsyncBuilder defaultContextSupplier(Supplier supplier) {
this.defaultContextSupplier = supplier::get;
return this;
}
public AsyncBuilder client(AsyncClient client) {
this.client = client;
return this;
}
/**
* Sets the {@link ExecutorService} used by the default {@link AsyncClient} to run the
* (blocking) underlying client calls. When provided, the caller owns the executor's lifecycle
* and is responsible for shutting it down. This is the recommended way to avoid the {@code
* ClassLoader} leak described in gh-3178: supply a managed, shut-downable executor instead of
* relying on the built-in default. Ignored when a custom {@link #client(AsyncClient)} is
* supplied.
*/
public AsyncBuilder executorService(ExecutorService executorService) {
this.executorService = executorService;
return this;
}
private AsyncClient resolveClient() {
if (client != null) {
return client;
}
final ExecutorService executor =
executorService != null ? executorService : defaultExecutorService();
return new DefaultAsyncClient<>(new DefaultClient(null, null), executor);
}
public AsyncBuilder methodInfoResolver(MethodInfoResolver methodInfoResolver) {
this.methodInfoResolver = methodInfoResolver;
return this;
}
@Override
public AsyncBuilder mapAndDecode(ResponseMapper mapper, Decoder decoder) {
return super.mapAndDecode(mapper, decoder);
}
@Override
public AsyncBuilder decoder(Decoder decoder) {
return super.decoder(decoder);
}
@Override
@Deprecated
public AsyncBuilder decode404() {
return super.decode404();
}
@Override
public AsyncBuilder dismiss404() {
return super.dismiss404();
}
@Override
public AsyncBuilder errorDecoder(ErrorDecoder errorDecoder) {
return super.errorDecoder(errorDecoder);
}
@Override
public AsyncBuilder doNotCloseAfterDecode() {
return super.doNotCloseAfterDecode();
}
@Override
public AsyncBuilder decodeVoid() {
return super.decodeVoid();
}
public AsyncBuilder defaultContextSupplier(AsyncContextSupplier supplier) {
this.defaultContextSupplier = supplier;
return this;
}
public T target(Class apiType, String url) {
return target(new HardCodedTarget<>(apiType, url));
}
public T target(Class apiType, String url, C context) {
return target(new HardCodedTarget<>(apiType, url), context);
}
public T target(Target target) {
return build().newInstance(target);
}
public T target(Target target, C context) {
return build().newInstance(target, context);
}
@Override
public AsyncBuilder logLevel(Level logLevel) {
return super.logLevel(logLevel);
}
@Override
public AsyncBuilder contract(Contract contract) {
return super.contract(contract);
}
@Override
public AsyncBuilder logger(Logger logger) {
return super.logger(logger);
}
@Override
public AsyncBuilder encoder(Encoder encoder) {
return super.encoder(encoder);
}
@Override
public AsyncBuilder queryMapEncoder(QueryMapEncoder queryMapEncoder) {
return super.queryMapEncoder(queryMapEncoder);
}
@Override
public AsyncBuilder options(Options options) {
return super.options(options);
}
@Override
public AsyncBuilder requestInterceptor(RequestInterceptor requestInterceptor) {
return super.requestInterceptor(requestInterceptor);
}
@Override
public AsyncBuilder requestInterceptors(Iterable requestInterceptors) {
return super.requestInterceptors(requestInterceptors);
}
@Override
public AsyncBuilder methodInterceptor(MethodInterceptor methodInterceptor) {
return super.methodInterceptor(methodInterceptor);
}
@Override
public AsyncBuilder methodInterceptors(Iterable methodInterceptors) {
return super.methodInterceptors(methodInterceptors);
}
@Override
public AsyncBuilder invocationHandlerFactory(
InvocationHandlerFactory invocationHandlerFactory) {
return super.invocationHandlerFactory(invocationHandlerFactory);
}
@Override
public AsyncFeign internalBuild() {
final AsyncClient client = resolveClient();
AsyncResponseHandler responseHandler =
(AsyncResponseHandler)
Capability.enrich(
new AsyncResponseHandler(
logLevel,
logger,
decoder,
errorDecoder,
dismiss404,
closeAfterDecode,
decodeVoid,
responseInterceptorChain()),
AsyncResponseHandler.class,
capabilities);
final MethodHandler.Factory methodHandlerFactory =
new AsynchronousMethodHandler.Factory<>(
client,
retryer,
requestInterceptors,
methodInterceptors,
responseHandler,
logger,
logLevel,
propagationPolicy,
methodInfoResolver,
new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
options);
final ReflectiveFeign feign =
new ReflectiveFeign<>(
contract, methodHandlerFactory, invocationHandlerFactory, defaultContextSupplier);
return new AsyncFeign<>(feign);
}
}
private final ReflectiveFeign feign;
private AsyncFeign(ReflectiveFeign feign) {
this.feign = feign;
}
public T newInstance(Target target) {
return feign.newInstance(target);
}
public T newInstance(Target target, C context) {
return feign.newInstance(target, context);
}
}