Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REST Client Reactive - param converter support #22396

Merged
merged 2 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.BiFunction;

import javax.ws.rs.RuntimeType;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.ext.ParamConverterProvider;

import org.jboss.resteasy.reactive.client.impl.ClientProxies;
import org.jboss.resteasy.reactive.client.impl.ClientSerialisers;
Expand Down Expand Up @@ -37,7 +39,8 @@ public static GenericTypeMapping getGenericTypeMapping() {
return genericTypeMapping;
}

public void setupClientProxies(Map<String, RuntimeValue<Function<WebTarget, ?>>> clientImplementations,
public void setupClientProxies(
Map<String, RuntimeValue<BiFunction<WebTarget, List<ParamConverterProvider>, ?>>> clientImplementations,
Map<String, String> failures) {
clientProxies = createClientImpls(clientImplementations, failures);
}
Expand All @@ -49,10 +52,12 @@ public Serialisers createSerializers() {
return s;
}

private ClientProxies createClientImpls(Map<String, RuntimeValue<Function<WebTarget, ?>>> clientImplementations,
private ClientProxies createClientImpls(
Map<String, RuntimeValue<BiFunction<WebTarget, List<ParamConverterProvider>, ?>>> clientImplementations,
Map<String, String> failureMessages) {
Map<Class<?>, Function<WebTarget, ?>> map = new HashMap<>();
for (Map.Entry<String, RuntimeValue<Function<WebTarget, ?>>> entry : clientImplementations.entrySet()) {
Map<Class<?>, BiFunction<WebTarget, List<ParamConverterProvider>, ?>> map = new HashMap<>();
for (Map.Entry<String, RuntimeValue<BiFunction<WebTarget, List<ParamConverterProvider>, ?>>> entry : clientImplementations
.entrySet()) {
map.put(loadClass(entry.getKey()), entry.getValue().getValue());
}
Map<Class<?>, String> failures = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.quarkus.jaxrs.client.reactive.runtime;

import java.io.Closeable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;

public abstract class RestClientBase implements Closeable {
private final List<ParamConverterProvider> paramConverterProviders;
private final Map<Class<?>, ParamConverterProvider> providerForClass = new ConcurrentHashMap<>();

public RestClientBase(List<ParamConverterProvider> providers) {
this.paramConverterProviders = providers;
}

@SuppressWarnings("unused") // used by generated code
public <T> Object[] convertParamArray(T[] value, Class<T> type) {
ParamConverter<T> converter = getConverter(type, null, null);

if (converter == null) {
return value;
} else {
Object[] result = new Object[value.length];

for (int i = 0; i < value.length; i++) {
result[i] = converter.toString(value[i]);
}
return result;
}
}

@SuppressWarnings("unused") // used by generated code
public <T> Object convertParam(T value, Class<T> type) {
ParamConverter<T> converter = getConverter(type, null, null);
if (converter != null) {
return converter.toString(value);
} else {
return value;
}
}

private <T> ParamConverter<T> getConverter(Class<T> type, Type genericType, Annotation[] annotations) {
ParamConverterProvider converterProvider = providerForClass.get(type);

if (converterProvider == null) {
for (ParamConverterProvider provider : paramConverterProviders) {
ParamConverter<T> converter = provider.getConverter(type, null, null);
if (converter != null) {
providerForClass.put(type, provider);
return converter;
}
}
providerForClass.put(type, NO_PROVIDER);
} else if (converterProvider != NO_PROVIDER) {
return converterProvider.getConverter(type, null, null);
}
return null;
}

private static final ParamConverterProvider NO_PROVIDER = new ParamConverterProvider() {
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
return null;
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package io.quarkus.rest.client.reactive.converter;

import static org.assertj.core.api.Assertions.assertThat;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;

import javax.ws.rs.BeanParam;
import javax.ws.rs.CookieParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
import javax.ws.rs.ext.ParamConverterProvider;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;

public class ParamConverterProviderTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest();

@TestHTTPResource
URI baseUri;

@Test
void shouldConvertPathParam() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri)
.build(Client.class);
assertThat(client.get(Param.FIRST)).isEqualTo("1");
assertThat(client.sub().get(Param.SECOND)).isEqualTo("2");

Bean bean = new Bean();
bean.param = Param.FIRST;
assertThat(client.get(bean)).isEqualTo("1");
}

@Test
void shouldConvertQueryParams() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri)
.build(Client.class);
assertThat(client.getWithQuery(Param.FIRST)).isEqualTo("1");
assertThat(client.sub().getWithQuery(Param.SECOND)).isEqualTo("2");

Bean bean = new Bean();
bean.param = Param.SECOND;
bean.queryParam = Param.FIRST;
assertThat(client.getWithQuery(bean)).isEqualTo("1");
}

@Test
void shouldConvertHeaderParams() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri)
.build(Client.class);
assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1");
assertThat(client.sub().getWithHeader(Param.SECOND)).isEqualTo("2");

Bean bean = new Bean();
bean.param = Param.SECOND;
bean.queryParam = Param.SECOND;
bean.headerParam = Param.FIRST;
assertThat(client.getWithHeader(bean)).isEqualTo("1");
}

@Test
void shouldConvertCookieParams() {
Client client = RestClientBuilder.newBuilder().baseUri(baseUri)
.build(Client.class);
assertThat(client.getWithHeader(Param.FIRST)).isEqualTo("1");
assertThat(client.sub().getWithCookie(Param.SECOND)).isEqualTo("2");

Bean bean = new Bean();
bean.param = Param.SECOND;
bean.queryParam = Param.SECOND;
bean.headerParam = Param.SECOND;
bean.cookieParam = Param.FIRST;
assertThat(client.getWithCookie(bean)).isEqualTo("1");
}

@Path("/echo")
@RegisterProvider(ParamConverter.class)
interface Client {
@Path("/sub")
SubClient sub();

@GET
@Path("/param/{param}")
String get(@PathParam("param") Param param);

@GET
@Path("/param/{param}")
String get(@BeanParam Bean beanParam);

@GET
@Path("/query")
String getWithQuery(@QueryParam("param") Param param);

@GET
@Path("/query")
String getWithQuery(@BeanParam Bean beanParam);

@GET
@Path("/header")
String getWithHeader(@HeaderParam("param") Param param);

@GET
@Path("/header")
String getWithHeader(@BeanParam Bean beanParam);

@GET
@Path("/cookie")
String getWithCookie(@HeaderParam("cookie-param") Param param);

@GET
@Path("/cookie")
String getWithCookie(@BeanParam Bean beanParam);
}

interface SubClient {
@GET
@Path("/param/{param}")
String get(@PathParam("param") Param param);

@GET
@Path("/query")
String getWithQuery(@QueryParam("param") Param param);

@GET
@Path("/header")
String getWithHeader(@HeaderParam("param") Param param);

@GET
@Path("cookie")
String getWithCookie(@CookieParam("cookie-param") Param param);
}

public static class Bean {
@PathParam("param")
public Param param;
@QueryParam("param")
public Param queryParam;
@HeaderParam("param")
public Param headerParam;
@CookieParam("cookie-param")
public Param cookieParam;
}

enum Param {
FIRST,
SECOND
}

public static class ParamConverter implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
public <T> javax.ws.rs.ext.ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
Annotation[] annotations) {
if (rawType == Param.class) {
return (javax.ws.rs.ext.ParamConverter<T>) new javax.ws.rs.ext.ParamConverter<Param>() {
@Override
public Param fromString(String value) {
return null;
}

@Override
public String toString(Param value) {
if (value == null) {
return null;
}
switch (value) {
case FIRST:
return "1";
case SECOND:
return "2";
default:
return "unexpected";
}
}
};
}
return null;
}
}

@Path("/echo")
public static class EchoEndpoint {
@Path("/param/{param}")
@GET
public String echoPath(@PathParam("param") String param) {
return param;
}

@Path("/sub/param/{param}")
@GET
public String echoSubPath(@PathParam("param") String param) {
return param;
}

@GET
@Path("/query")
public String get(@QueryParam("param") String param) {
return param;
}

@Path("/sub/query")
@GET
public String getSub(@QueryParam("param") String param) {
return param;
}

@GET
@Path("/header")
public String getHeader(@HeaderParam("param") String param) {
return param;
}

@Path("/sub/header")
@GET
public String getSubHeader(@HeaderParam("param") String param) {
return param;
}

@GET
@Path("/cookie")
public String getCookie(@CookieParam("cookie-param") String param) {
return param;
}

@Path("/sub/cookie")
@GET
public String getSubCookie(@CookieParam("cookie-param") String param) {
return param;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.net.ssl.SSLContext;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.Configuration;
import javax.ws.rs.ext.ParamConverterProvider;

import org.eclipse.microprofile.config.ConfigProvider;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
Expand Down Expand Up @@ -46,6 +47,7 @@ public class RestClientBuilderImpl implements RestClientBuilder {
private final ClientBuilderImpl clientBuilder = (ClientBuilderImpl) new ClientBuilderImpl()
.withConfig(new ConfigurationImpl(RuntimeType.CLIENT));
private final List<ResponseExceptionMapper<?>> exceptionMappers = new ArrayList<>();
private final List<ParamConverterProvider> paramConverterProviders = new ArrayList<>();

private URI uri;
private boolean followRedirects;
Expand Down Expand Up @@ -221,7 +223,8 @@ public RestClientBuilder baseUri(URI uri) {
}

private void registerMpSpecificProvider(Class<?> componentClass) {
if (ResponseExceptionMapper.class.isAssignableFrom(componentClass)) {
if (ResponseExceptionMapper.class.isAssignableFrom(componentClass)
|| ParamConverterProvider.class.isAssignableFrom(componentClass)) {
try {
registerMpSpecificProvider(componentClass.getDeclaredConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
Expand All @@ -235,6 +238,9 @@ private void registerMpSpecificProvider(Object component) {
if (component instanceof ResponseExceptionMapper) {
exceptionMappers.add((ResponseExceptionMapper<?>) component);
}
if (component instanceof ParamConverterProvider) {
paramConverterProviders.add((ParamConverterProvider) component);
}
}

@Override
Expand Down Expand Up @@ -287,6 +293,7 @@ public <T> T build(Class<T> aClass) throws IllegalStateException, RestClientDefi

ClientImpl client = clientBuilder.build();
WebTargetImpl target = (WebTargetImpl) client.target(uri);
target.setParamConverterProviders(paramConverterProviders);
try {
return target.proxy(aClass);
} catch (InvalidRestClientDefinitionException e) {
Expand Down
Loading