Skip to content

Commit

Permalink
Allow to exclude JAX-RS classes with annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
essobedo committed Feb 20, 2021
1 parent b042fca commit fba16b4
Show file tree
Hide file tree
Showing 10 changed files with 596 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeCon
}

@BuildStep
void ifBuildProperty(BeanArchiveIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
void ifBuildProperty(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
buildProperty(IF_BUILD_PROPERTY, new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String stringValue, String expectedStringValue) {
Expand All @@ -82,7 +82,7 @@ public Boolean apply(String stringValue, String expectedStringValue) {
}

@BuildStep
void unlessBuildProperty(BeanArchiveIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
void unlessBuildProperty(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
buildProperty(UNLESS_BUILD_PROPERTY, new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String stringValue, String expectedStringValue) {
Expand All @@ -91,7 +91,7 @@ public Boolean apply(String stringValue, String expectedStringValue) {
}, index, producer);
}

void buildProperty(DotName annotationName, BiFunction<String, String, Boolean> testFun, BeanArchiveIndexBuildItem index,
void buildProperty(DotName annotationName, BiFunction<String, String, Boolean> testFun, CombinedIndexBuildItem index,
BuildProducer<BuildTimeConditionBuildItem> producer) {
Config config = ConfigProviderResolver.instance().getConfig();
Collection<AnnotationInstance> annotationInstances = index.getIndex().getAnnotations(annotationName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import javax.ws.rs.RuntimeType;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
Expand All @@ -31,12 +33,14 @@
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.resteasy.reactive.common.runtime.JaxRsSecurityConfig;
import io.quarkus.resteasy.reactive.common.runtime.ResteasyReactiveConfig;
import io.quarkus.resteasy.reactive.spi.AbstractInterceptorBuildItem;
import io.quarkus.resteasy.reactive.spi.ContainerRequestFilterBuildItem;
import io.quarkus.resteasy.reactive.spi.ContainerResponseFilterBuildItem;
Expand Down Expand Up @@ -71,9 +75,12 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index,

@BuildStep
ApplicationResultBuildItem handleApplication(CombinedIndexBuildItem combinedIndexBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
List<BuildTimeConditionBuildItem> buildTimeConditions,
ResteasyReactiveConfig config) {
ApplicationScanningResult result = ResteasyReactiveScanner
.scanForApplicationClass(combinedIndexBuildItem.getComputingIndex());
.scanForApplicationClass(combinedIndexBuildItem.getComputingIndex(),
config.buildTimeConditionAware ? getExcludedClasses(buildTimeConditions) : Collections.emptySet());
if (result.getSelectedAppClass() != null) {
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, result.getSelectedAppClass().name().toString()));
}
Expand Down Expand Up @@ -268,4 +275,16 @@ public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
}
}

/**
* @param buildTimeConditions the build time conditions from which the excluded classes are extracted.
* @return the set of classes that have been annotated with unsuccessful build time conditions.
*/
private static Set<String> getExcludedClasses(List<BuildTimeConditionBuildItem> buildTimeConditions) {
return buildTimeConditions.stream()
.filter(item -> !item.isEnabled())
.map(BuildTimeConditionBuildItem::getTarget)
.filter(target -> target.kind() == AnnotationTarget.Kind.CLASS)
.map(target -> target.asClass().toString())
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,10 @@ public class ResteasyReactiveConfig {
@ConfigItem(defaultValue = "true")
@Experimental("This flag has a high probability of going away in the future")
public boolean defaultProduces;

/**
* Allow to rely on build time conditions to enable or not the JAX-RS resource, provider and feature classes.
*/
@ConfigItem(defaultValue = "true")
public boolean buildTimeConditionAware;
}
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,7 @@ public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem
Map<DotName, String> pathInterfaces = result.getPathInterfaces();

ApplicationScanningResult appResult = applicationResultBuildItem.getResult();
Set<String> allowedClasses = appResult.getAllowedClasses();
Set<String> singletonClasses = appResult.getSingletonClasses();
boolean filterClasses = appResult.isFilterClasses();
Application application = appResult.getApplication();

Map<String, String> existingConverters = new HashMap<>();
Expand Down Expand Up @@ -382,7 +380,7 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an
serverEndpointIndexer = serverEndpointIndexerBuilder.build();

for (ClassInfo i : scannedResources.values()) {
if (filterClasses && !allowedClasses.contains(i.name().toString())) {
if (!appResult.keepClass(i.name().toString())) {
continue;
}
ResourceClass endpoints = serverEndpointIndexer.createEndpoints(i);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package io.quarkus.resteasy.reactive.server.test.simple;

import static io.restassured.RestAssured.when;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

import java.io.IOException;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.container.*;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.hamcrest.Matchers;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.profile.IfBuildProfile;
import io.quarkus.arc.profile.UnlessBuildProfile;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.arc.properties.UnlessBuildProperty;
import io.quarkus.test.QuarkusUnitTest;

/**
* The integration test for the support of build time conditions on JAX-RS resource classes.
*/
class BuildProfileTest {

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(
ResourceTest1.class, ResourceTest2.class, ResponseFilter1.class, ResponseFilter2.class,
ResponseFilter3.class, ResponseFilter4.class, ResponseFilter5.class, ResponseFilter6.class,
Feature1.class, Feature2.class, DynamicFeature1.class, DynamicFeature2.class,
ExceptionMapper1.class, ExceptionMapper2.class))
.overrideConfigKey("some.prop1", "v1")
.overrideConfigKey("some.prop2", "v2");

@DisplayName("Should access to ok of resource 1 and provide a response with the expected headers")
@Test
void should_call_ok_of_resource_1() {
when()
.get("/rt-1/ok")
.then()
.header("X-RF-1", notNullValue())
.header("X-RF-2", nullValue())
.header("X-RF-3", notNullValue())
.header("X-RF-4", nullValue())
.header("X-RF-5", notNullValue())
.header("X-RF-6", nullValue())
.body(Matchers.is("ok1"));
}

@DisplayName("Should access to ko of resource 1 and call the expected exception mapper")
@Test
void should_call_ko_of_resource_1() {
when()
.get("/rt-1/ko")
.then()
.statusCode(Response.Status.SERVICE_UNAVAILABLE.getStatusCode());
}

@DisplayName("Should access to ok of resource 1 and provide a response with the expected headers")
@Test
void should_not_call_ok_of_resource_2() {
when()
.get("/rt-2/ok")
.then()
.statusCode(Response.Status.SERVICE_UNAVAILABLE.getStatusCode());
}

@IfBuildProfile("test")
@Path("rt-1")
public static class ResourceTest1 {

@GET
@Path("ok")
public String ok() {
return "ok1";
}

@GET
@Path("ko")
public String ko() {
throw new UnsupportedOperationException();
}
}

@IfBuildProfile("foo")
@Path("rt-2")
public static class ResourceTest2 {

@GET
@Path("ok")
public String ok() {
return "ok2";
}
}

@IfBuildProperty(name = "some.prop1", stringValue = "v1") // will be enabled because the value matches
@Provider
public static class ResponseFilter1 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-1", "Value");
}
}

@IfBuildProperty(name = "some.prop1", stringValue = "v") // won't be enabled because the value doesn't match
@Provider
public static class ResponseFilter2 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-2", "Value");
}
}

@IfBuildProfile("test")
@UnlessBuildProperty(name = "some.prop2", stringValue = "v1") // will be enabled because the value doesn't match
@Provider
public static class ResponseFilter3 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-3", "Value");
}
}

@UnlessBuildProperty(name = "some.prop2", stringValue = "v2") // won't be enabled because the value matches
@Provider
public static class ResponseFilter4 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-4", "Value");
}
}

@IfBuildProfile("test")
@Provider
public static class ResponseFilter5 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-5", "Value");
}
}

@IfBuildProfile("bar")
@Provider
public static class ResponseFilter6 implements ContainerResponseFilter {

@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("X-RF-6", "Value");
}
}

@IfBuildProfile("test")
@Provider
public static class Feature1 implements Feature {

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

@UnlessBuildProfile("test")
@Provider
public static class Feature2 implements Feature {

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

@IfBuildProfile("test")
@Provider
public static class ExceptionMapper1 implements ExceptionMapper<RuntimeException> {

@Override
public Response toResponse(RuntimeException exception) {
return Response.status(Response.Status.SERVICE_UNAVAILABLE.getStatusCode()).build();
}
}

@UnlessBuildProfile("test")
@Provider
public static class ExceptionMapper2 implements ExceptionMapper<UnsupportedOperationException> {

@Override
public Response toResponse(UnsupportedOperationException exception) {
return Response.status(Response.Status.NOT_IMPLEMENTED.getStatusCode()).build();
}
}

@IfBuildProfile("test")
@Provider
public static class DynamicFeature1 implements DynamicFeature {

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
context.register(ResponseFilter5.class);
}
}

@IfBuildProfile("bar")
@Provider
public static class DynamicFeature2 implements DynamicFeature {

@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
context.register(ResponseFilter6.class);
}
}
}
Loading

0 comments on commit fba16b4

Please sign in to comment.