Skip to content

Commit 328591b

Browse files
authored
Redesign RESTEasy client proxy support (#319)
* Remove @Provider annotation on ProblemClientResponseFilter so it no longer gets auto-registered for RESTEasy clients * Introduce ResteasyProblemSupport class for creating problem-enabled RESTEasy proxy clients Fixes #251. Closes #252.
1 parent 0bcb393 commit 328591b

11 files changed

Lines changed: 189 additions & 95 deletions

File tree

belgif-rest-problem-it/belgif-rest-problem-jakarta-ee-it/src/main/java/io/github/belgif/rest/problem/FrontendImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.github.belgif.rest.problem.api.Input;
2525
import io.github.belgif.rest.problem.api.Problem;
2626
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemSupport;
27+
import io.github.belgif.rest.problem.ee.resteasy.client.ResteasyProblemSupport;
2728
import io.github.belgif.rest.problem.i18n.I18N;
2829
import io.github.belgif.rest.problem.it.model.Bean;
2930
import io.github.belgif.rest.problem.it.model.ChildModel;
@@ -48,8 +49,8 @@ public class FrontendImpl implements Frontend {
4849
private final jakarta.ws.rs.client.Client resteasyClient =
4950
ProblemSupport.enable(new ResteasyClientBuilderImpl().build());
5051

51-
private final Backend resteasyProxyClient = ProblemSupport.enable(
52-
new ResteasyClientBuilderImpl().build().target(BASE_URI).proxy(Backend.class));
52+
private final Backend resteasyProxyClient = ResteasyProblemSupport.proxy(
53+
new ResteasyClientBuilderImpl().build().target(BASE_URI), Backend.class);
5354

5455
@EJB
5556
private EJBService ejb;

belgif-rest-problem-it/belgif-rest-problem-java-ee-it/src/main/java/io/github/belgif/rest/problem/FrontendImpl.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.github.belgif.rest.problem.api.Input;
2525
import io.github.belgif.rest.problem.api.Problem;
2626
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemSupport;
27+
import io.github.belgif.rest.problem.ee.resteasy.client.ResteasyProblemSupport;
2728
import io.github.belgif.rest.problem.i18n.I18N;
2829
import io.github.belgif.rest.problem.it.model.Bean;
2930
import io.github.belgif.rest.problem.it.model.ChildModel;
@@ -47,8 +48,8 @@ public class FrontendImpl implements Frontend {
4748

4849
private final javax.ws.rs.client.Client resteasyClient = ProblemSupport.enable(new ResteasyClientBuilder().build());
4950

50-
private final Backend resteasyProxyClient = ProblemSupport.enable(
51-
new ResteasyClientBuilder().build().target(BASE_URI).proxy(Backend.class));
51+
private final Backend resteasyProxyClient =
52+
ResteasyProblemSupport.proxy(new ResteasyClientBuilder().build().target(BASE_URI), Backend.class);
5253

5354
@EJB
5455
private EJBService ejb;

belgif-rest-problem-java-ee-client/src/main/java/io/github/belgif/rest/problem/ee/jaxrs/client/ProblemClientResponseFilter.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import javax.ws.rs.client.ClientResponseContext;
88
import javax.ws.rs.client.ClientResponseFilter;
99
import javax.ws.rs.core.MediaType;
10-
import javax.ws.rs.ext.Provider;
1110

1211
import org.slf4j.Logger;
1312
import org.slf4j.LoggerFactory;
@@ -25,7 +24,6 @@
2524
* @see ClientResponseFilter
2625
* @see ProblemWrapper
2726
*/
28-
@Provider
2927
public class ProblemClientResponseFilter implements ClientResponseFilter {
3028

3129
private static final Logger LOGGER = LoggerFactory.getLogger(ProblemClientResponseFilter.class);

belgif-rest-problem-java-ee-client/src/main/java/io/github/belgif/rest/problem/ee/jaxrs/client/ProblemSupport.java

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,6 @@ public static Client enable(Client client) {
4141
return createProxy(Client.class, new ClientInvocationHandler(client));
4242
}
4343

44-
/**
45-
* Enable problem support on the given client (e.g. RESTEasy proxy client).
46-
*
47-
* <p>
48-
* This causes the client to throw Problem exceptions instead of ProblemWrapper exceptions.
49-
* </p>
50-
*
51-
* @param client the client
52-
* @param <T> the client type
53-
* @return the problem-enabled client
54-
*/
55-
@SuppressWarnings("unchecked")
56-
public static <T> T enable(T client) {
57-
return (T) Proxy.newProxyInstance(ProblemSupport.class.getClassLoader(),
58-
client.getClass().getInterfaces(), new ProxyInvocationHandler(client));
59-
}
60-
6144
/**
6245
* JDK Dynamic Proxy InvocationHandler for JAX-RS Client.
6346
*/
@@ -109,31 +92,6 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
10992
}
11093
}
11194

112-
/**
113-
* JDK Dynamic Proxy InvocationHandler for proxy clients.
114-
*/
115-
static final class ProxyInvocationHandler implements InvocationHandler {
116-
117-
private final Object target;
118-
119-
ProxyInvocationHandler(Object target) {
120-
this.target = target;
121-
}
122-
123-
@Override
124-
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
125-
try {
126-
return method.invoke(target, args);
127-
} catch (InvocationTargetException e) {
128-
if (e.getTargetException() instanceof ProblemWrapper) {
129-
throw ((ProblemWrapper) e.getTargetException()).getProblem();
130-
}
131-
throw e.getTargetException();
132-
}
133-
}
134-
135-
}
136-
13795
@SuppressWarnings("unchecked")
13896
private static <T> T createProxy(Class<T> intf, InvocationHandler invocationHandler) {
13997
return (T) Proxy.newProxyInstance(ProblemSupport.class.getClassLoader(),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.github.belgif.rest.problem.ee.resteasy.client;
2+
3+
import java.lang.reflect.InvocationHandler;
4+
import java.lang.reflect.InvocationTargetException;
5+
import java.lang.reflect.Method;
6+
import java.lang.reflect.Proxy;
7+
8+
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
9+
10+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemClientResponseFilter;
11+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemSupport;
12+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemWrapper;
13+
14+
/**
15+
* Utility class for enabling problem support on RESTEasy Clients.
16+
*/
17+
public class ResteasyProblemSupport {
18+
19+
private ResteasyProblemSupport() {
20+
throw new IllegalStateException("Utility class");
21+
}
22+
23+
/**
24+
* Create a problem-enabled RESTEasy proxy client.
25+
*
26+
* @param target the ResteasyWebTarget
27+
* @param proxyInterface the service interface
28+
* @return the problem-enabled RESTEasy proxy client
29+
* @param <T> the service interface type
30+
*/
31+
@SuppressWarnings("unchecked")
32+
public static <T> T proxy(ResteasyWebTarget target, Class<T> proxyInterface) {
33+
if (!target.getConfiguration().isRegistered(ProblemClientResponseFilter.class)) {
34+
target.register(ProblemClientResponseFilter.class);
35+
}
36+
T client = target.proxy(proxyInterface);
37+
return (T) Proxy.newProxyInstance(ProblemSupport.class.getClassLoader(),
38+
client.getClass().getInterfaces(), new ProxyInvocationHandler(client));
39+
}
40+
41+
/**
42+
* JDK Dynamic Proxy InvocationHandler for RESTEasy proxy clients.
43+
*/
44+
static final class ProxyInvocationHandler implements InvocationHandler {
45+
46+
private final Object target;
47+
48+
ProxyInvocationHandler(Object target) {
49+
this.target = target;
50+
}
51+
52+
@Override
53+
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
54+
try {
55+
return method.invoke(target, args);
56+
} catch (InvocationTargetException e) {
57+
if (e.getTargetException() instanceof ProblemWrapper) {
58+
throw ((ProblemWrapper) e.getTargetException()).getProblem();
59+
}
60+
throw e.getTargetException();
61+
}
62+
}
63+
64+
}
65+
66+
}

belgif-rest-problem-java-ee-client/src/test/java/io/github/belgif/rest/problem/ee/jaxrs/client/ProblemSupportTest.java

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020
import org.mockito.Mock;
2121
import org.mockito.junit.jupiter.MockitoExtension;
2222

23-
import io.github.belgif.rest.problem.BadGatewayProblem;
2423
import io.github.belgif.rest.problem.BadRequestProblem;
2524
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemSupport.ClientInvocationHandler;
26-
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemSupport.ProxyInvocationHandler;
2725

2826
@ExtendWith(MockitoExtension.class)
2927
class ProblemSupportTest {
@@ -34,12 +32,8 @@ class ProblemSupportTest {
3432
@Mock(strictness = Mock.Strictness.LENIENT)
3533
private Configuration configuration;
3634

37-
interface Service {
38-
String test();
39-
}
40-
4135
@BeforeEach
42-
public void mockConfiguration() {
36+
void mockConfiguration() {
4337
when(client.getConfiguration()).thenReturn(configuration);
4438
when(configuration.isRegistered(ProblemClientResponseFilter.class)).thenReturn(true);
4539
}
@@ -217,42 +211,6 @@ void exceptionCreatingProxiedReturnType() {
217211
.withMessage("oops");
218212
}
219213

220-
@Test
221-
void unwrapProblemWrapperInProxyClient() {
222-
Service service = mock(Service.class);
223-
Service result = ProblemSupport.enable(service);
224-
assertThat(Proxy.isProxyClass(result.getClass())).isTrue();
225-
assertThat(Proxy.getInvocationHandler(result)).isInstanceOf(ProxyInvocationHandler.class);
226-
227-
doThrow(new ProblemWrapper(new BadGatewayProblem())).when(service).test();
228-
229-
assertThatExceptionOfType(BadGatewayProblem.class).isThrownBy(result::test);
230-
}
231-
232-
@Test
233-
void normalResponseFromProxyClient() {
234-
Service service = mock(Service.class);
235-
Service result = ProblemSupport.enable(service);
236-
assertThat(Proxy.isProxyClass(result.getClass())).isTrue();
237-
assertThat(Proxy.getInvocationHandler(result)).isInstanceOf(ProxyInvocationHandler.class);
238-
239-
when(service.test()).thenReturn("OK");
240-
241-
assertThat(result.test()).isEqualTo("OK");
242-
}
243-
244-
@Test
245-
void otherExceptionInProxyClient() {
246-
Service service = mock(Service.class);
247-
Service result = ProblemSupport.enable(service);
248-
assertThat(Proxy.isProxyClass(result.getClass())).isTrue();
249-
assertThat(Proxy.getInvocationHandler(result)).isInstanceOf(ProxyInvocationHandler.class);
250-
251-
doThrow(new RuntimeException("other")).when(service).test();
252-
253-
assertThatRuntimeException().isThrownBy(result::test).withMessage("other");
254-
}
255-
256214
@Test
257215
void configurable() {
258216
Client result = ProblemSupport.enable(client);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package io.github.belgif.rest.problem.ee.resteasy.client;
2+
3+
import static org.assertj.core.api.Assertions.*;
4+
import static org.mockito.Mockito.*;
5+
6+
import java.lang.reflect.Proxy;
7+
8+
import javax.ws.rs.core.Configuration;
9+
10+
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.ExtendWith;
14+
import org.mockito.Mock;
15+
import org.mockito.junit.jupiter.MockitoExtension;
16+
17+
import io.github.belgif.rest.problem.BadGatewayProblem;
18+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemClientResponseFilter;
19+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemWrapper;
20+
21+
@ExtendWith(MockitoExtension.class)
22+
class ResteasyProblemSupportTest {
23+
24+
@Mock
25+
private ResteasyWebTarget target;
26+
27+
@Mock
28+
private Configuration configuration;
29+
30+
@Mock
31+
private Service serviceMock;
32+
33+
interface Service {
34+
String test();
35+
}
36+
37+
@BeforeEach
38+
void mockConfiguration() {
39+
when(target.getConfiguration()).thenReturn(configuration);
40+
when(configuration.isRegistered(ProblemClientResponseFilter.class)).thenReturn(true);
41+
}
42+
43+
@Test
44+
void proxy() {
45+
when(configuration.isRegistered(ProblemClientResponseFilter.class)).thenReturn(false);
46+
when(target.proxy(Service.class)).thenReturn(serviceMock);
47+
Service service = ResteasyProblemSupport.proxy(target, Service.class);
48+
49+
assertThat(Proxy.isProxyClass(service.getClass())).isTrue();
50+
assertThat(Proxy.getInvocationHandler(service))
51+
.isInstanceOf(ResteasyProblemSupport.ProxyInvocationHandler.class);
52+
53+
verify(target).register(ProblemClientResponseFilter.class);
54+
}
55+
56+
@Test
57+
void normalResponseFromProxyClient() {
58+
when(target.proxy(Service.class)).thenReturn(serviceMock);
59+
Service service = ResteasyProblemSupport.proxy(target, Service.class);
60+
61+
when(serviceMock.test()).thenReturn("OK");
62+
63+
assertThat(service.test()).isEqualTo("OK");
64+
}
65+
66+
@Test
67+
void unwrapProblemWrapperInProxyClient() {
68+
when(target.proxy(Service.class)).thenReturn(serviceMock);
69+
Service service = ResteasyProblemSupport.proxy(target, Service.class);
70+
71+
doThrow(new ProblemWrapper(new BadGatewayProblem())).when(serviceMock).test();
72+
73+
assertThatExceptionOfType(BadGatewayProblem.class).isThrownBy(service::test);
74+
}
75+
76+
@Test
77+
void otherExceptionInProxyClient() {
78+
when(target.proxy(Service.class)).thenReturn(serviceMock);
79+
Service service = ResteasyProblemSupport.proxy(target, Service.class);
80+
81+
doThrow(new RuntimeException("other")).when(serviceMock).test();
82+
83+
assertThatRuntimeException().isThrownBy(service::test).withMessage("other");
84+
}
85+
86+
}

belgif-rest-problem-quarkus-client-deployment/src/main/java/io/github/belgif/rest/problem/quarkus/deployment/client/ProblemExtensionClientProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.eclipse.microprofile.rest.client.spi.RestClientListener;
1313

1414
import io.github.belgif.rest.problem.ee.jaxrs.ProblemObjectMapperContextResolver;
15+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemClientResponseFilter;
1516
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemResponseExceptionMapper;
1617
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemRestClientListener;
1718
import io.quarkus.deployment.annotations.BuildStep;
@@ -55,7 +56,8 @@ ReflectiveClassBuildItem registerRestClientListenerForReflection() {
5556
ProblemRestClientListener.class.getName(),
5657
ProblemRestClientListener.ClientProblemObjectMapperContextResolver.class.getName(),
5758
ProblemObjectMapperContextResolver.class.getName(),
58-
ProblemResponseExceptionMapper.class.getName())
59+
ProblemResponseExceptionMapper.class.getName(),
60+
ProblemClientResponseFilter.class.getName())
5961
.constructors().methods().fields()
6062
.build();
6163
}

belgif-rest-problem-quarkus-client-deployment/src/test/java/io/github/belgif/rest/problem/quarkus/deployment/client/ProblemExtensionClientProcessorTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.junit.jupiter.api.Test;
1515

1616
import io.github.belgif.rest.problem.ee.jaxrs.ProblemObjectMapperContextResolver;
17+
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemClientResponseFilter;
1718
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemResponseExceptionMapper;
1819
import io.github.belgif.rest.problem.ee.jaxrs.client.ProblemRestClientListener;
1920
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
@@ -56,7 +57,8 @@ void registerRestClientListenerForReflection() {
5657
ProblemRestClientListener.class.getName(),
5758
ProblemRestClientListener.ClientProblemObjectMapperContextResolver.class.getName(),
5859
ProblemObjectMapperContextResolver.class.getName(),
59-
ProblemResponseExceptionMapper.class.getName());
60+
ProblemResponseExceptionMapper.class.getName(),
61+
ProblemClientResponseFilter.class.getName());
6062
assertThat(result.isConstructors()).isTrue();
6163
assertThat(result.isMethods()).isTrue();
6264
assertThat(result.isFields()).isTrue();

0 commit comments

Comments
 (0)