Skip to content

Commit

Permalink
Merge pull request #34999 from Sgitario/34938
Browse files Browse the repository at this point in the history
Allow to exclude classes in REST Data with Panache with annotations
  • Loading branch information
Sgitario authored Jul 26, 2023
2 parents 919ba3d + 1f85a3c commit 01806a2
Show file tree
Hide file tree
Showing 11 changed files with 216 additions and 17 deletions.
32 changes: 32 additions & 0 deletions docs/src/main/asciidoc/rest-data-panache.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -597,3 +597,35 @@ Both responses would also contain these headers:
* Link: < http://example.com/people?page=1&size=2 >; rel="next"

A `previous` link header (and hal link) would not be included, because the previous page does not exist.

== Include/Exclude Jakarta REST classes

=== Using Build time conditions

Quarkus enables the inclusion or exclusion of Jakarta REST Resources, Providers and Features directly thanks to build time conditions in the same that it does for CDI beans.
Thus, the REST Data with Panache interfaces 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 generated Jakarta REST classes should be included.

In the following example, Quarkus will include the generated resource from the `PeopleResource` interface if and only if the build profile `app1` has been enabled.

[source,java]
----
@IfBuildProfile("app1")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}
----


=== Using a runtime property

IMPORTANT: This option is only available when using the RESTEasy Reactive Quarkus extension.

Quarkus can also conditionally disable the generated Jakarta REST Resources based on the value of runtime properties using the `@io.quarkus.resteasy.reactive.server.EndpointDisabled` annotation.

In the following example, Quarkus will exclude the generated resource from the `PeopleResource` interface at runtime if the application has `some.property` configured to `"disable"`.

[source,java]
----
@EndpointDisabled(name = "some.property", stringValue = "disable")
public interface PeopleResource extends PanacheEntityResource<Person, Long> {
}
----
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.build;

import static io.restassured.RestAssured.given;

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

import io.quarkus.test.QuarkusUnitTest;

public class BuildConditionsWithResourceDisabledTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Collection.class, CollectionsResource.class));

@Test
void shouldResourceNotBeFound() {
given().accept("application/json")
.when().get("/collections")
.then().statusCode(404);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.build;

import static io.restassured.RestAssured.given;

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

import io.quarkus.test.QuarkusUnitTest;

public class BuildConditionsWithResourceEnabledTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.overrideConfigKey("collections.enabled", "true")
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Collection.class, CollectionsResource.class));

@Test
void shouldResourceBeFound() {
given().accept("application/json")
.when().get("/collections")
.then().statusCode(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.build;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

import io.quarkus.hibernate.orm.panache.PanacheEntityBase;

@Entity
public class Collection extends PanacheEntityBase {

@Id
public String id;

public String name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.orm.rest.data.panache.deployment.build;

import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;

@IfBuildProperty(name = "collections.enabled", stringValue = "true")
public interface CollectionsResource extends PanacheEntityResource<Collection, String> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build;

import static io.restassured.RestAssured.given;

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

import io.quarkus.test.QuarkusUnitTest;

public class BuildConditionsWithResourceDisabledTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.overrideConfigKey("collections.endpoint", "disable")
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Collection.class, CollectionsResource.class));

@Test
void shouldResourceNotBeFound() {
given().accept("application/json")
.when().get("/collections")
.then().statusCode(404);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build;

import static io.restassured.RestAssured.given;

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

import io.quarkus.test.QuarkusUnitTest;

public class BuildConditionsWithResourceEnabledTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.overrideConfigKey("collections.endpoint", "enable")
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(Collection.class, CollectionsResource.class));

@Test
void shouldResourceBeFound() {
given().accept("application/json")
.when().get("/collections")
.then().statusCode(200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;

import io.quarkus.hibernate.reactive.panache.PanacheEntityBase;

@Entity
public class Collection extends PanacheEntityBase {

@Id
public String id;

public String name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.quarkus.hibernate.reactive.rest.data.panache.deployment.build;

import io.quarkus.hibernate.reactive.rest.data.panache.PanacheEntityResource;
import io.quarkus.resteasy.reactive.server.EndpointDisabled;

@EndpointDisabled(name = "collections.endpoint", stringValue = "disable")
public interface CollectionsResource extends PanacheEntityResource<Collection, String> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.jboss.jandex.AnnotationTarget;

import io.quarkus.arc.deployment.BuildTimeConditionBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
import io.quarkus.deployment.Capabilities;
Expand Down Expand Up @@ -50,8 +55,11 @@ void supportingBuildItems(Capabilities capabilities,
}

@BuildStep
void implementResources(CombinedIndexBuildItem index, List<RestDataResourceBuildItem> resourceBuildItems,
List<ResourcePropertiesBuildItem> resourcePropertiesBuildItems, Capabilities capabilities,
void implementResources(CombinedIndexBuildItem index,
List<RestDataResourceBuildItem> resourceBuildItems,
List<ResourcePropertiesBuildItem> resourcePropertiesBuildItems,
List<BuildTimeConditionBuildItem> buildTimeConditions,
Capabilities capabilities,
BuildProducer<GeneratedBeanBuildItem> resteasyClassicImplementationsProducer,
BuildProducer<GeneratedJaxRsResourceBuildItem> resteasyReactiveImplementationsProducer) {

Expand All @@ -63,27 +71,30 @@ void implementResources(CombinedIndexBuildItem index, List<RestDataResourceBuild
"Reactive REST Data Panache does not work with 'quarkus-resteasy'. Only 'quarkus-resteasy-reactive' extensions are supported");
}

Set<String> excludedClasses = getExcludedClasses(buildTimeConditions);
ClassOutput classOutput = isResteasyClassic ? new GeneratedBeanGizmoAdaptor(resteasyClassicImplementationsProducer)
: new GeneratedJaxRsResourceGizmoAdaptor(resteasyReactiveImplementationsProducer);
JaxRsResourceImplementor jaxRsResourceImplementor = new JaxRsResourceImplementor(capabilities);
ResourcePropertiesProvider resourcePropertiesProvider = new ResourcePropertiesProvider(index.getIndex());

for (RestDataResourceBuildItem resourceBuildItem : resourceBuildItems) {
ResourceMetadata resourceMetadata = resourceBuildItem.getResourceMetadata();
ResourceProperties resourceProperties = getResourceProperties(resourcePropertiesProvider,
resourceMetadata, resourcePropertiesBuildItems);
if (resourceProperties.isHal()) {
if (isResteasyClassic && !hasAnyJsonCapabilityForResteasyClassic(capabilities)) {
throw new IllegalStateException("Cannot generate HAL endpoints without "
+ "either 'quarkus-resteasy-jsonb' or 'quarkus-resteasy-jackson'");
} else if (!isResteasyClassic && !hasAnyJsonCapabilityForResteasyReactive(capabilities)) {
throw new IllegalStateException("Cannot generate HAL endpoints without "
+ "either 'quarkus-resteasy-reactive-jsonb' or 'quarkus-resteasy-reactive-jackson'");
}
if (!excludedClasses.contains(resourceBuildItem.getResourceMetadata().getResourceInterface())) {
ResourceMetadata resourceMetadata = resourceBuildItem.getResourceMetadata();
ResourceProperties resourceProperties = getResourceProperties(resourcePropertiesProvider,
resourceMetadata, resourcePropertiesBuildItems);
if (resourceProperties.isHal()) {
if (isResteasyClassic && !hasAnyJsonCapabilityForResteasyClassic(capabilities)) {
throw new IllegalStateException("Cannot generate HAL endpoints without "
+ "either 'quarkus-resteasy-jsonb' or 'quarkus-resteasy-jackson'");
} else if (!isResteasyClassic && !hasAnyJsonCapabilityForResteasyReactive(capabilities)) {
throw new IllegalStateException("Cannot generate HAL endpoints without "
+ "either 'quarkus-resteasy-reactive-jsonb' or 'quarkus-resteasy-reactive-jackson'");
}

}
if (resourceProperties.isExposed()) {
jaxRsResourceImplementor.implement(classOutput, resourceMetadata, resourceProperties, capabilities);
}
if (resourceProperties.isExposed()) {
jaxRsResourceImplementor.implement(classOutput, resourceMetadata, resourceProperties, capabilities);
}
}
}
}
Expand All @@ -108,4 +119,13 @@ private boolean hasAnyJsonCapabilityForResteasyReactive(Capabilities capabilitie
return capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JSONB)
|| capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JACKSON);
}

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 @@ -25,7 +25,9 @@ public class ResourcePropertiesProvider {
private static final DotName METHOD_PROPERTIES_ANNOTATION = DotName.createSimple(
io.quarkus.rest.data.panache.MethodProperties.class.getName());

private static final List<String> ANNOTATIONS_TO_COPY = List.of(RolesAllowed.class.getPackageName());
private static final List<String> ANNOTATIONS_TO_COPY = List.of(RolesAllowed.class.getPackageName(),
// To also support `@EndpointDisabled` if used
"io.quarkus.resteasy.reactive.server");

private final IndexView index;

Expand Down

0 comments on commit 01806a2

Please sign in to comment.