Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to exclude JAX-RS classes with annotations #15215

Merged
merged 1 commit into from
Feb 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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