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

ArC: allow putting bean enablement annotations on stereotypes #37302

Merged
merged 1 commit into from
Nov 28, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,8 @@

NOTE: The runtime profile has absolutely no effect on the bean resolution using `@IfBuildProfile` and `@UnlessBuildProfile`.

TIP: It is also possible to use `@IfBuildProfile` and `@UnlessBuildProfile` on stereotypes.

[[enable_build_properties]]
=== Enabling Beans for Quarkus Build Properties

Expand Down Expand Up @@ -652,11 +654,13 @@
}
----

NOTE: Properties set at runtime have absolutely no effect on the bean resolution using `@IfBuildProperty`.

Check warning on line 657 in docs/src/main/asciidoc/cdi-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/cdi-reference.adoc", "range": {"start": {"line": 657, "column": 81}}}, "severity": "INFO"}

TIP: It is also possible to use `@IfBuildProperty` and `@UnlessBuildProperty` on stereotypes.

=== Declaring Selected Alternatives

Check warning on line 661 in docs/src/main/asciidoc/cdi-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.HeadingPunctuation] Do not use end punctuation in headings. Raw Output: {"message": "[Quarkus.HeadingPunctuation] Do not use end punctuation in headings.", "location": {"path": "docs/src/main/asciidoc/cdi-reference.adoc", "range": {"start": {"line": 661, "column": 1}}}, "severity": "INFO"}

Check warning on line 661 in docs/src/main/asciidoc/cdi-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in '4.9. Declaring Selected Alternatives'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in '4.9. Declaring Selected Alternatives'.", "location": {"path": "docs/src/main/asciidoc/cdi-reference.adoc", "range": {"start": {"line": 661, "column": 1}}}, "severity": "INFO"}

In CDI, an alternative bean may be selected either globally for an application by means of `@Priority`, or for a bean archive using a `beans.xml` descriptor.

Check warning on line 663 in docs/src/main/asciidoc/cdi-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/cdi-reference.adoc", "range": {"start": {"line": 663, "column": 11}}}, "severity": "WARNING"}

Check warning on line 663 in docs/src/main/asciidoc/cdi-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'by using' or 'that uses' rather than 'using'.", "location": {"path": "docs/src/main/asciidoc/cdi-reference.adoc", "range": {"start": {"line": 663, "column": 108}}}, "severity": "INFO"}
Quarkus has a simplified bean discovery and the content of `beans.xml` is ignored.

However, it is also possible to select alternatives for an application using the unified configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public BuildTimeConditionBuildItem(AnnotationTarget target, boolean enabled) {
this.target = target;
break;
default:
throw new IllegalArgumentException("'target' can only be a class, a field or a method");
throw new IllegalArgumentException("'target' can only be a class, a field or a method: " + target);
}
this.enabled = enabled;
}
Expand Down

Large diffs are not rendered by default.

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

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;

import io.quarkus.builder.item.SimpleBuildItem;

final class BuildTimeEnabledStereotypesBuildItem extends SimpleBuildItem {
private final Map<DotName, BuildTimeEnabledStereotype> map;

BuildTimeEnabledStereotypesBuildItem(List<BuildTimeEnabledStereotype> buildTimeEnabledStereotypes) {
Map<DotName, BuildTimeEnabledStereotype> map = new HashMap<>();
for (BuildTimeEnabledStereotype buildTimeEnabledStereotype : buildTimeEnabledStereotypes) {
map.put(buildTimeEnabledStereotype.name, buildTimeEnabledStereotype);
}
this.map = map;
}

boolean isStereotype(DotName name) {
return map.containsKey(name);
}

BuildTimeEnabledStereotype getStereotype(DotName stereotypeName) {
return map.get(stereotypeName);
}

Collection<BuildTimeEnabledStereotype> all() {
return map.values();
}

static final class BuildTimeEnabledStereotype {
final DotName name;
final boolean inheritable; // meta-annotated `@Inherited`

// enablement annotations present directly _or transitively_ on this stereotype
final Map<DotName, List<AnnotationInstance>> annotations;

BuildTimeEnabledStereotype(DotName name, boolean inheritable, Map<DotName, List<AnnotationInstance>> annotations) {
this.name = name;
this.inheritable = inheritable;
this.annotations = annotations;
}

List<AnnotationInstance> getEnablementAnnotations(DotName enablementAnnotationName) {
return annotations.getOrDefault(enablementAnnotationName, List.of());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
package io.quarkus.arc.test.profile;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.Stereotype;
import jakarta.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.profile.IfBuildProfile;
import io.quarkus.test.QuarkusUnitTest;

public class IfBuildProfileStereotypeTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(DevOnly.class, InheritableDevOnly.class, TransitiveDevOnly.class,
InheritableTransitiveDevOnly.class, MyService.class, DevOnlyMyService.class,
InheritableDevOnlyMyService.class, TransitiveDevOnlyMyService.class,
InheritableTransitiveDevOnlyMyService.class, MyServiceSimple.class,
MyServiceDevOnlyDirect.class, MyServiceDevOnlyTransitive.class,
MyServiceDevOnlyOnSuperclassNotInheritable.class,
MyServiceDevOnlyOnSuperclassInheritable.class,
MyServiceDevOnlyTransitiveOnSuperclassNotInheritable.class,
MyServiceDevOnlyTransitiveOnSuperclassInheritable.class, Producers.class));

@Inject
@Any
Instance<MyService> services;

@Test
public void test() {
Set<String> hello = services.stream().map(MyService::hello).collect(Collectors.toSet());
Set<Object> expected = Set.of(
MyServiceSimple.class.getSimpleName(),
MyServiceDevOnlyOnSuperclassNotInheritable.class.getSimpleName(),
MyServiceDevOnlyTransitiveOnSuperclassNotInheritable.class.getSimpleName(),
Producers.SIMPLE);
assertEquals(expected, hello);
}

@IfBuildProfile("dev")
@Stereotype
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DevOnly {
}

@IfBuildProfile("dev")
@Stereotype
@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritableDevOnly {
}

@DevOnly
@Stereotype
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TransitiveDevOnly {
}

@DevOnly
@Stereotype
@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritableTransitiveDevOnly {
}

interface MyService {
String hello();
}

@DevOnly
static abstract class DevOnlyMyService implements MyService {
}

@InheritableDevOnly
static abstract class InheritableDevOnlyMyService implements MyService {
}

@TransitiveDevOnly
static abstract class TransitiveDevOnlyMyService implements MyService {
}

@InheritableTransitiveDevOnly
static abstract class InheritableTransitiveDevOnlyMyService implements MyService {
}

@ApplicationScoped
static class MyServiceSimple implements MyService {
@Override
public String hello() {
return MyServiceSimple.class.getSimpleName();
}
}

@ApplicationScoped
@DevOnly
static class MyServiceDevOnlyDirect implements MyService {
@Override
public String hello() {
return MyServiceDevOnlyDirect.class.getSimpleName();
}
}

@ApplicationScoped
@TransitiveDevOnly
static class MyServiceDevOnlyTransitive implements MyService {
@Override
public String hello() {
return MyServiceDevOnlyTransitive.class.getSimpleName();
}
}

@ApplicationScoped
static class MyServiceDevOnlyOnSuperclassNotInheritable extends DevOnlyMyService {
@Override
public String hello() {
return MyServiceDevOnlyOnSuperclassNotInheritable.class.getSimpleName();
}
}

@ApplicationScoped
static class MyServiceDevOnlyOnSuperclassInheritable extends InheritableDevOnlyMyService {
@Override
public String hello() {
return MyServiceDevOnlyOnSuperclassInheritable.class.getSimpleName();
}
}

@ApplicationScoped
static class MyServiceDevOnlyTransitiveOnSuperclassNotInheritable extends TransitiveDevOnlyMyService {
@Override
public String hello() {
return MyServiceDevOnlyTransitiveOnSuperclassNotInheritable.class.getSimpleName();
}
}

@ApplicationScoped
static class MyServiceDevOnlyTransitiveOnSuperclassInheritable extends InheritableTransitiveDevOnlyMyService {
@Override
public String hello() {
return MyServiceDevOnlyTransitiveOnSuperclassInheritable.class.getSimpleName();
}
}

@ApplicationScoped
static class Producers {
static final String SIMPLE = "Producers.simple";
static final String DEV_ONLY_DIRECT = "Producers.devOnlyDirect";
static final String DEV_ONLY_TRANSITIVE = "Producers.devOnlyTransitive";

@Produces
MyService simple = new MyService() {
@Override
public String hello() {
return SIMPLE;
}
};

@Produces
@DevOnly
MyService devOnlyDirect = new MyService() {
@Override
public String hello() {
return DEV_ONLY_DIRECT;
}
};

@Produces
@TransitiveDevOnly
MyService devOnlyTransitive = new MyService() {
@Override
public String hello() {
return DEV_ONLY_TRANSITIVE;
}
};
}
}