feign.BaseBuilder Maven / Gradle / Ivy
/*
* Copyright 2012-2023 The Feign Authors
*
* 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 static feign.ExceptionPropagationPolicy.NONE;
import feign.Feign.ResponseMappingDecoder;
import feign.Logger.NoOpLogger;
import feign.Request.Options;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import feign.querymap.FieldQueryMapEncoder;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public abstract class BaseBuilder, T> implements Cloneable {
private final B thisB;
protected final List requestInterceptors =
new ArrayList<>();
protected final List responseInterceptors = new ArrayList<>();
protected Logger.Level logLevel = Logger.Level.NONE;
protected Contract contract = new Contract.Default();
protected Retryer retryer = new Retryer.Default();
protected Logger logger = new NoOpLogger();
protected Encoder encoder = new Encoder.Default();
protected Decoder decoder = new Decoder.Default();
protected boolean closeAfterDecode = true;
protected boolean decodeVoid = false;
protected QueryMapEncoder queryMapEncoder = QueryMap.MapEncoder.FIELD.instance();
protected ErrorDecoder errorDecoder = new ErrorDecoder.Default();
protected Options options = new Options();
protected InvocationHandlerFactory invocationHandlerFactory =
new InvocationHandlerFactory.Default();
protected boolean dismiss404;
protected ExceptionPropagationPolicy propagationPolicy = NONE;
protected List capabilities = new ArrayList<>();
public BaseBuilder() {
super();
thisB = (B) this;
}
public B logLevel(Logger.Level logLevel) {
this.logLevel = logLevel;
return thisB;
}
public B contract(Contract contract) {
this.contract = contract;
return thisB;
}
public B retryer(Retryer retryer) {
this.retryer = retryer;
return thisB;
}
public B logger(Logger logger) {
this.logger = logger;
return thisB;
}
public B encoder(Encoder encoder) {
this.encoder = encoder;
return thisB;
}
public B decoder(Decoder decoder) {
this.decoder = decoder;
return thisB;
}
/**
* This flag indicates that the response should not be automatically closed upon completion of
* decoding the message. This should be set if you plan on processing the response into a
* lazy-evaluated construct, such as a {@link java.util.Iterator}.
*
*
* Feign standard decoders do not have built in support for this flag. If you are using this flag,
* you MUST also use a custom Decoder, and be sure to close all resources appropriately somewhere
* in the Decoder (you can use {@link Util#ensureClosed} for convenience).
*
* @since 9.6
*
*/
public B doNotCloseAfterDecode() {
this.closeAfterDecode = false;
return thisB;
}
public B decodeVoid() {
this.decodeVoid = true;
return thisB;
}
public B queryMapEncoder(QueryMapEncoder queryMapEncoder) {
this.queryMapEncoder = queryMapEncoder;
return thisB;
}
/**
* Allows to map the response before passing it to the decoder.
*/
public B mapAndDecode(ResponseMapper mapper, Decoder decoder) {
this.decoder = new ResponseMappingDecoder(mapper, decoder);
return thisB;
}
/**
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
*
*
* All first-party (ex gson) decoders return well-known empty values defined by
* {@link Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder)
* decoder} or make your own.
*
*
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or
* fallback policy. If your server returns a different status for not-found, correct via a custom
* {@link #client(Client) client}.
*
* @since 11.9
*/
public B dismiss404() {
this.dismiss404 = true;
return thisB;
}
/**
* This flag indicates that the {@link #decoder(Decoder) decoder} should process responses with
* 404 status, specifically returning null or empty instead of throwing {@link FeignException}.
*
*
* All first-party (ex gson) decoders return well-known empty values defined by
* {@link Util#emptyValueOf}. To customize further, wrap an existing {@link #decoder(Decoder)
* decoder} or make your own.
*
*
* This flag only works with 404, as opposed to all or arbitrary status codes. This was an
* explicit decision: 404 -> empty is safe, common and doesn't complicate redirection, retry or
* fallback policy. If your server returns a different status for not-found, correct via a custom
* {@link #client(Client) client}.
*
* @since 8.12
* @deprecated use {@link #dismiss404()} instead.
*/
@Deprecated
public B decode404() {
this.dismiss404 = true;
return thisB;
}
public B errorDecoder(ErrorDecoder errorDecoder) {
this.errorDecoder = errorDecoder;
return thisB;
}
public B options(Options options) {
this.options = options;
return thisB;
}
/**
* Adds a single request interceptor to the builder.
*/
public B requestInterceptor(RequestInterceptor requestInterceptor) {
this.requestInterceptors.add(requestInterceptor);
return thisB;
}
/**
* Sets the full set of request interceptors for the builder, overwriting any previous
* interceptors.
*/
public B requestInterceptors(Iterable requestInterceptors) {
this.requestInterceptors.clear();
for (RequestInterceptor requestInterceptor : requestInterceptors) {
this.requestInterceptors.add(requestInterceptor);
}
return thisB;
}
/**
* Sets the full set of request interceptors for the builder, overwriting any previous
* interceptors.
*/
public B responseInterceptors(Iterable responseInterceptors) {
this.responseInterceptors.clear();
for (ResponseInterceptor responseInterceptor : responseInterceptors) {
this.responseInterceptors.add(responseInterceptor);
}
return thisB;
}
/**
* Adds a single response interceptor to the builder.
*/
public B responseInterceptor(ResponseInterceptor responseInterceptor) {
this.responseInterceptors.add(responseInterceptor);
return thisB;
}
/**
* Allows you to override how reflective dispatch works inside of Feign.
*/
public B invocationHandlerFactory(InvocationHandlerFactory invocationHandlerFactory) {
this.invocationHandlerFactory = invocationHandlerFactory;
return thisB;
}
public B exceptionPropagationPolicy(ExceptionPropagationPolicy propagationPolicy) {
this.propagationPolicy = propagationPolicy;
return thisB;
}
public B addCapability(Capability capability) {
this.capabilities.add(capability);
return thisB;
}
@SuppressWarnings("unchecked")
B enrich() {
if (capabilities.isEmpty()) {
return thisB;
}
try {
B clone = (B) thisB.clone();
getFieldsToEnrich().forEach(field -> {
field.setAccessible(true);
try {
final Object originalValue = field.get(clone);
final Object enriched;
if (originalValue instanceof List) {
Type ownerType =
((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
enriched = ((List) originalValue).stream()
.map(value -> Capability.enrich(value, (Class>) ownerType, capabilities))
.collect(Collectors.toList());
} else {
enriched = Capability.enrich(originalValue, field.getType(), capabilities);
}
field.set(clone, enriched);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException("Unable to enrich field " + field, e);
} finally {
field.setAccessible(false);
}
});
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
List getFieldsToEnrich() {
return Util.allFields(getClass())
.stream()
// exclude anything generated by compiler
.filter(field -> !field.isSynthetic())
// and capabilities itself
.filter(field -> !Objects.equals(field.getName(), "capabilities"))
// and thisB helper field
.filter(field -> !Objects.equals(field.getName(), "thisB"))
// skip primitive types
.filter(field -> !field.getType().isPrimitive())
// skip enumerations
.filter(field -> !field.getType().isEnum())
.collect(Collectors.toList());
}
public final T build() {
return enrich().internalBuild();
}
protected abstract T internalBuild();
protected ResponseInterceptor.Chain responseInterceptorChain() {
ResponseInterceptor.Chain endOfChain =
ResponseInterceptor.Chain.DEFAULT;
ResponseInterceptor.Chain executionChain = this.responseInterceptors.stream()
.reduce(ResponseInterceptor::andThen)
.map(interceptor -> interceptor.apply(endOfChain))
.orElse(endOfChain);
return (ResponseInterceptor.Chain) Capability.enrich(executionChain,
ResponseInterceptor.Chain.class, capabilities);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy