Skip to content

Commit eb0daad

Browse files
committed
Add Invocation.body() and apply review cleanups (chain reduce, no-store regex, annotation cache)
Signed-off-by: Marvin Froeder <velo.br@gmail.com>
1 parent 2c6c0d4 commit eb0daad

6 files changed

Lines changed: 133 additions & 122 deletions

File tree

core/src/main/java/feign/AsynchronousMethodHandler.java

Lines changed: 5 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import feign.interceptor.Invocation;
2525
import feign.interceptor.MethodInterceptor;
2626
import java.io.IOException;
27-
import java.util.Collections;
2827
import java.util.List;
2928
import java.util.Optional;
3029
import java.util.concurrent.CompletableFuture;
@@ -79,11 +78,11 @@ public Object invoke(Object[] argv) throws Throwable {
7978
throw e.getCause();
8079
}
8180
};
82-
MethodInterceptor.Chain chain = endOfChain;
83-
List<MethodInterceptor> methodInterceptors = methodHandlerConfiguration.getMethodInterceptors();
84-
for (int i = methodInterceptors.size() - 1; i >= 0; i--) {
85-
chain = methodInterceptors.get(i).apply(chain);
86-
}
81+
MethodInterceptor.Chain chain =
82+
methodHandlerConfiguration.getMethodInterceptors().stream()
83+
.reduce(MethodInterceptor::andThen)
84+
.map(interceptor -> interceptor.apply(endOfChain))
85+
.orElse(endOfChain);
8786
return chain.next(invocation);
8887
}
8988

@@ -269,31 +268,6 @@ static class Factory<C> implements MethodHandler.Factory<C> {
269268
private final RequestTemplateFactoryResolver requestTemplateFactoryResolver;
270269
private final Options options;
271270

272-
Factory(
273-
AsyncClient<C> client,
274-
Retryer retryer,
275-
List<RequestInterceptor> requestInterceptors,
276-
AsyncResponseHandler responseHandler,
277-
Logger logger,
278-
Logger.Level logLevel,
279-
ExceptionPropagationPolicy propagationPolicy,
280-
MethodInfoResolver methodInfoResolver,
281-
RequestTemplateFactoryResolver requestTemplateFactoryResolver,
282-
Options options) {
283-
this(
284-
client,
285-
retryer,
286-
requestInterceptors,
287-
Collections.emptyList(),
288-
responseHandler,
289-
logger,
290-
logLevel,
291-
propagationPolicy,
292-
methodInfoResolver,
293-
requestTemplateFactoryResolver,
294-
options);
295-
}
296-
297271
Factory(
298272
AsyncClient<C> client,
299273
Retryer retryer,

core/src/main/java/feign/SynchronousMethodHandler.java

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import feign.interceptor.Invocation;
2525
import feign.interceptor.MethodInterceptor;
2626
import java.io.IOException;
27-
import java.util.Collections;
2827
import java.util.List;
2928
import java.util.concurrent.TimeUnit;
3029
import java.util.stream.Stream;
@@ -58,11 +57,11 @@ public Object invoke(Object[] argv) throws Throwable {
5857
argv);
5958

6059
MethodInterceptor.Chain endOfChain = inv -> runWithRetry(inv, options);
61-
MethodInterceptor.Chain chain = endOfChain;
62-
List<MethodInterceptor> methodInterceptors = methodHandlerConfiguration.getMethodInterceptors();
63-
for (int i = methodInterceptors.size() - 1; i >= 0; i--) {
64-
chain = methodInterceptors.get(i).apply(chain);
65-
}
60+
MethodInterceptor.Chain chain =
61+
methodHandlerConfiguration.getMethodInterceptors().stream()
62+
.reduce(MethodInterceptor::andThen)
63+
.map(interceptor -> interceptor.apply(endOfChain))
64+
.orElse(endOfChain);
6665
return chain.next(invocation);
6766
}
6867

@@ -174,29 +173,6 @@ static class Factory implements MethodHandler.Factory<Object> {
174173
private final RequestTemplateFactoryResolver requestTemplateFactoryResolver;
175174
private final Options options;
176175

177-
Factory(
178-
Client client,
179-
Retryer retryer,
180-
List<RequestInterceptor> requestInterceptors,
181-
ResponseHandler responseHandler,
182-
Logger logger,
183-
Logger.Level logLevel,
184-
ExceptionPropagationPolicy propagationPolicy,
185-
RequestTemplateFactoryResolver requestTemplateFactoryResolver,
186-
Options options) {
187-
this(
188-
client,
189-
retryer,
190-
requestInterceptors,
191-
Collections.emptyList(),
192-
responseHandler,
193-
logger,
194-
logLevel,
195-
propagationPolicy,
196-
requestTemplateFactoryResolver,
197-
options);
198-
}
199-
200176
Factory(
201177
Client client,
202178
Retryer retryer,

core/src/main/java/feign/interceptor/Invocation.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ public Object[] arguments() {
7373
return arguments;
7474
}
7575

76+
/**
77+
* Returns the typed argument that will be serialized as the request body, or {@code null} if the
78+
* method has no body parameter or it was passed as {@code null}.
79+
*/
80+
public Object body() {
81+
Integer bodyIndex = methodMetadata.bodyIndex();
82+
if (bodyIndex == null || arguments == null || bodyIndex >= arguments.length) {
83+
return null;
84+
}
85+
return arguments[bodyIndex];
86+
}
87+
7688
/**
7789
* The HTTP {@link Response} captured after the framework's terminal chain step. Returns {@code
7890
* null} when the chain has not yet executed the HTTP call, or when the call failed before a

http-cache/src/main/java/feign/cache/HttpCacheInterceptor.java

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
import feign.FeignException;
2020
import feign.RequestTemplate;
2121
import feign.Response;
22+
import feign.Util;
2223
import feign.interceptor.Invocation;
2324
import feign.interceptor.MethodInterceptor;
2425
import java.time.Instant;
2526
import java.util.Collection;
2627
import java.util.Map;
2728
import java.util.function.Function;
29+
import java.util.regex.Pattern;
2830

2931
/**
3032
* A {@link MethodInterceptor} that adds conditional revalidation headers ({@code If-None-Match} /
@@ -43,6 +45,8 @@
4345
@Experimental
4446
public final class HttpCacheInterceptor implements MethodInterceptor {
4547

48+
private static final Pattern NO_STORE = Pattern.compile("(?i)\\bno-store\\b");
49+
4650
private final HttpCacheStore store;
4751
private final Function<Invocation, String> keyFn;
4852
private final Function<RequestTemplate, Boolean> cacheable;
@@ -78,27 +82,10 @@ public Object intercept(Invocation invocation, Chain chain) throws Throwable {
7882
}
7983
String key = keyFn.apply(invocation);
8084
CachedEntry hit = store.get(key);
81-
if (hit != null) {
82-
if (hit.etag() != null) {
83-
template.header("If-None-Match", hit.etag());
84-
}
85-
if (hit.lastModified() != null) {
86-
template.header("If-Modified-Since", hit.lastModified());
87-
}
88-
}
85+
addConditionalHeaders(template, hit);
8986
try {
9087
Object result = chain.next(invocation);
91-
Response response = invocation.response();
92-
if (response != null
93-
&& response.status() >= 200
94-
&& response.status() < 300
95-
&& !hasNoStore(response)) {
96-
String etag = firstHeader(response.headers(), "ETag");
97-
String lastMod = firstHeader(response.headers(), "Last-Modified");
98-
if (etag != null || lastMod != null) {
99-
store.put(key, new CachedEntry(result, etag, lastMod, Instant.now()));
100-
}
101-
}
88+
maybeStore(key, result, invocation.response());
10289
return result;
10390
} catch (FeignException e) {
10491
if (e.status() == 304 && hit != null) {
@@ -108,6 +95,38 @@ public Object intercept(Invocation invocation, Chain chain) throws Throwable {
10895
}
10996
}
11097

98+
private static void addConditionalHeaders(RequestTemplate template, CachedEntry hit) {
99+
if (hit == null) {
100+
return;
101+
}
102+
if (hit.etag() != null) {
103+
template.header("If-None-Match", hit.etag());
104+
}
105+
if (hit.lastModified() != null) {
106+
template.header("If-Modified-Since", hit.lastModified());
107+
}
108+
}
109+
110+
private void maybeStore(String key, Object result, Response response) {
111+
if (response == null) {
112+
return;
113+
}
114+
int status = response.status();
115+
if (status < 200 || status >= 300) {
116+
return;
117+
}
118+
Map<String, Collection<String>> headers = response.headers();
119+
if (containsNoStore(headers)) {
120+
return;
121+
}
122+
String etag = firstHeader(headers, "ETag");
123+
String lastMod = firstHeader(headers, "Last-Modified");
124+
if (etag == null && lastMod == null) {
125+
return;
126+
}
127+
store.put(key, new CachedEntry(result, etag, lastMod, Instant.now()));
128+
}
129+
111130
private static String defaultKey(Invocation invocation) {
112131
RequestTemplate template = invocation.requestTemplate();
113132
return invocation.methodMetadata().configKey() + "|" + template.method() + " " + template.url();
@@ -118,13 +137,9 @@ private static Boolean defaultCacheable(RequestTemplate template) {
118137
return "GET".equalsIgnoreCase(method) || "HEAD".equalsIgnoreCase(method);
119138
}
120139

121-
private static boolean hasNoStore(Response response) {
122-
Collection<String> values = response.headers().get("Cache-Control");
123-
if (values == null) {
124-
return false;
125-
}
126-
for (String value : values) {
127-
if (value != null && value.toLowerCase().contains("no-store")) {
140+
private static boolean containsNoStore(Map<String, Collection<String>> headers) {
141+
for (String value : Util.valuesOrEmpty(headers, "Cache-Control")) {
142+
if (value != null && NO_STORE.matcher(value).find()) {
128143
return true;
129144
}
130145
}

validation-jakarta/src/main/java/feign/validation/BeanValidationMethodInterceptor.java

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package feign.validation;
1717

1818
import feign.Experimental;
19-
import feign.MethodMetadata;
2019
import feign.interceptor.Invocation;
2120
import feign.interceptor.MethodInterceptor;
2221
import jakarta.validation.ConstraintViolation;
@@ -25,8 +24,12 @@
2524
import jakarta.validation.Validation;
2625
import jakarta.validation.Validator;
2726
import java.lang.annotation.Annotation;
27+
import java.lang.reflect.Method;
28+
import java.util.Collections;
2829
import java.util.LinkedHashSet;
2930
import java.util.Set;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.ConcurrentMap;
3033

3134
/**
3235
* A {@link MethodInterceptor} that validates the method's body argument and any {@link Valid}
@@ -46,6 +49,8 @@ public final class BeanValidationMethodInterceptor implements MethodInterceptor
4649

4750
private final Validator validator;
4851
private final Class<?>[] groups;
52+
private final ConcurrentMap<Method, Annotation[][]> parameterAnnotationsCache =
53+
new ConcurrentHashMap<>();
4954

5055
public BeanValidationMethodInterceptor(Validator validator, Class<?>... groups) {
5156
this.validator = validator;
@@ -62,27 +67,39 @@ public static BeanValidationMethodInterceptor usingDefaultFactory() {
6267

6368
@Override
6469
public Object intercept(Invocation invocation, Chain chain) throws Throwable {
70+
Set<ConstraintViolation<Object>> violations = collectViolations(invocation);
71+
if (!violations.isEmpty()) {
72+
throw new ConstraintViolationException(violations);
73+
}
74+
return chain.next(invocation);
75+
}
76+
77+
private Set<ConstraintViolation<Object>> collectViolations(Invocation invocation) {
6578
Object[] arguments = invocation.arguments();
66-
if (arguments != null && arguments.length > 0) {
67-
Set<ConstraintViolation<Object>> violations = new LinkedHashSet<>();
68-
MethodMetadata metadata = invocation.methodMetadata();
69-
Annotation[][] parameterAnnotations = invocation.method().getParameterAnnotations();
70-
Integer bodyIndex = metadata.bodyIndex();
71-
for (int i = 0; i < arguments.length; i++) {
72-
Object argument = arguments[i];
73-
if (argument == null) {
74-
continue;
75-
}
76-
boolean isBody = bodyIndex != null && bodyIndex == i;
77-
if (isBody || hasValidAnnotation(parameterAnnotations[i])) {
78-
violations.addAll(validator.validate(argument, groups));
79-
}
79+
if (arguments == null || arguments.length == 0) {
80+
return Collections.emptySet();
81+
}
82+
Set<ConstraintViolation<Object>> violations = new LinkedHashSet<>();
83+
Object body = invocation.body();
84+
if (body != null) {
85+
violations.addAll(validator.validate(body, groups));
86+
}
87+
Integer bodyIndex = invocation.methodMetadata().bodyIndex();
88+
Annotation[][] parameterAnnotations =
89+
parameterAnnotationsCache.computeIfAbsent(
90+
invocation.method(), Method::getParameterAnnotations);
91+
for (int i = 0; i < arguments.length; i++) {
92+
if (arguments[i] == null) {
93+
continue;
8094
}
81-
if (!violations.isEmpty()) {
82-
throw new ConstraintViolationException(violations);
95+
if (bodyIndex != null && bodyIndex == i) {
96+
continue;
97+
}
98+
if (hasValidAnnotation(parameterAnnotations[i])) {
99+
violations.addAll(validator.validate(arguments[i], groups));
83100
}
84101
}
85-
return chain.next(invocation);
102+
return violations;
86103
}
87104

88105
private static boolean hasValidAnnotation(Annotation[] annotations) {

0 commit comments

Comments
 (0)