Skip to content

Commit 4455fc2

Browse files
JKomoroskivelo
andauthored
Add Optional BeanParam support for JAXRS2Contract (#1935)
* Add Optional BeanParam support for JAXRS2Contract * Fix Code Style * Commit Build Code Format Changes * Roll Back Removal of braces * Make sure jaxrs1 dependencies not avaiable on jaxrs2 * Update JAXRS2ContractWithBeanParamSupportTest.java * Always Use Enable BeanParam Support * Roll Back Test Changes --------- Co-authored-by: Marvin Froeder <velo@users.noreply.github.com>
1 parent 0cb3a0a commit 4455fc2

File tree

5 files changed

+174
-10
lines changed

5 files changed

+174
-10
lines changed

jaxrs/src/main/java/feign/jaxrs/JAXRSContract.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ protected void registerParamAnnotations() {
151151
}
152152

153153
// Not using override as the super-type's method is deprecated and will be removed.
154-
private String addTemplatedParam(String name) {
154+
// Protected so JAXRS2Contract can make use of this
155+
protected String addTemplatedParam(String name) {
155156
return String.format("{%s}", name);
156157
}
157158
}

jaxrs2/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ Links the value of the corresponding parameter to a query parameter. When invok
3535
Links the value of the corresponding parameter to a header.
3636
#### `@FormParam`
3737
Links the value of the corresponding parameter to a key passed to `Encoder.Text<Map<String, Object>>.encode()`.
38+
#### `@BeanParm`
39+
Aggregates the above supported parameter annotations under a single value object.

jaxrs2/pom.xml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
<artifactId>feign-core</artifactId>
4343
</dependency>
4444

45+
<dependency>
46+
<groupId>javax.ws.rs</groupId>
47+
<artifactId>javax.ws.rs-api</artifactId>
48+
<version>2.1.1</version>
49+
</dependency>
50+
4551
<dependency>
4652
<groupId>${project.groupId}</groupId>
4753
<artifactId>feign-jaxrs</artifactId>
@@ -53,12 +59,6 @@
5359
</exclusions>
5460
</dependency>
5561

56-
<dependency>
57-
<groupId>javax.ws.rs</groupId>
58-
<artifactId>javax.ws.rs-api</artifactId>
59-
<version>2.1.1</version>
60-
</dependency>
61-
6262
<!-- for example -->
6363
<dependency>
6464
<groupId>${project.groupId}</groupId>
@@ -77,6 +77,12 @@
7777
<artifactId>feign-jaxrs</artifactId>
7878
<type>test-jar</type>
7979
<scope>test</scope>
80+
<exclusions>
81+
<exclusion>
82+
<groupId>javax.ws.rs</groupId>
83+
<artifactId>jsr311-api</artifactId>
84+
</exclusion>
85+
</exclusions>
8086
</dependency>
8187

8288
<dependency>

jaxrs2/src/main/java/feign/jaxrs2/JAXRS2Contract.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
*/
1414
package feign.jaxrs2;
1515

16-
import javax.ws.rs.BeanParam;
16+
import feign.jaxrs.JAXRSContract;
17+
import javax.ws.rs.*;
1718
import javax.ws.rs.container.Suspended;
1819
import javax.ws.rs.core.Context;
19-
import feign.jaxrs.JAXRSContract;
20+
import java.lang.reflect.Field;
21+
import static feign.Util.checkState;
22+
import static feign.Util.emptyToNull;
2023

2124
/**
2225
* Please refer to the <a href="https://github.com/Netflix/feign/tree/master/feign-jaxrs2">Feign
@@ -31,7 +34,67 @@ public JAXRS2Contract() {
3134
// https://github.com/OpenFeign/feign/issues/669
3235
super.registerParameterAnnotation(Suspended.class, (ann, data, i) -> data.ignoreParamater(i));
3336
super.registerParameterAnnotation(Context.class, (ann, data, i) -> data.ignoreParamater(i));
34-
super.registerParameterAnnotation(BeanParam.class, (ann, data, i) -> data.ignoreParamater(i));
37+
3538
}
3639

40+
@Override
41+
protected void registerParamAnnotations() {
42+
super.registerParamAnnotations();
43+
44+
registerParameterAnnotation(BeanParam.class, (param, data, paramIndex) -> {
45+
final Field[] aggregatedParams = data.method()
46+
.getParameters()[paramIndex]
47+
.getType()
48+
.getDeclaredFields();
49+
50+
for (Field aggregatedParam : aggregatedParams) {
51+
52+
if (aggregatedParam.isAnnotationPresent(PathParam.class)) {
53+
final String name = aggregatedParam.getAnnotation(PathParam.class).value();
54+
checkState(
55+
emptyToNull(name) != null,
56+
"BeanParam parameter %s contains PathParam with empty .value() on field %s",
57+
paramIndex,
58+
aggregatedParam.getName());
59+
nameParam(data, name, paramIndex);
60+
}
61+
62+
if (aggregatedParam.isAnnotationPresent(QueryParam.class)) {
63+
final String name = aggregatedParam.getAnnotation(QueryParam.class).value();
64+
checkState(
65+
emptyToNull(name) != null,
66+
"BeanParam parameter %s contains QueryParam with empty .value() on field %s",
67+
paramIndex,
68+
aggregatedParam.getName());
69+
final String query = addTemplatedParam(name);
70+
data.template().query(name, query);
71+
nameParam(data, name, paramIndex);
72+
}
73+
74+
if (aggregatedParam.isAnnotationPresent(HeaderParam.class)) {
75+
final String name = aggregatedParam.getAnnotation(HeaderParam.class).value();
76+
checkState(
77+
emptyToNull(name) != null,
78+
"BeanParam parameter %s contains HeaderParam with empty .value() on field %s",
79+
paramIndex,
80+
aggregatedParam.getName());
81+
final String header = addTemplatedParam(name);
82+
data.template().header(name, header);
83+
nameParam(data, name, paramIndex);
84+
}
85+
86+
if (aggregatedParam.isAnnotationPresent(FormParam.class)) {
87+
final String name = aggregatedParam.getAnnotation(FormParam.class).value();
88+
checkState(
89+
emptyToNull(name) != null,
90+
"BeanParam parameter %s contains FormParam with empty .value() on field %s",
91+
paramIndex,
92+
aggregatedParam.getName());
93+
data.formParams().add(name);
94+
nameParam(data, name, paramIndex);
95+
}
96+
}
97+
});
98+
99+
}
37100
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2012-2023 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.jaxrs2;
15+
16+
import feign.MethodMetadata;
17+
import feign.jaxrs.JAXRSContract;
18+
import feign.jaxrs.JAXRSContractTest;
19+
import feign.jaxrs2.JAXRS2ContractWithBeanParamSupportTest.Jaxrs2Internals.BeanParamInput;
20+
import org.junit.Test;
21+
import javax.ws.rs.*;
22+
import javax.ws.rs.container.AsyncResponse;
23+
import javax.ws.rs.container.Suspended;
24+
import javax.ws.rs.core.Context;
25+
import javax.ws.rs.core.UriInfo;
26+
import static feign.assertj.FeignAssertions.assertThat;
27+
import static java.util.Arrays.asList;
28+
import static org.assertj.core.data.MapEntry.entry;
29+
30+
/**
31+
* Tests interfaces defined per {@link JAXRS2Contract} are interpreted into expected
32+
* {@link feign .RequestTemplate template} instances.
33+
*/
34+
public class JAXRS2ContractWithBeanParamSupportTest extends JAXRSContractTest {
35+
36+
@Override
37+
protected JAXRSContract createContract() {
38+
return new JAXRS2Contract();
39+
}
40+
41+
@Test
42+
public void injectJaxrsInternals() throws Exception {
43+
final MethodMetadata methodMetadata =
44+
parseAndValidateMetadata(Jaxrs2Internals.class, "inject", AsyncResponse.class,
45+
UriInfo.class);
46+
assertThat(methodMetadata.template())
47+
.noRequestBody();
48+
}
49+
50+
@Test
51+
public void injectBeanParam() throws Exception {
52+
final MethodMetadata methodMetadata =
53+
parseAndValidateMetadata(Jaxrs2Internals.class, "beanParameters", BeanParamInput.class);
54+
assertThat(methodMetadata.template())
55+
.noRequestBody();
56+
57+
assertThat(methodMetadata.template())
58+
.hasHeaders(entry("X-Custom-Header", asList("{X-Custom-Header}")));
59+
assertThat(methodMetadata.template())
60+
.hasQueries(entry("query", asList("{query}")));
61+
assertThat(methodMetadata.formParams())
62+
.isNotEmpty()
63+
.containsExactly("form");
64+
65+
}
66+
67+
public interface Jaxrs2Internals {
68+
@GET
69+
@Path("/")
70+
void inject(@Suspended AsyncResponse ar, @Context UriInfo info);
71+
72+
@Path("/{path}")
73+
@POST
74+
void beanParameters(@BeanParam BeanParamInput beanParam);
75+
76+
public class BeanParamInput {
77+
78+
@PathParam("path")
79+
String path;
80+
81+
@QueryParam("query")
82+
String query;
83+
84+
@FormParam("form")
85+
String form;
86+
87+
@HeaderParam("X-Custom-Header")
88+
String header;
89+
}
90+
}
91+
92+
}

0 commit comments

Comments
 (0)