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;
}
public B codec(Codec codec) {
this.encoder = codec.encoder();
this.decoder = codec.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);
}
});
// enrich each request interceptor, then enrich the list as a whole
RequestInterceptor[] requestArray =
clone.requestInterceptors.toArray(new RequestInterceptor[0]);
for (int i = 0; i < requestArray.length; i++) {
requestArray[i] =
(RequestInterceptor)
Capability.enrich(requestArray[i], RequestInterceptor.class, capabilities);
}
RequestInterceptors requestInterceptors =
(RequestInterceptors)
Capability.enrich(
new RequestInterceptors(Arrays.asList(requestArray)),
RequestInterceptors.class,
capabilities);
clone.requestInterceptors = requestInterceptors.interceptors();
// enrich each response interceptor, then enrich the list as a whole
ResponseInterceptor[] responseArray =
clone.responseInterceptors.toArray(new ResponseInterceptor[0]);
for (int i = 0; i < responseArray.length; i++) {
responseArray[i] =
(ResponseInterceptor)
Capability.enrich(responseArray[i], ResponseInterceptor.class, capabilities);
}
ResponseInterceptors responseInterceptors =
(ResponseInterceptors)
Capability.enrich(
new ResponseInterceptors(Arrays.asList(responseArray)),
ResponseInterceptors.class,
capabilities);
clone.responseInterceptors = responseInterceptors.interceptors();
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"))
// interceptor lists are enriched per-element then as a whole via custom types
.filter(field -> !Objects.equals(field.getName(), "requestInterceptors"))
.filter(field -> !Objects.equals(field.getName(), "responseInterceptors"))
// 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);
}
}