Skip to content

Commit

Permalink
Rest Client Reactive: globally register providers annotated with @Pro…
Browse files Browse the repository at this point in the history
  • Loading branch information
michalszynkiewicz committed Jul 23, 2021
1 parent 8df4cbb commit dd69a26
Show file tree
Hide file tree
Showing 13 changed files with 357 additions and 10 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 @@ -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,9 +180,14 @@ 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
Expand Down Expand Up @@ -217,14 +224,42 @@ void registerProvidersFromAnnotations(CombinedIndexBuildItem indexBuildItem,
constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(AnnotationRegisteredProviders.class),
constructor.getThis());

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;
}
}

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 +273,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,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,64 @@
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 javax.ws.rs.core.Response;

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.AfterEach;
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;

public class ProviderPriorityTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(HelloResource.class,
HelloClient.class,
HelloClientWithFilter.class,
ResponseFilterLowestPrio.class,
GlobalResponseFilter.class,
GlobalResponseFilterLowPrio.class)
.addAsResource(
new StringAsset(setUrlForClass(HelloClient.class)
+ setUrlForClass(HelloClientWithFilter.class)),
"application.properties"));

@RestClient
HelloClient helloClient;

@RestClient
HelloClientWithFilter helloClientWithFilter;

@AfterEach
void cleanUp() {
GlobalResponseFilterLowPrio.skip = false;
}

@Test
void shouldApplyLocalLowestPrioFilterLast() {
Response response = helloClientWithFilter.echo("foo");
assertThat(response.getStatus()).isEqualTo(ResponseFilterLowestPrio.STATUS);
}

@Test
void shouldApplyLowPrioFilterLast() {
Response response = helloClient.echo("foo");
assertThat(response.getStatus()).isEqualTo(GlobalResponseFilterLowPrio.STATUS);
}

@Test
void shouldApplyHighPrioFilter() {
GlobalResponseFilterLowPrio.skip = true;
Response response = helloClient.echo("foo");
assertThat(response.getStatus()).isEqualTo(GlobalResponseFilter.STATUS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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.AfterEach;
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 ProviderTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(HelloResource.class, HelloClient.class, GlobalRequestFilter.class,
GlobalResponseFilter.class, GlobalRequestFilterConstrainedToServer.class)
.addAsResource(
new StringAsset(setUrlForClass(HelloClient.class)),
"application.properties"));

@RestClient
HelloClient helloClient;

@TestHTTPResource
URI baseUri;

@AfterEach
public void cleanUp() {
GlobalRequestFilter.abort = false;
}

@Test
void shouldUseGlobalRequestFilterForInjectedClient() {
GlobalRequestFilter.abort = true;
Response response = helloClient.echo("Michał");
assertThat(response.getStatus()).isEqualTo(GlobalRequestFilter.STATUS);
}

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

@Test
void shouldUseGlobalRequestFilterForBuiltClient() {
GlobalRequestFilter.abort = true;
Response response = helloClient().echo("Michał");
assertThat(response.getStatus()).isEqualTo(GlobalRequestFilter.STATUS);
}

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

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

0 comments on commit dd69a26

Please sign in to comment.