Skip to content

Commit

Permalink
Rest Client Reactive: globally register providers annotated with `@Pr…
Browse files Browse the repository at this point in the history
…ovider`

fixes #18560
  • Loading branch information
michalszynkiewicz committed Jul 24, 2021
1 parent 8df4cbb commit 2b52269
Show file tree
Hide file tree
Showing 17 changed files with 464 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem;
import io.quarkus.deployment.builditem.EnableAllSecurityServicesBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
Expand All @@ -24,8 +25,11 @@ EnableAllSecurityServicesBuildItem security() {

@BuildStep(onlyIf = IsEnabled.class)
void registerProvider(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
BuildProducer<AdditionalIndexedClassesBuildItem> additionalIndexedClassesBuildItem) {
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(OidcClientRequestReactiveFilter.class));
additionalIndexedClassesBuildItem
.produce(new AdditionalIndexedClassesBuildItem(OidcClientRequestReactiveFilter.class.getName()));
reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, OidcClientRequestReactiveFilter.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import javax.ws.rs.Priorities;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
Expand All @@ -20,7 +19,6 @@
import io.quarkus.oidc.client.runtime.DisabledOidcClientException;
import io.quarkus.oidc.common.runtime.OidcConstants;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestReactiveFilter extends AbstractTokensProducer implements ResteasyReactiveClientRequestFilter {
private static final Logger LOG = Logger.getLogger(OidcClientRequestReactiveFilter.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Typed;
import javax.inject.Singleton;
import javax.ws.rs.Priorities;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.config.Config;
Expand Down Expand Up @@ -178,17 +180,23 @@ void registerHeaderFactoryBeans(CombinedIndexBuildItem index,
}

/**
* Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that
* puts all the providers registered by the @RegisterProvider annotation in a
* map using the {@link AnnotationRegisteredProviders#addProviders(String, Map)} method
* Creates an implementation of `AnnotationRegisteredProviders` class with a constructor that:
* <ul>
* <li>puts all the providers registered by the @RegisterProvider annotation in a
* map using the {@link AnnotationRegisteredProviders#addProviders(String, Map)} method</li>
* <li>registers all the provider implementations annotated with @Provider using
* {@link AnnotationRegisteredProviders#addGlobalProvider(Class, int)}</li>
* </ul>
*
*
* @param indexBuildItem index
* @param generatedBeans build producer for generated beans
*/
@BuildStep
void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
BuildProducer<GeneratedBeanBuildItem> generatedBeans,
BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
BuildProducer<UnremovableBeanBuildItem> unremovableBeans,
RestClientReactiveConfig clientConfig) {
String annotationRegisteredProvidersImpl = AnnotationRegisteredProviders.class.getName() + "Implementation";
IndexView index = indexBuildItem.getIndex();
Map<String, List<AnnotationInstance>> annotationsByClassName = new HashMap<>();
Expand Down Expand Up @@ -217,14 +225,48 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationRegisteredProviders.class),
constructor.getThis());

if (clientConfig.providerAutodiscovery) {
for (AnnotationInstance instance : index.getAnnotations(ResteasyReactiveDotNames.PROVIDER)) {
ClassInfo providerClass = instance.target().asClass();

// ignore providers annotated with `@ConstrainedTo(SERVER)`
AnnotationInstance constrainedToInstance = providerClass
.classAnnotation(ResteasyReactiveDotNames.CONSTRAINED_TO);
if (constrainedToInstance != null) {
if (RuntimeType.valueOf(constrainedToInstance.value().asEnum()) == RuntimeType.SERVER) {
continue;
}
}

if (providerClass.interfaceNames().contains(ResteasyReactiveDotNames.FEATURE)) {
continue; // features should not be automatically registered for the client, see javadoc for Feature
}

int priority = getAnnotatedPriority(index, providerClass.name().toString(), Priorities.USER);

constructor.invokeVirtualMethod(
MethodDescriptor.ofMethod(AnnotationRegisteredProviders.class, "addGlobalProvider",
void.class, Class.class,
int.class),
constructor.getThis(), constructor.loadClass(providerClass.name().toString()),
constructor.load(priority));
}
}

for (Map.Entry<String, List<AnnotationInstance>> annotationsForClass : annotationsByClassName.entrySet()) {
ResultHandle map = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class));
for (AnnotationInstance value : annotationsForClass.getValue()) {
String className = value.value().asString();
AnnotationValue priority = value.value("priority");
AnnotationValue priorityAnnotationValue = value.value("priority");
int priority;
if (priorityAnnotationValue == null) {
priority = getAnnotatedPriority(index, className, Priorities.USER);
} else {
priority = priorityAnnotationValue.asInt();
}

constructor.invokeInterfaceMethod(MAP_PUT, map, constructor.loadClass(className),
constructor.load(priority == null ? -1 : priority.asInt()));
constructor.load(priority));
}
constructor.invokeVirtualMethod(
MethodDescriptor.ofMethod(AnnotationRegisteredProviders.class, "addProviders", void.class, String.class,
Expand All @@ -238,6 +280,21 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
unremovableBeans.produce(UnremovableBeanBuildItem.beanClassNames(annotationRegisteredProvidersImpl));
}

private int getAnnotatedPriority(IndexView index, String className, int defaultPriority) {
ClassInfo providerClass = index.getClassByName(DotName.createSimple(className));
int priority = defaultPriority;
if (providerClass == null) {
log.warnv("Unindexed provider class {0}. The priority of the provider will be set to {1}. ", className,
defaultPriority);
} else {
AnnotationInstance priorityAnnoOnProvider = providerClass.classAnnotation(ResteasyReactiveDotNames.PRIORITY);
if (priorityAnnoOnProvider != null) {
priority = priorityAnnoOnProvider.value().asInt();
}
}
return priority;
}

@BuildStep
AdditionalBeanBuildItem registerProviderBeans(CombinedIndexBuildItem combinedIndex) {
IndexView index = combinedIndex.getIndex();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.quarkus.rest.client.reactive.provider;

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;

@Provider
public class GlobalFeature implements Feature {

public static boolean called;

@Override
public boolean configure(FeatureContext context) {
context.register(FeatureInstalledFilter.class);
return true;
}

public static class FeatureInstalledFilter implements ResteasyReactiveClientRequestFilter {

@Override
public void filter(ResteasyReactiveClientRequestContext requestContext) {
called = true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.rest.client.reactive.provider;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;

@Provider
public class GlobalRequestFilter implements ResteasyReactiveClientRequestFilter {
public static final int STATUS = 233;

public static boolean abort = false;

@Override
public void filter(ResteasyReactiveClientRequestContext requestContext) {
if (abort) {
requestContext.abortWith(Response.status(STATUS).build());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.rest.client.reactive.provider;

import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.RuntimeType;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter;

@Provider
@ConstrainedTo(RuntimeType.SERVER)
public class GlobalRequestFilterConstrainedToServer implements ResteasyReactiveClientRequestFilter {
@Override
public void filter(ResteasyReactiveClientRequestContext requestContext) {
throw new RuntimeException("Invoked filter that is constrained to server");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.quarkus.rest.client.reactive.provider;

import javax.annotation.Priority;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientResponseFilter;

@Provider
@Priority(20)
public class GlobalResponseFilter implements ResteasyReactiveClientResponseFilter {

public static final int STATUS = 222;

@Override
public void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext) {
if (responseContext.getStatus() != GlobalRequestFilter.STATUS) {
responseContext.setStatus(STATUS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.rest.client.reactive.provider;

import javax.annotation.Priority;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext;
import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientResponseFilter;

@Priority(10) // lower prio here means executed later
@Provider
public class GlobalResponseFilterLowPrio implements ResteasyReactiveClientResponseFilter {

public static final int STATUS = 244;
public static boolean skip = false;

@Override
public void filter(ResteasyReactiveClientRequestContext requestContext, ClientResponseContext responseContext) {
if (!skip) {
responseContext.setStatus(STATUS);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.quarkus.rest.client.reactive.provider;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

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

@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.TEXT_PLAIN)
@RegisterRestClient
public interface HelloClient {
@POST
Response echo(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.rest.client.reactive.provider;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.TEXT_PLAIN)
@RegisterProvider(ResponseFilterLowestPrio.class)
@RegisterRestClient
public interface HelloClientWithFilter {
@POST
Response echo(String name);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.quarkus.rest.client.reactive.provider;

import static io.quarkus.rest.client.reactive.RestClientTestUtil.setUrlForClass;
import static org.assertj.core.api.Assertions.assertThat;

import java.net.URI;

import javax.ws.rs.core.Response;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.rest.client.reactive.HelloResource;
import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;

public class ProviderDisabledAutodiscoveryTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(HelloResource.class, HelloClient.class, GlobalRequestFilter.class, GlobalResponseFilter.class)
.addAsResource(
new StringAsset(setUrlForClass(HelloClient.class)
+ "quarkus.rest-client-reactive.provider-autodiscovery=false"),
"application.properties"));

@RestClient
HelloClient helloClient;

@TestHTTPResource
URI baseUri;

@Test
void shouldNotUseGlobalFilterForInjectedClient() {
Response response = helloClient.echo("Michał");
assertThat(response.getStatus()).isEqualTo(200);
}

@Test
void shouldNotUseGlobalFilterForBuiltClient() {
Response response = helloClient().echo("Michał");
assertThat(response.getStatus()).isEqualTo(200);
}

private HelloClient helloClient() {
return RestClientBuilder.newBuilder()
.baseUri(baseUri)
.build(HelloClient.class);
}
}
Loading

0 comments on commit 2b52269

Please sign in to comment.