Skip to content

Commit 9d9405e

Browse files
authored
Merge pull request #48444 from geoand/#48431
Add support for sending header params using a Map in Rest Client
2 parents 2ebdf3f + afcaa33 commit 9d9405e

File tree

4 files changed

+144
-36
lines changed

4 files changed

+144
-36
lines changed

extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java

Lines changed: 92 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,14 +1153,14 @@ A more full example of generated client (with sub-resource) can is at the bottom
11531153
bodyParameterIdx = paramIdx;
11541154
} else if (param.parameterType == ParameterType.HEADER) {
11551155
Type paramType = jandexMethod.parameterType(paramIdx);
1156-
String effectiveParamTypeStr = paramType.name().toString();
1156+
Type effectiveParamType = paramType;
11571157
boolean isOptional = isOptional(paramType, index);
11581158
if (isOptional) {
1159-
effectiveParamTypeStr = DotNames.OBJECT.toString();
1159+
effectiveParamType = Type.create(Object.class);
11601160
if (paramType.kind() == PARAMETERIZED_TYPE) {
11611161
Type objectType = paramType.asParameterizedType().arguments().get(0);
11621162
if ((objectType.kind() == CLASS) || (objectType.kind() == PARAMETERIZED_TYPE)) {
1163-
effectiveParamTypeStr = objectType.name().toString();
1163+
effectiveParamType = objectType;
11641164
}
11651165
}
11661166
}
@@ -1177,14 +1177,14 @@ A more full example of generated client (with sub-resource) can is at the bottom
11771177
.createVariable(Invocation.Builder.class);
11781178
handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0));
11791179
ResultHandle headerValue = handleHeaderMethod.getMethodParam(1);
1180-
addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
1180+
addHeaderParam(jandexMethod, handleHeaderMethod, invocationBuilderRef, param.name,
11811181
isOptional
11821182
? handleHeaderMethod.invokeVirtualMethod(
11831183
MethodDescriptor.ofMethod(Optional.class, "orElse", Object.class,
11841184
Object.class),
11851185
headerValue, handleHeaderMethod.loadNull())
11861186
: headerValue,
1187-
effectiveParamTypeStr,
1187+
effectiveParamType, index,
11881188
handleHeaderMethod.getThis(),
11891189
getGenericTypeFromArray(handleHeaderMethod, methodGenericParametersField, paramIdx),
11901190
getAnnotationsFromArray(handleHeaderMethod, methodParamAnnotationsField, paramIdx));
@@ -1804,14 +1804,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
18041804
bodyParameterValue = paramValue;
18051805
} else if (param.parameterType == ParameterType.HEADER) {
18061806
Type paramType = jandexSubMethod.parameterType(subParamField.paramIndex);
1807-
String effectiveParamTypeStr = paramType.name().toString();
1807+
Type effectiveParamType = paramType;
18081808
boolean isOptional = isOptional(paramType, index);
18091809
if (isOptional) {
1810-
effectiveParamTypeStr = DotNames.OBJECT.toString();
1810+
effectiveParamType = Type.create(Object.class);
18111811
if (paramType.kind() == PARAMETERIZED_TYPE) {
18121812
Type objectType = paramType.asParameterizedType().arguments().get(0);
18131813
if ((objectType.kind() == CLASS) || (objectType.kind() == PARAMETERIZED_TYPE)) {
1814-
effectiveParamTypeStr = objectType.name().toString();
1814+
effectiveParamType = objectType;
18151815
}
18161816
}
18171817
}
@@ -1829,14 +1829,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
18291829
.createVariable(Invocation.Builder.class);
18301830
handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0));
18311831
ResultHandle headerValue = handleHeaderMethod.getMethodParam(1);
1832-
addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
1832+
addHeaderParam(jandexMethod, handleHeaderMethod, invocationBuilderRef, param.name,
18331833
isOptional
18341834
? handleHeaderMethod.invokeVirtualMethod(
18351835
MethodDescriptor.ofMethod(Optional.class, "orElse", Object.class,
18361836
Object.class),
18371837
headerValue, handleHeaderMethod.loadNull())
18381838
: headerValue,
1839-
effectiveParamTypeStr,
1839+
effectiveParamType, index,
18401840
handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()),
18411841
getGenericTypeFromArray(handleHeaderMethod, subParamField.genericsParametersField,
18421842
subParamField.paramIndex),
@@ -1949,14 +1949,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
19491949
bodyParameterValue = subMethodCreator.getMethodParam(paramIdx);
19501950
} else if (param.parameterType == ParameterType.HEADER) {
19511951
Type paramType = jandexSubMethod.parameterType(paramIdx);
1952-
String effectiveParamTypeStr = paramType.name().toString();
1952+
Type effectiveParamType = paramType;
19531953
boolean isOptional = isOptional(paramType, index);
19541954
if (isOptional) {
1955-
effectiveParamTypeStr = DotNames.OBJECT.toString();
1955+
effectiveParamType = Type.create(Object.class);
19561956
if (paramType.kind() == PARAMETERIZED_TYPE) {
19571957
Type objectType = paramType.asParameterizedType().arguments().get(0);
19581958
if ((objectType.kind() == CLASS) || (objectType.kind() == PARAMETERIZED_TYPE)) {
1959-
effectiveParamTypeStr = objectType.name().toString();
1959+
effectiveParamType = objectType;
19601960
}
19611961
}
19621962
}
@@ -1973,14 +1973,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
19731973
.createVariable(Invocation.Builder.class);
19741974
ResultHandle headerValue = handleHeaderMethod.getMethodParam(1);
19751975
handleHeaderMethod.assign(invocationBuilderRef, handleHeaderMethod.getMethodParam(0));
1976-
addHeaderParam(handleHeaderMethod, invocationBuilderRef, param.name,
1976+
addHeaderParam(jandexMethod, handleHeaderMethod, invocationBuilderRef, param.name,
19771977
isOptional
19781978
? handleHeaderMethod.invokeVirtualMethod(
19791979
MethodDescriptor.ofMethod(Optional.class, "orElse", Object.class,
19801980
Object.class),
19811981
headerValue, handleHeaderMethod.loadNull())
19821982
: headerValue,
1983-
effectiveParamTypeStr,
1983+
effectiveParamType, index,
19841984
handleHeaderMethod.readInstanceField(clientField, handleHeaderMethod.getThis()),
19851985
getGenericTypeFromArray(handleHeaderMethod, subMethodGenericParametersField, paramIdx),
19861986
getAnnotationsFromArray(handleHeaderMethod, subMethodParamAnnotationsField, paramIdx));
@@ -2997,10 +2997,10 @@ private void addSubBeanParamData(MethodInfo jandexMethod, int paramIndex, Byteco
29972997
break;
29982998
case HEADER_PARAM:
29992999
HeaderParamItem headerParam = (HeaderParamItem) item;
3000-
addHeaderParam(invoEnricher, invocationBuilder,
3000+
addHeaderParam(jandexMethod, invoEnricher, invocationBuilder,
30013001
headerParam.getHeaderName(),
30023002
headerParam.extract(invoEnricher, invoEnricher.getMethodParam(1)),
3003-
headerParam.getParamType(), invocationEnricherClient,
3003+
headerParam.getParamType(), index, invocationEnricherClient,
30043004
getGenericTypeFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()),
30053005
getAnnotationsFromParameter(invoEnricher, beanParamDescriptorField, item.fieldName()));
30063006
break;
@@ -3318,23 +3318,86 @@ private boolean isOptional(Type type, IndexView index) {
33183318
return isAssignableFrom(OPTIONAL, type.name(), index);
33193319
}
33203320

3321-
private void addHeaderParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder,
3322-
String headerName, ResultHandle headerValueHandle, String paramType, ResultHandle client,
3321+
private void addHeaderParam(MethodInfo jandexMethod, BytecodeCreator invoBuilderEnricher,
3322+
AssignableResultHandle invocationBuilder,
3323+
String headerName, ResultHandle headerValueHandle,
3324+
Type paramType, IndexView index,
3325+
ResultHandle client,
33233326
ResultHandle genericType, ResultHandle annotations) {
33243327

33253328
BytecodeCreator notNullValue = invoBuilderEnricher.ifNull(headerValueHandle).falseBranch();
33263329

3327-
headerValueHandle = notNullValue.invokeVirtualMethod(
3328-
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
3329-
Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
3330-
client, headerValueHandle,
3331-
notNullValue.loadClassFromTCCL(paramType), genericType, annotations);
3330+
if (isMap(paramType, index)) {
3331+
Map.Entry<Type, Type> resolvesTypes = resolveMapTypes(paramType, index, jandexMethod);
3332+
Type keyType = resolvesTypes.getKey();
3333+
if (!ResteasyReactiveDotNames.STRING.equals(keyType.name())) {
3334+
throw new IllegalArgumentException(
3335+
"Map parameter types must have String keys. Offending method is: " + jandexMethod);
3336+
}
33323337

3333-
notNullValue.assign(invocationBuilder,
3334-
notNullValue.invokeInterfaceMethod(
3335-
MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class,
3336-
String.class, Object.class),
3337-
invocationBuilder, notNullValue.load(headerName), headerValueHandle));
3338+
// Loop through the keys
3339+
ResultHandle keySet = notNullValue.invokeInterfaceMethod(ofMethod(Map.class, "keySet", Set.class),
3340+
headerValueHandle);
3341+
ResultHandle keysSetIterator = notNullValue.invokeInterfaceMethod(
3342+
ofMethod(Set.class, "iterator", Iterator.class), keySet);
3343+
BytecodeCreator keySetLoop = notNullValue.whileLoop(c -> iteratorHasNext(c, keysSetIterator)).block();
3344+
ResultHandle mapKey = keySetLoop.invokeInterfaceMethod(
3345+
ofMethod(Iterator.class, "next", Object.class), keysSetIterator);
3346+
// get the value and convert
3347+
ResultHandle mapValue = keySetLoop.invokeInterfaceMethod(ofMethod(Map.class, "get", Object.class, Object.class),
3348+
headerValueHandle, mapKey);
3349+
Type valueType = resolvesTypes.getValue();
3350+
String effectiveValueTypeStr = valueType.name().toString();
3351+
if (isCollection(valueType, index)) {
3352+
if (valueType.kind() == PARAMETERIZED_TYPE) {
3353+
Type componentType = valueType.asParameterizedType().arguments().get(0);
3354+
if ((componentType.kind() == CLASS) || (componentType.kind() == PARAMETERIZED_TYPE)) {
3355+
effectiveValueTypeStr = componentType.name().toString();
3356+
}
3357+
}
3358+
if (effectiveValueTypeStr == null) {
3359+
effectiveValueTypeStr = DotNames.OBJECT.toString();
3360+
}
3361+
ForEachLoop mapValueForLoop = keySetLoop.forEach(mapValue);
3362+
BytecodeCreator mapValueCreator = mapValueForLoop.block();
3363+
3364+
mapValue = mapValueCreator.invokeVirtualMethod(
3365+
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
3366+
Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
3367+
client, mapValueForLoop.element(),
3368+
mapValueCreator.loadClassFromTCCL(effectiveValueTypeStr), genericType, annotations);
3369+
3370+
mapValueCreator.assign(invocationBuilder,
3371+
mapValueCreator.invokeInterfaceMethod(
3372+
MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class,
3373+
String.class, Object.class),
3374+
invocationBuilder, mapKey, mapValue));
3375+
} else {
3376+
mapValue = keySetLoop.invokeVirtualMethod(
3377+
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
3378+
Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
3379+
client, mapValue,
3380+
keySetLoop.loadClassFromTCCL(valueType.name().toString()), genericType, annotations);
3381+
3382+
keySetLoop.assign(invocationBuilder,
3383+
keySetLoop.invokeInterfaceMethod(
3384+
MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class,
3385+
String.class, Object.class),
3386+
invocationBuilder, mapKey, mapValue));
3387+
}
3388+
} else {
3389+
headerValueHandle = notNullValue.invokeVirtualMethod(
3390+
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class,
3391+
Object.class, Class.class, java.lang.reflect.Type.class, Annotation[].class),
3392+
client, headerValueHandle,
3393+
notNullValue.loadClassFromTCCL(paramType.name().toString()), genericType, annotations);
3394+
3395+
notNullValue.assign(invocationBuilder,
3396+
notNullValue.invokeInterfaceMethod(
3397+
MethodDescriptor.ofMethod(Invocation.Builder.class, "header", Invocation.Builder.class,
3398+
String.class, Object.class),
3399+
invocationBuilder, notNullValue.load(headerName), headerValueHandle));
3400+
}
33383401
}
33393402

33403403
private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle methodTarget,

extensions/resteasy-reactive/rest-client/deployment/src/test/java/io/quarkus/rest/client/reactive/headers/HeaderTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.ArrayList;
77
import java.util.Collection;
88
import java.util.List;
9+
import java.util.Map;
910
import java.util.Set;
1011
import java.util.SortedSet;
1112
import java.util.TreeSet;
@@ -17,9 +18,11 @@
1718
import jakarta.ws.rs.Path;
1819
import jakarta.ws.rs.core.Context;
1920
import jakarta.ws.rs.core.HttpHeaders;
21+
import jakarta.ws.rs.core.MultivaluedMap;
2022

2123
import org.eclipse.microprofile.rest.client.RestClientBuilder;
2224
import org.jboss.resteasy.reactive.RestHeader;
25+
import org.jboss.resteasy.reactive.common.util.MultivaluedTreeMap;
2326
import org.junit.jupiter.api.Test;
2427
import org.junit.jupiter.api.extension.RegisterExtension;
2528

@@ -56,6 +59,21 @@ void testHeadersWithCollections() {
5659
assertThat(client.headersSet(new TreeSet(List.of("a", "b")), new TreeSet(List.of("c", "d")))).isEqualTo(expected);
5760
}
5861

62+
@Test
63+
void testHeadersMap() {
64+
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
65+
assertThat(client.headersMap(Map.of("client-a", "A", "client-b", "B"))).contains("client-a=A").contains("client-b=B");
66+
}
67+
68+
@Test
69+
void testHeadersMultiMap() {
70+
Client client = RestClientBuilder.newBuilder().baseUri(baseUri).build(Client.class);
71+
MultivaluedTreeMap<String, Integer> headers = new MultivaluedTreeMap<>();
72+
headers.addAll("client-a", 1, 2, 3);
73+
headers.addAll("client-b", 4, 5);
74+
assertThat(client.headersMultiMap(headers)).contains("client-a=1,2,3").contains("client-b=4,5");
75+
}
76+
5977
@Path("/")
6078
@ApplicationScoped
6179
public static class Resource {
@@ -86,6 +104,23 @@ public String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader So
86104
return joiningCollections(foo, header);
87105
}
88106

107+
@GET
108+
@Path("/headers-map")
109+
public String headersMap(HttpHeaders headers) {
110+
StringBuilder sb = new StringBuilder();
111+
boolean isFirst = true;
112+
for (var entry : headers.getRequestHeaders().entrySet().stream().filter(e -> e.getKey().startsWith("client-"))
113+
.collect(Collectors.toSet())) {
114+
if (!isFirst) {
115+
sb.append("/");
116+
} else {
117+
isFirst = false;
118+
}
119+
sb.append(entry.getKey()).append("=").append(String.join(",", entry.getValue()));
120+
}
121+
return sb.toString();
122+
}
123+
89124
private String joiningCollections(Collection... collections) {
90125
List<String> allHeaders = new ArrayList<>();
91126
for (Collection collection : collections) {
@@ -111,6 +146,14 @@ public interface Client {
111146
@GET
112147
@Path("/headers-sorted-set")
113148
String headersSortedSet(@HeaderParam("foo") SortedSet foo, @RestHeader SortedSet header);
149+
150+
@GET
151+
@Path("/headers-map")
152+
String headersMap(@RestHeader Map<String, String> headers);
153+
154+
@GET
155+
@Path("/headers-map")
156+
String headersMultiMap(@RestHeader MultivaluedMap<String, Integer> headers);
114157
}
115158

116159
public interface SubClient {

0 commit comments

Comments
 (0)