Skip to content

Commit 49c0333

Browse files
authored
Merge pull request quarkusio#35926 from geoand/quarkusio#35884
Fix use of multiple @ClientXXX annotations in REST Client Reactive
2 parents 8604738 + ddb6360 commit 49c0333

File tree

2 files changed

+142
-42
lines changed

2 files changed

+142
-42
lines changed

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

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import jakarta.ws.rs.Priorities;
4040
import jakarta.ws.rs.RuntimeType;
4141
import jakarta.ws.rs.core.MediaType;
42+
import jakarta.ws.rs.core.MultivaluedMap;
4243

4344
import org.eclipse.microprofile.config.Config;
4445
import org.eclipse.microprofile.config.ConfigProvider;
@@ -60,6 +61,7 @@
6061
import org.jboss.resteasy.reactive.client.spi.MissingMessageBodyReaderErrorMessageContextualizer;
6162
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;
6263
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationStore;
64+
import org.jboss.resteasy.reactive.common.util.QuarkusMultivaluedHashMap;
6365

6466
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
6567
import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
@@ -331,12 +333,15 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
331333
}
332334
}
333335

334-
Map<String, GeneratedClassResult> generatedProviders = new HashMap<>();
335-
populateClientExceptionMapperFromAnnotations(generatedClasses, reflectiveClasses, index, generatedProviders);
336-
populateClientRedirectHandlerFromAnnotations(generatedClasses, reflectiveClasses, index, generatedProviders);
336+
MultivaluedMap<String, GeneratedClassResult> generatedProviders = new QuarkusMultivaluedHashMap<>();
337+
populateClientExceptionMapperFromAnnotations(generatedClasses, reflectiveClasses, index)
338+
.forEach(generatedProviders::add);
339+
populateClientRedirectHandlerFromAnnotations(generatedClasses, reflectiveClasses, index)
340+
.forEach(generatedProviders::add);
337341
for (AnnotationToRegisterIntoClientContextBuildItem annotation : annotationsToRegisterIntoClientContext) {
338-
populateClientProviderFromAnnotations(annotation, generatedClasses, reflectiveClasses,
339-
index, generatedProviders);
342+
populateClientProviderFromAnnotations(annotation, generatedClasses, reflectiveClasses, index)
343+
.forEach(generatedProviders::add);
344+
340345
}
341346

342347
addGeneratedProviders(index, constructor, annotationsByClassName, generatedProviders);
@@ -551,77 +556,83 @@ && isImplementorOf(index, target.asClass(), RESPONSE_EXCEPTION_MAPPER, Set.of(AP
551556
}
552557
}
553558

554-
private void populateClientExceptionMapperFromAnnotations(BuildProducer<GeneratedClassBuildItem> generatedClasses,
555-
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index,
556-
Map<String, GeneratedClassResult> generatedProviders) {
559+
private Map<String, GeneratedClassResult> populateClientExceptionMapperFromAnnotations(
560+
BuildProducer<GeneratedClassBuildItem> generatedClasses,
561+
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index) {
557562

563+
var result = new HashMap<String, GeneratedClassResult>();
558564
ClientExceptionMapperHandler clientExceptionMapperHandler = new ClientExceptionMapperHandler(
559565
new GeneratedClassGizmoAdaptor(generatedClasses, true));
560566
for (AnnotationInstance instance : index.getAnnotations(CLIENT_EXCEPTION_MAPPER)) {
561-
GeneratedClassResult result = clientExceptionMapperHandler.generateResponseExceptionMapper(instance);
562-
if (result == null) {
567+
GeneratedClassResult classResult = clientExceptionMapperHandler.generateResponseExceptionMapper(instance);
568+
if (classResult == null) {
563569
continue;
564570
}
565-
if (generatedProviders.containsKey(result.interfaceName)) {
571+
if (result.containsKey(classResult.interfaceName)) {
566572
throw new IllegalStateException("Only a single instance of '" + CLIENT_EXCEPTION_MAPPER
567-
+ "' is allowed per REST Client interface. Offending class is '" + result.interfaceName + "'");
573+
+ "' is allowed per REST Client interface. Offending class is '" + classResult.interfaceName + "'");
568574
}
569-
generatedProviders.put(result.interfaceName, result);
570-
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(result.generatedClassName)
575+
result.put(classResult.interfaceName, classResult);
576+
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(classResult.generatedClassName)
571577
.serialization(false).build());
572578
}
579+
return result;
573580
}
574581

575-
private void populateClientRedirectHandlerFromAnnotations(BuildProducer<GeneratedClassBuildItem> generatedClasses,
576-
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index,
577-
Map<String, GeneratedClassResult> generatedProviders) {
582+
private Map<String, GeneratedClassResult> populateClientRedirectHandlerFromAnnotations(
583+
BuildProducer<GeneratedClassBuildItem> generatedClasses,
584+
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index) {
578585

586+
var result = new HashMap<String, GeneratedClassResult>();
579587
ClientRedirectHandler clientHandler = new ClientRedirectHandler(new GeneratedClassGizmoAdaptor(generatedClasses, true));
580588
for (AnnotationInstance instance : index.getAnnotations(CLIENT_REDIRECT_HANDLER)) {
581-
GeneratedClassResult result = clientHandler.generateResponseExceptionMapper(instance);
582-
if (result == null) {
589+
GeneratedClassResult classResult = clientHandler.generateResponseExceptionMapper(instance);
590+
if (classResult == null) {
583591
continue;
584592
}
585593

586-
GeneratedClassResult existing = generatedProviders.get(result.interfaceName);
587-
if (existing != null && existing.priority == result.priority) {
594+
GeneratedClassResult existing = result.get(classResult.interfaceName);
595+
if (existing != null && existing.priority == classResult.priority) {
588596
throw new IllegalStateException("Only a single instance of '" + CLIENT_REDIRECT_HANDLER
589597
+ "' with the same priority is allowed per REST Client interface. "
590-
+ "Offending class is '" + result.interfaceName + "'");
591-
} else if (existing == null || existing.priority < result.priority) {
592-
generatedProviders.put(result.interfaceName, result);
593-
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(result.generatedClassName)
598+
+ "Offending class is '" + classResult.interfaceName + "'");
599+
} else if (existing == null || existing.priority < classResult.priority) {
600+
result.put(classResult.interfaceName, classResult);
601+
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(classResult.generatedClassName)
594602
.serialization(false).build());
595603
}
596604
}
605+
return result;
597606
}
598607

599-
private void populateClientProviderFromAnnotations(AnnotationToRegisterIntoClientContextBuildItem annotationBuildItem,
608+
private Map<String, GeneratedClassResult> populateClientProviderFromAnnotations(
609+
AnnotationToRegisterIntoClientContextBuildItem annotationBuildItem,
600610
BuildProducer<GeneratedClassBuildItem> generatedClasses,
601-
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index,
602-
Map<String, GeneratedClassResult> generatedProviders) {
611+
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses, IndexView index) {
603612

613+
var result = new HashMap<String, GeneratedClassResult>();
604614
ClientContextResolverHandler handler = new ClientContextResolverHandler(annotationBuildItem.getAnnotation(),
605615
annotationBuildItem.getExpectedReturnType(),
606616
new GeneratedClassGizmoAdaptor(generatedClasses, true));
607617
for (AnnotationInstance instance : index.getAnnotations(annotationBuildItem.getAnnotation())) {
608-
GeneratedClassResult result = handler.generateContextResolver(instance);
609-
if (result == null) {
618+
GeneratedClassResult classResult = handler.generateContextResolver(instance);
619+
if (classResult == null) {
610620
continue;
611621
}
612-
if (generatedProviders.containsKey(result.interfaceName)) {
622+
if (result.containsKey(classResult.interfaceName)) {
613623
throw new IllegalStateException("Only a single instance of '" + annotationBuildItem.getAnnotation()
614-
+ "' is allowed per REST Client interface. Offending class is '" + result.interfaceName + "'");
624+
+ "' is allowed per REST Client interface. Offending class is '" + classResult.interfaceName + "'");
615625
}
616-
generatedProviders.put(result.interfaceName, result);
617-
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(result.generatedClassName)
626+
result.put(classResult.interfaceName, classResult);
627+
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(classResult.generatedClassName)
618628
.serialization(false).build());
619629
}
630+
return result;
620631
}
621632

622633
private void addGeneratedProviders(IndexView index, MethodCreator constructor,
623634
Map<String, List<AnnotationInstance>> annotationsByClassName,
624-
Map<String, GeneratedClassResult> generatedProviders) {
635+
Map<String, List<GeneratedClassResult>> generatedProviders) {
625636
for (Map.Entry<String, List<AnnotationInstance>> annotationsForClass : annotationsByClassName.entrySet()) {
626637
ResultHandle map = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
627638
for (AnnotationInstance value : annotationsForClass.getValue()) {
@@ -641,18 +652,24 @@ private void addGeneratedProviders(IndexView index, MethodCreator constructor,
641652
if (generatedProviders.containsKey(ifaceName)) {
642653
// remove the interface from the generated provider since it's going to be handled now
643654
// the remaining entries will be handled later
644-
GeneratedClassResult result = generatedProviders.remove(ifaceName);
645-
constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(result.generatedClassName),
646-
constructor.load(result.priority));
655+
List<GeneratedClassResult> providers = generatedProviders.remove(ifaceName);
656+
for (GeneratedClassResult classResult : providers) {
657+
constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(classResult.generatedClassName),
658+
constructor.load(classResult.priority));
659+
}
660+
647661
}
648662
addProviders(constructor, ifaceName, map);
649663
}
650664

651-
for (Map.Entry<String, GeneratedClassResult> entry : generatedProviders.entrySet()) {
665+
for (Map.Entry<String, List<GeneratedClassResult>> entry : generatedProviders.entrySet()) {
652666
ResultHandle map = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
653-
constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(entry.getValue().generatedClassName),
654-
constructor.load(entry.getValue().priority));
655-
addProviders(constructor, entry.getKey(), map);
667+
for (GeneratedClassResult classResult : entry.getValue()) {
668+
constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(classResult.generatedClassName),
669+
constructor.load(classResult.priority));
670+
addProviders(constructor, entry.getKey(), map);
671+
}
672+
656673
}
657674
}
658675

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.quarkus.rest.client.reactive.redirect;
2+
3+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4+
5+
import java.net.URI;
6+
7+
import jakarta.ws.rs.GET;
8+
import jakarta.ws.rs.Path;
9+
import jakarta.ws.rs.QueryParam;
10+
import jakarta.ws.rs.core.Response;
11+
12+
import org.eclipse.microprofile.rest.client.RestClientBuilder;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.RegisterExtension;
15+
16+
import io.quarkus.rest.client.reactive.ClientExceptionMapper;
17+
import io.quarkus.rest.client.reactive.ClientRedirectHandler;
18+
import io.quarkus.test.QuarkusUnitTest;
19+
import io.quarkus.test.common.http.TestHTTPResource;
20+
21+
public class MultipleProvidersFromAnnotationTest {
22+
23+
@RegisterExtension
24+
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
25+
.withApplicationRoot((jar) -> jar
26+
.addClasses(Client.class, Resource.class));
27+
28+
@Test
29+
void test() {
30+
Client client = RestClientBuilder.newBuilder()
31+
.baseUri(uri)
32+
.followRedirects(true)
33+
.build(Client.class);
34+
assertThatThrownBy(() -> client.call(2)).hasMessage("dummy");
35+
}
36+
37+
@TestHTTPResource
38+
URI uri;
39+
40+
@Path("test")
41+
public interface Client {
42+
43+
@GET
44+
void call(@QueryParam("redirects") Integer numberOfRedirects);
45+
46+
@ClientRedirectHandler
47+
static URI redirectFor3xx(Response response) {
48+
int status = response.getStatus();
49+
if (status > 300 && response.getStatus() < 400) {
50+
return response.getLocation();
51+
}
52+
53+
return null;
54+
}
55+
56+
@ClientExceptionMapper
57+
static RuntimeException toException(Response response) {
58+
if (response.getStatus() == 999) {
59+
throw new RuntimeException("dummy") {
60+
@Override
61+
public synchronized Throwable fillInStackTrace() {
62+
return this;
63+
}
64+
};
65+
}
66+
return null;
67+
}
68+
}
69+
70+
@Path("test")
71+
public static class Resource {
72+
73+
@GET
74+
public Response redirectedResponse(@QueryParam("redirects") Integer number) {
75+
if (number == null || 0 == number) {
76+
return Response.status(999).build();
77+
} else {
78+
return Response.status(Response.Status.FOUND).location(URI.create("/test?redirects=" + (number - 1)))
79+
.build();
80+
}
81+
}
82+
}
83+
}

0 commit comments

Comments
 (0)