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 23, 2021
1 parent b042fca commit 4ab150f
Show file tree
Hide file tree
Showing 13 changed files with 742 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 @@ -605,6 +605,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 allows to include or exclude root resource, provider and feature classes directly thanks to build time conditions like it is possible with CDI beans.
Indeed, the different 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 the 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 allows to include or exclude root resource, provider and feature classes directly thanks to build time conditions like it is possible with CDI beans.
Indeed, the different 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 the 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<EagerBuildTimeConditionBuildItem> producerEagerBuildItem) {
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));
producerEagerBuildItem.produce(new EagerBuildTimeConditionBuildItem(instance.target(), enabled));
}
}

@BuildStep
void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer) {
void unlessBuildProfile(CombinedIndexBuildItem index, BuildProducer<BuildTimeConditionBuildItem> producer,
BuildProducer<EagerBuildTimeConditionBuildItem> producerEagerBuildItem) {
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));
producerEagerBuildItem.produce(new EagerBuildTimeConditionBuildItem(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,48 @@ 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 ifBuildPropertyEager(CombinedIndexBuildItem index, BuildProducer<EagerBuildTimeConditionBuildItem> 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 EagerBuildTimeConditionBuildItem(target, enabled));
}
});
}

@BuildStep
void unlessBuildPropertyEager(CombinedIndexBuildItem index, BuildProducer<EagerBuildTimeConditionBuildItem> 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 EagerBuildTimeConditionBuildItem(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 +169,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 EagerBuildTimeConditionBuildItem extends MultiBuildItem {

private final AnnotationTarget target;
private final boolean enabled;

public EagerBuildTimeConditionBuildItem(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.EagerBuildTimeConditionBuildItem;
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<EagerBuildTimeConditionBuildItem> buildTimeConditions,
ResteasyReactiveConfig config) {
// Use the build time conditions in "eager mode" 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<EagerBuildTimeConditionBuildItem> buildTimeConditions) {
return buildTimeConditions.stream()
.filter(item -> !item.isEnabled())
.map(EagerBuildTimeConditionBuildItem::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
Loading

0 comments on commit 4ab150f

Please sign in to comment.