Skip to content

Commit

Permalink
Merge pull request #15215 from essobedo/15214/exclude-jaxrs-classes
Browse files Browse the repository at this point in the history
Allow to exclude JAX-RS classes with annotations
  • Loading branch information
gsmet authored Feb 24, 2021
2 parents a623735 + c03a357 commit faa48d7
Show file tree
Hide file tree
Showing 13 changed files with 746 additions and 28 deletions.
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/rest-json.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,28 @@ If set to `true` (default) then a *single instance* of a resource class is creat
If set to `false` then a *new instance* of the resource class is created per each request.
An explicit CDI scope annotation (`@RequestScoped`, `@ApplicationScoped`, etc.) always overrides the default behavior and specifies the lifecycle of resource instances.

== Include/Exclude JAX-RS classes with build time conditions

Quarkus enables the inclusion or exclusion of JAX-RS Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans.
Thus, the various JAX-RS classes can be annotated with profile conditions (`@io.quarkus.arc.profile.IfBuildProfile` or `@io.quarkus.arc.profile.UnlessBuildProfile`) and/or with property conditions (`io.quarkus.arc.properties.IfBuildProperty` or `io.quarkus.arc.properties.UnlessBuildProperty`) to indicate to Quarkus at build time under which conditions these JAX-RS classes should be included.

In the following example, Quarkus includes the endpoint `sayHello` if and only if the build profile `app1` has been enabled.

[source,java]
----
@IfBuildProfile("app1")
public class ResourceForApp1Only {
@GET
@Path("sayHello")
public String sayHello() {
return "hello";
}
}
----

Please note that if a JAX-RS Application has been detected and the method `getClasses()` and/or `getSingletons()` has/have been overridden, Quarkus will ignore the build time conditions and consider only what has been defined in the JAX-RS Application.

== Conclusion

Creating JSON REST services with Quarkus is easy as it relies on proven and well known technologies.
Expand Down
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/resteasy-reactive.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1790,3 +1790,25 @@ Or plain text:
<
< {"name":"roquefort"}
----

== Include/Exclude JAX-RS classes with build time conditions

Quarkus enables the inclusion or exclusion of JAX-RS Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans.
Thus, the various JAX-RS classes can be annotated with profile conditions (`@io.quarkus.arc.profile.IfBuildProfile` or `@io.quarkus.arc.profile.UnlessBuildProfile`) and/or with property conditions (`io.quarkus.arc.properties.IfBuildProperty` or `io.quarkus.arc.properties.UnlessBuildProperty`) to indicate to Quarkus at build time under which conditions these JAX-RS classes should be included.

In the following example, Quarkus includes the endpoint `sayHello` if and only if the build profile `app1` has been enabled.

[source,java]
----
@IfBuildProfile("app1")
public class ResourceForApp1Only {
@GET
@Path("sayHello")
public String sayHello() {
return "hello";
}
}
----

Please note that if a JAX-RS Application has been detected and the method `getClasses()` and/or `getSingletons()` has/have been overridden, Quarkus will ignore the build time conditions and consider only what has been defined in the JAX-RS Application.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import org.eclipse.microprofile.config.Config;
Expand All @@ -15,6 +16,7 @@
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;

Expand Down Expand Up @@ -42,7 +44,8 @@ public class BuildTimeEnabledProcessor {
private static final DotName UNLESS_BUILD_PROPERTY = DotName.createSimple(UnlessBuildProperty.class.getName());

@BuildStep
void ifBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
void ifBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer,
BuildProducer<PreAdditionalBeanBuildTimeConditionBuildItem> producerPreAdditionalBean) {
Collection<AnnotationInstance> annotationInstances = index.getIndex().getAnnotations(IF_BUILD_PROFILE);
for (AnnotationInstance instance : annotationInstances) {
String profileOnInstance = instance.value().asString();
Expand All @@ -53,11 +56,13 @@ void ifBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditi
LOGGER.debug("Disabling " + instance.target() + " since the profile value does not match the active profile.");
}
producer.produce(new BuildTimeConditionBuildItem(instance.target(), enabled));
producerPreAdditionalBean.produce(new PreAdditionalBeanBuildTimeConditionBuildItem(instance.target(), enabled));
}
}

@BuildStep
void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer,
BuildProducer<PreAdditionalBeanBuildTimeConditionBuildItem> producerPreAdditionalBean) {
Collection<AnnotationInstance> annotationInstances = index.getIndex().getAnnotations(UNLESS_BUILD_PROFILE);
for (AnnotationInstance instance : annotationInstances) {
String profileOnInstance = instance.value().asString();
Expand All @@ -68,6 +73,7 @@ void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeCon
LOGGER.debug("Disabling " + instance.target() + " since the profile value matches the active profile.");
}
producer.produce(new BuildTimeConditionBuildItem(instance.target(), enabled));
producerPreAdditionalBean.produce(new PreAdditionalBeanBuildTimeConditionBuildItem(instance.target(), enabled));
}
}

Expand All @@ -78,7 +84,12 @@ void ifBuildProperty(BeanArchiveIndexBuildItem index, BuildProducer<BuildTimeCon
public Boolean apply(String stringValue, String expectedStringValue) {
return stringValue.equals(expectedStringValue);
}
}, index, producer);
}, index.getIndex(), new BiConsumer<AnnotationTarget, Boolean>() {
@Override
public void accept(AnnotationTarget target, Boolean enabled) {
producer.produce(new BuildTimeConditionBuildItem(target, enabled));
}
});
}

@BuildStep
Expand All @@ -88,13 +99,50 @@ void unlessBuildProperty(BeanArchiveIndexBuildItem index, BuildProducer<BuildTim
public Boolean apply(String stringValue, String expectedStringValue) {
return !stringValue.equals(expectedStringValue);
}
}, index, producer);
}, index.getIndex(), new BiConsumer<AnnotationTarget, Boolean>() {
@Override
public void accept(AnnotationTarget target, Boolean enabled) {
producer.produce(new BuildTimeConditionBuildItem(target, enabled));
}
});
}

@BuildStep
void ifBuildPropertyPreAdditionalBean(CombinedIndexBuildItem index,
BuildProducer<PreAdditionalBeanBuildTimeConditionBuildItem> producer) {
buildProperty(IF_BUILD_PROPERTY, new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String stringValue, String expectedStringValue) {
return stringValue.equals(expectedStringValue);
}
}, index.getIndex(), new BiConsumer<AnnotationTarget, Boolean>() {
@Override
public void accept(AnnotationTarget target, Boolean enabled) {
producer.produce(new PreAdditionalBeanBuildTimeConditionBuildItem(target, enabled));
}
});
}

@BuildStep
void unlessBuildPropertyPreAdditionalBean(CombinedIndexBuildItem index,
BuildProducer<PreAdditionalBeanBuildTimeConditionBuildItem> producer) {
buildProperty(UNLESS_BUILD_PROPERTY, new BiFunction<String, String, Boolean>() {
@Override
public Boolean apply(String stringValue, String expectedStringValue) {
return !stringValue.equals(expectedStringValue);
}
}, index.getIndex(), new BiConsumer<AnnotationTarget, Boolean>() {
@Override
public void accept(AnnotationTarget target, Boolean enabled) {
producer.produce(new PreAdditionalBeanBuildTimeConditionBuildItem(target, enabled));
}
});
}

void buildProperty(DotName annotationName, BiFunction<String, String, Boolean> testFun, BeanArchiveIndexBuildItem index,
BuildProducer<BuildTimeConditionBuildItem> producer) {
void buildProperty(DotName annotationName, BiFunction<String, String, Boolean> testFun, IndexView index,
BiConsumer<AnnotationTarget, Boolean> producer) {
Config config = ConfigProviderResolver.instance().getConfig();
Collection<AnnotationInstance> annotationInstances = index.getIndex().getAnnotations(annotationName);
Collection<AnnotationInstance> annotationInstances = index.getAnnotations(annotationName);
for (AnnotationInstance instance : annotationInstances) {
String propertyName = instance.value("name").asString();
String expectedStringValue = instance.value("stringValue").asString();
Expand Down Expand Up @@ -123,8 +171,7 @@ void buildProperty(DotName annotationName, BiFunction<String, String, Boolean> t
enabled = false;
}
}

producer.produce(new BuildTimeConditionBuildItem(instance.target(), enabled));
producer.accept(instance.target(), enabled);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.arc.deployment;

import org.jboss.jandex.AnnotationTarget;

import io.quarkus.builder.item.MultiBuildItem;

/**
* A type of build item that is similar to {@link BuildTimeConditionBuildItem} but evaluated before
* processing the {@link AdditionalBeanBuildItem} in order to filter the beans thanks to build time conditions
* before actually adding them with a {@link AdditionalBeanBuildItem}.
*
* @see io.quarkus.arc.deployment.BuildTimeConditionBuildItem
* @see io.quarkus.arc.deployment.AdditionalBeanBuildItem
*/
public final class PreAdditionalBeanBuildTimeConditionBuildItem extends MultiBuildItem {

private final AnnotationTarget target;
private final boolean enabled;

public PreAdditionalBeanBuildTimeConditionBuildItem(AnnotationTarget target, boolean enabled) {
switch (target.kind()) {
case CLASS:
case METHOD:
case FIELD:
this.target = target;
break;
default:
throw new IllegalArgumentException("'target' can only be a class, a field or a method");
}
this.enabled = enabled;
}

public AnnotationTarget getTarget() {
return target;
}

public boolean isEnabled() {
return enabled;
}
}
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.PreAdditionalBeanBuildTimeConditionBuildItem;
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,15 @@ void setUpDenyAllJaxRs(CombinedIndexBuildItem index,

@BuildStep
ApplicationResultBuildItem handleApplication(CombinedIndexBuildItem combinedIndexBuildItem,
BuildProducer<ReflectiveClassBuildItem> reflectiveClass) {
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
List<PreAdditionalBeanBuildTimeConditionBuildItem> buildTimeConditions,
ResteasyReactiveConfig config) {
// Use the "pre additional bean" build time conditions since we need to be able to filter the beans
// before actually adding them otherwise if we use normal build time conditions, we end up
// with a circular dependency
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 +278,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<PreAdditionalBeanBuildTimeConditionBuildItem> buildTimeConditions) {
return buildTimeConditions.stream()
.filter(item -> !item.isEnabled())
.map(PreAdditionalBeanBuildTimeConditionBuildItem::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,11 @@ public class ResteasyReactiveConfig {
@ConfigItem(defaultValue = "true")
@Experimental("This flag has a high probability of going away in the future")
public boolean defaultProduces;

/**
* Whether or not annotations such `@IfBuildTimeProfile`, `@IfBuildTimeProperty` and friends will be taken
* into account when used on JAX-RS 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
Loading

0 comments on commit faa48d7

Please sign in to comment.