Skip to content

Commit

Permalink
Copy RestClient interface annotations to generated class
Browse files Browse the repository at this point in the history
When RestClient Reactive generates the implementation class, it does
not copy interface-level annotations to the generated class (though
it does copy annotations from RestClient methods). This means that
interceptor bindings declared on the interface itself don't work.

With this commit, RestClient Reactive will copy all annotations
declared on the RestClient interface (with the exception of
a scope annotation and some MicroProfile RestClient annotations)
to the generated class. This includes interceptor bindings, too.
  • Loading branch information
Ladicek committed Jan 17, 2022
1 parent 3c9d92d commit 0e4e88a
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.quarkus.rest.client.reactive.deployment;

import static io.quarkus.arc.processor.MethodDescriptors.MAP_PUT;
import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_HEADER_PARAM;
import static io.quarkus.rest.client.reactive.deployment.DotNames.CLIENT_HEADER_PARAMS;
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_CLIENT_HEADERS;
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDER;
import static io.quarkus.rest.client.reactive.deployment.DotNames.REGISTER_PROVIDERS;
Expand Down Expand Up @@ -45,6 +47,7 @@
import org.jboss.resteasy.reactive.common.processor.ResteasyReactiveDotNames;

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
Expand Down Expand Up @@ -94,6 +97,14 @@ class RestClientReactiveProcessor {
private static final String DISABLE_SMART_PRODUCES_QUARKUS = "quarkus.rest-client.disable-smart-produces";
private static final String KOTLIN_INTERFACE_DEFAULT_IMPL_SUFFIX = "$DefaultImpls";

private static final Set<DotName> SKIP_COPYING_ANNOTATIONS_TO_GENERATED_CLASS = Set.of(
REGISTER_REST_CLIENT,
REGISTER_PROVIDER,
REGISTER_PROVIDERS,
CLIENT_HEADER_PARAM,
CLIENT_HEADER_PARAMS,
REGISTER_CLIENT_HEADERS);

@BuildStep
void announceFeature(BuildProducer<FeatureBuildItem> features) {
features.produce(new FeatureBuildItem(Feature.REST_CLIENT_REACTIVE));
Expand Down Expand Up @@ -331,6 +342,7 @@ AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedInd
@Record(ExecutionTime.STATIC_INIT)
void addRestClientBeans(Capabilities capabilities,
CombinedIndexBuildItem combinedIndexBuildItem,
CustomScopeAnnotationsBuildItem scopes,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
RestClientReactiveConfig clientConfig,
RestClientRecorder recorder) {
Expand Down Expand Up @@ -389,6 +401,19 @@ void addRestClientBeans(Capabilities capabilities,
classCreator.addAnnotation(Typed.class.getName(), RetentionPolicy.RUNTIME)
.addValue("value", new org.objectweb.asm.Type[] { asmType });

for (AnnotationInstance annotation : jaxrsInterface.classAnnotations()) {
if (SKIP_COPYING_ANNOTATIONS_TO_GENERATED_CLASS.contains(annotation.name())) {
continue;
}

// scope annotation is added to the generated class already, see above
if (scopes.isScopeIn(Set.of(annotation))) {
continue;
}

classCreator.addAnnotation(annotation);
}

// CONSTRUCTOR:

MethodCreator constructor = classCreator
Expand Down Expand Up @@ -525,7 +550,7 @@ private ScopeInfo computeDefaultScope(Capabilities capabilities, Config config,
if (scopeToUse == null) {
log.warnf("Unsupported default scope {} provided for rest client {}. Defaulting to {}",
scope, restClientInterface.name(), globalDefaultScope.getName());
scopeToUse = BuiltinScope.DEPENDENT.getInfo();
scopeToUse = globalDefaultScope.getInfo();
}
} else {
final Set<DotName> annotations = restClientInterface.annotations().keySet();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import org.jboss.logging.Logger;

import io.quarkus.arc.NoClassInterceptors;

public abstract class RestClientReactiveCDIWrapperBase<T extends Closeable> implements Closeable {
private static final Logger log = Logger.getLogger(RestClientReactiveCDIWrapperBase.class);

Expand All @@ -17,11 +19,13 @@ public RestClientReactiveCDIWrapperBase(Class<T> jaxrsInterface, String baseUriF
}

@Override
@NoClassInterceptors
public void close() throws IOException {
delegate.close();
}

@PreDestroy
@NoClassInterceptors
public void destroy() {
try {
close();
Expand All @@ -32,6 +36,7 @@ public void destroy() {

// used by generated code
@SuppressWarnings("unused")
@NoClassInterceptors
public Object getDelegate() {
return delegate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ void forEachMethod(ClassInfo clazz, Consumer<MethodInfo> action) {
// synthetic methods can't be intercepted
continue;
}
if (annotationStore.hasAnnotation(method, io.quarkus.arc.processor.DotNames.NO_CLASS_INTERCEPTORS)
&& !annotationStore.hasAnyAnnotation(method, DotNames.FT_ANNOTATIONS)) {
// methods annotated @NoClassInterceptors and not annotated with an interceptor binding are not intercepted
continue;
}

action.accept(method);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public class ClientCallingResource {
@RestClient
FaultToleranceClient faultToleranceClient;

@RestClient
FaultToleranceOnInterfaceClient faultToleranceOnInterfaceClient;

@Inject
InMemorySpanExporter inMemorySpanExporter;

Expand Down Expand Up @@ -139,6 +142,16 @@ void init(@Observes Router router) {
router.route("/call-with-fault-tolerance").blockingHandler(rc -> {
rc.end(faultToleranceClient.helloWithFallback());
});

router.route("/call-with-fault-tolerance-on-interface").blockingHandler(rc -> {
String exception = "";
try {
faultToleranceOnInterfaceClient.hello();
} catch (Exception e) {
exception = e.getClass().getSimpleName();
}
rc.end(exception);
});
}

private Future<Void> success(RoutingContext rc, String body) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.it.rest.client.main;

import java.time.temporal.ChronoUnit;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/unprocessable")
@RegisterRestClient(configKey = "w-fault-tolerance")
@CircuitBreaker(requestVolumeThreshold = 2, delay = 1, delayUnit = ChronoUnit.MINUTES)
public interface FaultToleranceOnInterfaceClient {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.TEXT_PLAIN)
String hello();
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ void shouldInterceptDefaultMethod() {
.body(equalTo("Hello fallback!"));
}

@Test
void shouldApplyInterfaceLevelInterceptorBinding() {
for (int i = 0; i < 2; i++) {
RestAssured.with().body(baseUrl).post("/call-with-fault-tolerance-on-interface")
.then()
.statusCode(200)
.body(equalTo("ClientWebApplicationException"));
}

RestAssured.with().body(baseUrl).post("/call-with-fault-tolerance-on-interface")
.then()
.statusCode(200)
.body(equalTo("CircuitBreakerOpenException"));
}

@Test
void shouldCreateClientSpans() {
// Reset captured traces
Expand Down

0 comments on commit 0e4e88a

Please sign in to comment.