Skip to content

Commit

Permalink
Move responsibility for resolving RequestTemplateFactory (#1850)
Browse files Browse the repository at this point in the history
* Extract RequestTemplateFactoryResolver

* Pass RequestTemplateFactoryResolver to MethodHandler.Factory

* Remove buildTemplateFromArgs parameter from MethodHandler.Factory

* Remove requestTemplateFactoryResolver parameter from ParseHandlersByName

Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
  • Loading branch information
wplong11 and velo committed Nov 22, 2022
1 parent 1d1699d commit 273e5dc
Show file tree
Hide file tree
Showing 7 changed files with 301 additions and 261 deletions.
4 changes: 3 additions & 1 deletion core/src/main/java/feign/AsyncFeign.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,11 @@ public AsyncFeign<C> build() {
client, retryer, requestInterceptors,
responseHandler, logger, logLevel,
propagationPolicy, methodInfoResolver,
new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
options, decoder, errorDecoder);
final ParseHandlersByName<C> handlersByName =
new ParseHandlersByName<>(contract, encoder, queryMapEncoder, methodHandlerFactory);
new ParseHandlersByName<>(contract,
methodHandlerFactory);
final ReflectiveFeign<C> feign =
new ReflectiveFeign<>(handlersByName, invocationHandlerFactory, defaultContextSupplier);
return new AsyncFeign<>(feign);
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/java/feign/AsynchronousMethodHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ static class Factory<C> implements MethodHandler.Factory<C> {
private final Logger.Level logLevel;
private final ExceptionPropagationPolicy propagationPolicy;
private final MethodInfoResolver methodInfoResolver;
private final RequestTemplateFactoryResolver requestTemplateFactoryResolver;
private final Options options;
private final Decoder decoder;
private final ErrorDecoder errorDecoder;
Expand All @@ -255,6 +256,7 @@ static class Factory<C> implements MethodHandler.Factory<C> {
Logger logger, Logger.Level logLevel,
ExceptionPropagationPolicy propagationPolicy,
MethodInfoResolver methodInfoResolver,
RequestTemplateFactoryResolver requestTemplateFactoryResolver,
Options options,
Decoder decoder,
ErrorDecoder errorDecoder) {
Expand All @@ -266,15 +268,18 @@ static class Factory<C> implements MethodHandler.Factory<C> {
this.logLevel = checkNotNull(logLevel, "logLevel");
this.propagationPolicy = propagationPolicy;
this.methodInfoResolver = methodInfoResolver;
this.requestTemplateFactoryResolver =
checkNotNull(requestTemplateFactoryResolver, "requestTemplateFactoryResolver");
this.options = checkNotNull(options, "options");
this.errorDecoder = checkNotNull(errorDecoder, "errorDecoder");
this.decoder = checkNotNull(decoder, "decoder");
}

public MethodHandler create(Target<?> target,
MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
C requestContext) {
final RequestTemplate.Factory buildTemplateFromArgs =
requestTemplateFactoryResolver.resolve(target, md);
return new AsynchronousMethodHandler<C>(target, client, retryer, requestInterceptors,
logger, logLevel, md, buildTemplateFromArgs, options, responseHandler,
propagationPolicy, requestContext,
Expand Down
6 changes: 4 additions & 2 deletions core/src/main/java/feign/Feign.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,11 @@ public Feign build() {
dismiss404, closeAfterDecode, responseInterceptor);
MethodHandler.Factory<Object> synchronousMethodHandlerFactory =
new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors,
responseHandler, logger, logLevel, propagationPolicy, options);
responseHandler, logger, logLevel, propagationPolicy,
new RequestTemplateFactoryResolver(encoder, queryMapEncoder),
options);
ParseHandlersByName<Object> handlersByName =
new ParseHandlersByName<>(contract, encoder, queryMapEncoder,
new ParseHandlersByName<>(contract,
synchronousMethodHandlerFactory);
return new ReflectiveFeign<>(handlersByName, invocationHandlerFactory, () -> null);
}
Expand Down
1 change: 0 additions & 1 deletion core/src/main/java/feign/InvocationHandlerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ interface MethodHandler {
interface Factory<C> {
MethodHandler create(Target<?> target,
MethodMetadata md,
RequestTemplate.Factory buildTemplateFromArgs,
C requestContext);
}
}
Expand Down
256 changes: 1 addition & 255 deletions core/src/main/java/feign/ReflectiveFeign.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*/
package feign;

import static feign.Util.checkArgument;
import static feign.Util.checkNotNull;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
Expand All @@ -22,12 +21,8 @@
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import feign.InvocationHandlerFactory.MethodHandler;
import feign.Param.Expander;
import feign.codec.*;
import feign.template.UriUtils;

public class ReflectiveFeign<C> extends Feign {

Expand Down Expand Up @@ -121,19 +116,13 @@ public String toString() {
static final class ParseHandlersByName<C> {

private final Contract contract;
private final Encoder encoder;
private final QueryMapEncoder queryMapEncoder;
private final MethodHandler.Factory<C> factory;

ParseHandlersByName(
Contract contract,
Encoder encoder,
QueryMapEncoder queryMapEncoder,
MethodHandler.Factory<C> factory) {
this.contract = contract;
this.factory = factory;
this.queryMapEncoder = queryMapEncoder;
this.encoder = checkNotNull(encoder, "encoder");
}

public Map<Method, MethodHandler> apply(Target target, C requestContext) {
Expand Down Expand Up @@ -169,250 +158,7 @@ private MethodHandler createMethodHandler(final Target<?> target,
};
}

BuildTemplateByResolvingArgs buildTemplate = getBuildTemplate(target, md);
return factory.create(target, md, buildTemplate, requestContext);
}

private BuildTemplateByResolvingArgs getBuildTemplate(Target target, MethodMetadata md) {
if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {
return new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else if (md.bodyIndex() != null || md.alwaysEncodeBody()) {
return new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);
} else {
return new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);
}
}
}

private static class BuildTemplateByResolvingArgs implements RequestTemplate.Factory {

private final QueryMapEncoder queryMapEncoder;

protected final MethodMetadata metadata;
protected final Target<?> target;
private final Map<Integer, Expander> indexToExpander = new LinkedHashMap<Integer, Expander>();

private BuildTemplateByResolvingArgs(MethodMetadata metadata, QueryMapEncoder queryMapEncoder,
Target target) {
this.metadata = metadata;
this.target = target;
this.queryMapEncoder = queryMapEncoder;
if (metadata.indexToExpander() != null) {
indexToExpander.putAll(metadata.indexToExpander());
return;
}
if (metadata.indexToExpanderClass().isEmpty()) {
return;
}
for (Entry<Integer, Class<? extends Expander>> indexToExpanderClass : metadata
.indexToExpanderClass().entrySet()) {
try {
indexToExpander
.put(indexToExpanderClass.getKey(), indexToExpanderClass.getValue().newInstance());
} catch (InstantiationException e) {
throw new IllegalStateException(e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}

@Override
public RequestTemplate create(Object[] argv) {
RequestTemplate mutable = RequestTemplate.from(metadata.template());
mutable.feignTarget(target);
if (metadata.urlIndex() != null) {
int urlIndex = metadata.urlIndex();
checkArgument(argv[urlIndex] != null, "URI parameter %s was null", urlIndex);
mutable.target(String.valueOf(argv[urlIndex]));
}
Map<String, Object> varBuilder = new LinkedHashMap<String, Object>();
for (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {
int i = entry.getKey();
Object value = argv[entry.getKey()];
if (value != null) { // Null values are skipped.
if (indexToExpander.containsKey(i)) {
value = expandElements(indexToExpander.get(i), value);
}
for (String name : entry.getValue()) {
varBuilder.put(name, value);
}
}
}

RequestTemplate template = resolve(argv, mutable, varBuilder);
if (metadata.queryMapIndex() != null) {
// add query map parameters after initial resolve so that they take
// precedence over any predefined values
Object value = argv[metadata.queryMapIndex()];
Map<String, Object> queryMap = toQueryMap(value);
template = addQueryMapQueryParameters(queryMap, template);
}

if (metadata.headerMapIndex() != null) {
// add header map parameters for a resolution of the user pojo object
Object value = argv[metadata.headerMapIndex()];
Map<String, Object> headerMap = toQueryMap(value);
template = addHeaderMapHeaders(headerMap, template);
}

return template;
}

private Map<String, Object> toQueryMap(Object value) {
if (value instanceof Map) {
return (Map<String, Object>) value;
}
try {
return queryMapEncoder.encode(value);
} catch (EncodeException e) {
throw new IllegalStateException(e);
}
}

private Object expandElements(Expander expander, Object value) {
if (value instanceof Iterable) {
return expandIterable(expander, (Iterable) value);
}
return expander.expand(value);
}

private List<String> expandIterable(Expander expander, Iterable value) {
List<String> values = new ArrayList<String>();
for (Object element : value) {
if (element != null) {
values.add(expander.expand(element));
}
}
return values;
}

@SuppressWarnings("unchecked")
private RequestTemplate addHeaderMapHeaders(Map<String, Object> headerMap,
RequestTemplate mutable) {
for (Entry<String, Object> currEntry : headerMap.entrySet()) {
Collection<String> values = new ArrayList<String>();

Object currValue = currEntry.getValue();
if (currValue instanceof Iterable<?>) {
Iterator<?> iter = ((Iterable<?>) currValue).iterator();
while (iter.hasNext()) {
Object nextObject = iter.next();
values.add(nextObject == null ? null : nextObject.toString());
}
} else {
values.add(currValue == null ? null : currValue.toString());
}

mutable.header(currEntry.getKey(), values);
}
return mutable;
}

@SuppressWarnings("unchecked")
private RequestTemplate addQueryMapQueryParameters(Map<String, Object> queryMap,
RequestTemplate mutable) {
for (Entry<String, Object> currEntry : queryMap.entrySet()) {
Collection<String> values = new ArrayList<String>();

Object currValue = currEntry.getValue();
if (currValue instanceof Iterable<?>) {
Iterator<?> iter = ((Iterable<?>) currValue).iterator();
while (iter.hasNext()) {
Object nextObject = iter.next();
values.add(nextObject == null ? null : UriUtils.encode(nextObject.toString()));
}
} else if (currValue instanceof Object[]) {
for (Object value : (Object[]) currValue) {
values.add(value == null ? null : UriUtils.encode(value.toString()));
}
} else {
if (currValue != null) {
values.add(UriUtils.encode(currValue.toString()));
}
}

if (values.size() > 0) {
mutable.query(UriUtils.encode(currEntry.getKey()), values);
}
}
return mutable;
}

protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
return mutable.resolve(variables);
}
}

private static class BuildFormEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {

private final Encoder encoder;

private BuildFormEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder,
QueryMapEncoder queryMapEncoder, Target target) {
super(metadata, queryMapEncoder, target);
this.encoder = encoder;
}

@Override
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {
Map<String, Object> formVariables = new LinkedHashMap<String, Object>();
for (Entry<String, Object> entry : variables.entrySet()) {
if (metadata.formParams().contains(entry.getKey())) {
formVariables.put(entry.getKey(), entry.getValue());
}
}
try {
encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
throw new EncodeException(e.getMessage(), e);
}
return super.resolve(argv, mutable, variables);
}
}

private static class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {

private final Encoder encoder;

private BuildEncodedTemplateFromArgs(MethodMetadata metadata, Encoder encoder,
QueryMapEncoder queryMapEncoder, Target target) {
super(metadata, queryMapEncoder, target);
this.encoder = encoder;
}

@Override
protected RequestTemplate resolve(Object[] argv,
RequestTemplate mutable,
Map<String, Object> variables) {

boolean alwaysEncodeBody = mutable.methodMetadata().alwaysEncodeBody();

Object body = null;
if (!alwaysEncodeBody) {
body = argv[metadata.bodyIndex()];
checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());
}

try {
if (alwaysEncodeBody) {
body = argv == null ? new Object[0] : argv;
encoder.encode(body, Object[].class, mutable);
} else {
encoder.encode(body, metadata.bodyType(), mutable);
}
} catch (EncodeException e) {
throw e;
} catch (RuntimeException e) {
throw new EncodeException(e.getMessage(), e);
}
return super.resolve(argv, mutable, variables);
return factory.create(target, md, requestContext);
}
}

Expand Down

0 comments on commit 273e5dc

Please sign in to comment.