diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index 7b47f12986bcb..67cb7d529a5ca 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -248,6 +248,24 @@ class MyBeanStarter { <1> The `AmazingService` is created during injection. <2> The `CoolService` is a normal scoped bean so we have to invoke a method upon the injected proxy to force the instantiation. +* Annotate the bean with `@io.quarkus.runtime.Startup` as described in <>: ++ +[source,java] +---- +@Startup // <1> +@ApplicationScoped +public class EagerAppBean { + + private final String name; + + EagerAppBean(NameGenerator generator) { // <2> + this.name = generator.createName(); + } +} +---- +1. For each bean annotated with `@Startup` a synthetic observer of `StartupEvent` is generated. The default priority is used. +2. The bean constructor is called when the application starts and the resulting contextual instance is stored in the application context. + NOTE: Quarkus users are encouraged to always prefer the `@Observes StartupEvent` to `@Initialized(ApplicationScoped.class)` as explained in the link:lifecycle[Application Initialization and Termination] guide. === Request Context Lifecycle @@ -408,6 +426,35 @@ public class CustomTracerConfiguration { `@DefaultBean` allows extensions (or any other code for that matter) to provide defaults while backing off if beans of that type are supplied in any way Quarkus supports. +=== Declaring Selected Alternatives + +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. +Quarkus has a simplified bean discovery and the content of `beans.xml` is ignored. + +The disadvantage of `@Priority` is that it has `@Target({ TYPE, PARAMETER })` and so it cannot be used for producer methods and fields. +To address this problem and to simplify the code Quarkus provides the `io.quarkus.arc.AlternativePriority` annotation. +It's basically a shortcut for `@Alternative` plus `@Priority`. +Additionaly, it can be used for producers. + +However, it is also possible to select alternatives for an application using the unified configuration. +The `quarkus.arc.selected-alternatives` property accepts a list of string values that are used to match alternative beans. +If any value matches then the priority of `Integer#MAX_VALUE` is used for the relevant bean. +The priority declared via `@Priority` or `@AlernativePriority` is overriden. + +.Value Examples +|=== +|Value|Description +|`org.acme.Foo`| Match the fully qualified name of the bean class or the bean class of the bean that declares the producer +|`org.acme.*`| Match beans where the package of the bean class is `org.acme` +|`org.acme.**`| Match beans where the package of the bean class starts with `org.acme` +|`Bar`| Match the simple name of the bean class or the bean class of the bean that declares the producer +|=== + +.Example application.properties +[source,properties] +---- +quarkus.arc.selected-alternatives=org.acme.Foo,org.acme.*,Bar +---- == Build Time Extension Points diff --git a/docs/src/main/asciidoc/lifecycle.adoc b/docs/src/main/asciidoc/lifecycle.adoc index 1d5d00bf25a23..3730bdf29e7f9 100644 --- a/docs/src/main/asciidoc/lifecycle.adoc +++ b/docs/src/main/asciidoc/lifecycle.adoc @@ -102,6 +102,7 @@ See link:writing-extensions#bootstrap-three-phases[Three Phases of Bootstrap and NOTE: In CDI applications, an event with qualifier `@Initialized(ApplicationScoped.class)` is fired when the application context is initialized. See https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#application_context[the spec, window="_blank"] for more info. +[[startup_annotation]] === Using `@Startup` to initialize a CDI bean at application startup A bean represented by a class, producer method or field annotated with `@Startup` is initialized at application startup: diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java index 724c220cb4e5e..123a701d9cff2 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java @@ -5,6 +5,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.List; +import java.util.Optional; import java.util.Set; import io.quarkus.arc.config.ConfigProperties; @@ -19,10 +21,12 @@ public class ArcConfig { /** * *

@@ -78,6 +82,24 @@ public class ArcConfig { @ConfigItem(defaultValue = "kebab-case") public ConfigProperties.NamingStrategy configPropertiesDefaultNamingStrategy; + /** + * The list of selected alternatives for an application. + *

+ * An element value can be: + *

+ * Each element value is used to match an alternative bean class, an alternative stereotype annotation type or a bean class + * that declares an alternative producer. If any value matches then the priority of {@link Integer#MAX_VALUE} is used for + * the relevant bean. The priority declared via {@link javax.annotation.Priority} or + * {@link io.quarkus.arc.AlternativePriority} is overriden. + */ + @ConfigItem + public Optional> selectedAlternatives; + public final boolean isRemoveUnusedBeansFieldValid() { return ALLOWED_REMOVE_UNUSED_BEANS_VALUES.contains(removeUnusedBeans.toLowerCase()); } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 83fdd2cfe9081..d58275ce4f1e4 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -3,6 +3,7 @@ import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -31,6 +32,7 @@ import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassAnnotationExclusion; import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassNameExclusion; import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem; +import io.quarkus.arc.processor.AlternativePriorities; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanConfigurator; import io.quarkus.arc.processor.BeanDefiningAnnotation; @@ -39,9 +41,11 @@ import io.quarkus.arc.processor.BytecodeTransformer; import io.quarkus.arc.processor.ContextConfigurator; import io.quarkus.arc.processor.ContextRegistrar; +import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.ObserverConfigurator; import io.quarkus.arc.processor.ReflectionRegistration; import io.quarkus.arc.processor.ResourceOutput; +import io.quarkus.arc.processor.StereotypeInfo; import io.quarkus.arc.runtime.AdditionalBean; import io.quarkus.arc.runtime.ArcRecorder; import io.quarkus.arc.runtime.BeanContainer; @@ -248,6 +252,42 @@ public boolean test(BeanInfo bean) { builder.setGenerateSources(BootstrapDebug.DEBUG_SOURCES_DIR != null); builder.setAllowMocking(launchModeBuildItem.getLaunchMode() == LaunchMode.TEST); + if (arcConfig.selectedAlternatives.isPresent()) { + final List> selectedAlternatives = initAlternativePredicates( + arcConfig.selectedAlternatives.get()); + builder.setAlternativePriorities(new AlternativePriorities() { + + @Override + public Integer compute(AnnotationTarget target, Collection stereotypes) { + ClassInfo clazz; + switch (target.kind()) { + case CLASS: + clazz = target.asClass(); + break; + case FIELD: + clazz = target.asField().declaringClass(); + break; + case METHOD: + clazz = target.asMethod().declaringClass(); + break; + default: + return null; + } + if (selectedAlternatives.stream().anyMatch(p -> p.test(clazz))) { + return Integer.MAX_VALUE; + } + if (!stereotypes.isEmpty()) { + for (StereotypeInfo stereotype : stereotypes) { + if (selectedAlternatives.stream().anyMatch(p -> p.test(stereotype.getTarget()))) { + return Integer.MAX_VALUE; + } + } + } + return null; + } + }); + } + BeanProcessor beanProcessor = builder.build(); ContextRegistrar.RegistrationContext context = beanProcessor.registerCustomContexts(); return new ContextRegistrationPhaseBuildItem(context, beanProcessor); @@ -400,6 +440,50 @@ CustomScopeAnnotationsBuildItem exposeCustomScopeNames(List> initAlternativePredicates(List selectedAlternatives) { + final String packMatch = ".*"; + final String packStarts = ".**"; + List> predicates = new ArrayList<>(); + for (String val : selectedAlternatives) { + if (val.endsWith(packMatch)) { + // Package matches + final String pack = val.substring(0, val.length() - packMatch.length()); + predicates.add(new Predicate() { + @Override + public boolean test(ClassInfo c) { + return DotNames.packageName(c.name()).equals(pack); + } + }); + } else if (val.endsWith(packStarts)) { + // Package starts with + final String prefix = val.substring(0, val.length() - packStarts.length()); + predicates.add(new Predicate() { + @Override + public boolean test(ClassInfo c) { + return DotNames.packageName(c.name()).startsWith(prefix); + } + }); + } else if (val.contains(".")) { + // Fully qualified name matches + predicates.add(new Predicate() { + @Override + public boolean test(ClassInfo c) { + return c.name().toString().equals(val); + } + }); + } else { + // Simple name matches + predicates.add(new Predicate() { + @Override + public boolean test(ClassInfo c) { + return DotNames.simpleName(c).equals(val); + } + }); + } + } + return predicates; + } + private abstract static class AbstractCompositeApplicationClassesPredicate implements Predicate { private final IndexView applicationClassesIndex; diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Foo.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Foo.java new file mode 100644 index 0000000000000..761f616c60fc0 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Foo.java @@ -0,0 +1,12 @@ +package io.quarkus.arc.test.alternatives; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class Foo { + + public String ping() { + return getClass().getName(); + } + +} \ No newline at end of file diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Producers.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Producers.java new file mode 100644 index 0000000000000..e4af5a4981c40 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/Producers.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.test.alternatives; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Produces; + +@ApplicationScoped +class Producers { + + @Alternative + @Produces + static final int CHARLIE = 10; + + @Produces + @Alternative + public String bravo() { + return "bravo"; + } +} \ No newline at end of file diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesFqcnTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesFqcnTest.java new file mode 100644 index 0000000000000..00e5258afe049 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesFqcnTest.java @@ -0,0 +1,67 @@ +package io.quarkus.arc.test.alternatives; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.alternatives.bar.Bar; +import io.quarkus.arc.test.alternatives.bar.MyStereotype; +import io.quarkus.test.QuarkusUnitTest; + +public class SelectedAlternativesFqcnTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SelectedAlternativesFqcnTest.class, Alpha.class, Producers.class, Foo.class, Bar.class, + MyStereotype.class) + .addAsResource(new StringAsset( + "quarkus.arc.selected-alternatives=io.quarkus.arc.test.alternatives.SelectedAlternativesFqcnTest$Alpha,io.quarkus.arc.test.alternatives.Producers,io.quarkus.arc.test.alternatives.bar.MyStereotype"), + "application.properties")); + + @Inject + Instance alpha; + + @Inject + Instance bravo; + + @Inject + Instance charlie; + + @Inject + Instance foo; + + @Test + public void testSelectedAlternatives() { + assertTrue(alpha.isResolvable()); + assertEquals("ok", alpha.get().ping()); + assertTrue(bravo.isResolvable()); + assertEquals("bravo", bravo.get()); + assertTrue(charlie.isResolvable()); + assertEquals(10, charlie.get()); + // Note that we should get Bar because it's annotated with an alternative stereotype selected in app properties + assertTrue(foo.isResolvable()); + assertEquals(Bar.class.getName(), foo.get().ping()); + } + + @Alternative + @ApplicationScoped + static class Alpha { + + public String ping() { + return "ok"; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageStartsTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageStartsTest.java new file mode 100644 index 0000000000000..df51421814aac --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageStartsTest.java @@ -0,0 +1,67 @@ +package io.quarkus.arc.test.alternatives; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.alternatives.bar.Bar; +import io.quarkus.arc.test.alternatives.bar.MyStereotype; +import io.quarkus.test.QuarkusUnitTest; + +public class SelectedAlternativesPackageStartsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SelectedAlternativesPackageStartsTest.class, Alpha.class, Producers.class, Foo.class, Bar.class, + MyStereotype.class) + .addAsResource(new StringAsset( + "quarkus.arc.selected-alternatives=io.quarkus.arc.test.**"), + "application.properties")); + + @Inject + Instance alpha; + + @Inject + Instance bravo; + + @Inject + Instance charlie; + + @Inject + Instance foo; + + @Test + public void testSelectedAlternatives() { + assertTrue(alpha.isResolvable()); + assertEquals("ok", alpha.get().ping()); + assertTrue(bravo.isResolvable()); + assertEquals("bravo", bravo.get()); + assertTrue(charlie.isResolvable()); + assertEquals(10, charlie.get()); + // MyStereotype is selected because its package starts with "io.quarkus.arc.test" + assertTrue(foo.isResolvable()); + assertEquals(Bar.class.getName(), foo.get().ping()); + } + + @Alternative + @ApplicationScoped + static class Alpha { + + public String ping() { + return "ok"; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageTest.java new file mode 100644 index 0000000000000..308a50f2aca4a --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesPackageTest.java @@ -0,0 +1,67 @@ +package io.quarkus.arc.test.alternatives; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.alternatives.bar.Bar; +import io.quarkus.arc.test.alternatives.bar.MyStereotype; +import io.quarkus.test.QuarkusUnitTest; + +public class SelectedAlternativesPackageTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SelectedAlternativesPackageTest.class, Alpha.class, Producers.class, Foo.class, Bar.class, + MyStereotype.class) + .addAsResource(new StringAsset( + "quarkus.arc.selected-alternatives=io.quarkus.arc.test.alternatives.*"), + "application.properties")); + + @Inject + Instance alpha; + + @Inject + Instance bravo; + + @Inject + Instance charlie; + + @Inject + Instance foo; + + @Test + public void testSelectedAlternatives() { + assertTrue(alpha.isResolvable()); + assertEquals("ok", alpha.get().ping()); + assertTrue(bravo.isResolvable()); + assertEquals("bravo", bravo.get()); + assertTrue(charlie.isResolvable()); + assertEquals(10, charlie.get()); + // Bar/MyStereotype is not selected because its package is not "io.quarkus.arc.test.alternatives" + assertTrue(foo.isResolvable()); + assertEquals(Foo.class.getName(), foo.get().ping()); + } + + @Alternative + @ApplicationScoped + static class Alpha { + + public String ping() { + return "ok"; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesSimpleNameTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesSimpleNameTest.java new file mode 100644 index 0000000000000..40d0a83ad5f54 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/SelectedAlternativesSimpleNameTest.java @@ -0,0 +1,67 @@ +package io.quarkus.arc.test.alternatives; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.test.alternatives.bar.Bar; +import io.quarkus.arc.test.alternatives.bar.MyStereotype; +import io.quarkus.test.QuarkusUnitTest; + +public class SelectedAlternativesSimpleNameTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(SelectedAlternativesSimpleNameTest.class, Alpha.class, Producers.class, Foo.class, Bar.class, + MyStereotype.class) + .addAsResource(new StringAsset( + "quarkus.arc.selected-alternatives=Alpha,Producers,MyStereotype"), + "application.properties")); + + @Inject + Instance alpha; + + @Inject + Instance bravo; + + @Inject + Instance charlie; + + @Inject + Instance foo; + + @Test + public void testSelectedAlternatives() { + assertTrue(alpha.isResolvable()); + assertEquals("ok", alpha.get().ping()); + assertTrue(bravo.isResolvable()); + assertEquals("bravo", bravo.get()); + assertTrue(charlie.isResolvable()); + assertEquals(10, charlie.get()); + // Note that we should get Bar because it's annotated with an alternative stereotype selected in app properties + assertTrue(foo.isResolvable()); + assertEquals(Bar.class.getName(), foo.get().ping()); + } + + @Alternative + @ApplicationScoped + static class Alpha { + + public String ping() { + return "ok"; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/Bar.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/Bar.java new file mode 100644 index 0000000000000..c85fbae9c2d85 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/Bar.java @@ -0,0 +1,8 @@ +package io.quarkus.arc.test.alternatives.bar; + +import io.quarkus.arc.test.alternatives.Foo; + +@MyStereotype +public class Bar extends Foo { + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/MyStereotype.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/MyStereotype.java new file mode 100644 index 0000000000000..ee139c1661646 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/alternatives/bar/MyStereotype.java @@ -0,0 +1,18 @@ +package io.quarkus.arc.test.alternatives.bar; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Stereotype; + +@Alternative +@Dependent +@Stereotype +@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface MyStereotype { +} \ No newline at end of file diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AlternativePriorities.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AlternativePriorities.java new file mode 100644 index 0000000000000..6617779135072 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AlternativePriorities.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.processor; + +import java.util.Collection; +import org.jboss.jandex.AnnotationTarget; + +/** + * Represents an additional way of defining priorities for alternative beans. + */ +public interface AlternativePriorities { + + /** + * + * @param target The bean class, producer method or field + * @param stereotypes The collection of stereotypes + * @return a computed priority value or {@code null} + */ + Integer compute(AnnotationTarget target, Collection stereotypes); + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 94ea15e1c3395..a36c292bf6e1f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -49,7 +49,8 @@ public class BeanDeployment { private static final Logger LOGGER = Logger.getLogger(BeanDeployment.class); - public static final EnumSet CLASS_TYPES = EnumSet.of(Type.Kind.CLASS, Type.Kind.PARAMETERIZED_TYPE); + + static final EnumSet CLASS_TYPES = EnumSet.of(Type.Kind.CLASS, Type.Kind.PARAMETERIZED_TYPE); private final BuildContextImpl buildContext; @@ -97,11 +98,13 @@ public class BeanDeployment { final boolean transformUnproxyableClasses; private final boolean jtaCapabilities; + private final AlternativePriorities alternativePriorities; + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - null, false, null, Collections.emptyMap(), Collections.emptyList(), false, false); + null, false, null, Collections.emptyMap(), Collections.emptyList(), false, false, null); } BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, @@ -113,7 +116,8 @@ public class BeanDeployment { Map> additionalStereotypes, List bindingRegistrars, boolean transformUnproxyableClasses, - boolean jtaCapabilities) { + boolean jtaCapabilities, + AlternativePriorities alternativePriorities) { this.buildContext = buildContext; Set beanDefiningAnnotations = new HashSet<>(); if (additionalBeanDefiningAnnotations != null) { @@ -170,6 +174,7 @@ public class BeanDeployment { this.interceptorResolver = new InterceptorResolver(this); this.transformUnproxyableClasses = transformUnproxyableClasses; this.jtaCapabilities = jtaCapabilities; + this.alternativePriorities = alternativePriorities; } ContextRegistrar.RegistrationContext registerCustomContexts(List contextRegistrars) { @@ -434,6 +439,16 @@ ScopeInfo getScope(DotName scopeAnnotationName) { return getScope(scopeAnnotationName, customContexts); } + /** + * + * @param target + * @param stereotypes + * @return the computed priority or {@code null} + */ + Integer computeAlternativePriority(AnnotationTarget target, List stereotypes) { + return alternativePriorities != null ? alternativePriorities.compute(target, stereotypes) : null; + } + private void buildContextPut(String key, Object value) { if (buildContext != null) { buildContext.putInternal(key, value); @@ -784,9 +799,11 @@ private List findBeans(Collection beanDefiningAnnotations, Li Map beanClassToBean = new HashMap<>(); for (ClassInfo beanClass : beanClasses) { BeanInfo classBean = Beans.createClassBean(beanClass, this, injectionPointTransformer); - beans.add(classBean); - beanClassToBean.put(beanClass, classBean); - injectionPoints.addAll(classBean.getAllInjectionPoints()); + if (classBean != null) { + beans.add(classBean); + beanClassToBean.put(beanClass, classBean); + injectionPoints.addAll(classBean.getAllInjectionPoints()); + } } List disposers = new ArrayList<>(); @@ -805,16 +822,21 @@ private List findBeans(Collection beanDefiningAnnotations, Li if (declaringBean != null) { BeanInfo producerMethodBean = Beans.createProducerMethod(producerMethod, declaringBean, this, findDisposer(declaringBean, producerMethod, disposers), injectionPointTransformer); - beans.add(producerMethodBean); - injectionPoints.addAll(producerMethodBean.getAllInjectionPoints()); + if (producerMethodBean != null) { + beans.add(producerMethodBean); + injectionPoints.addAll(producerMethodBean.getAllInjectionPoints()); + } } } for (FieldInfo producerField : producerFields) { BeanInfo declaringBean = beanClassToBean.get(producerField.declaringClass()); if (declaringBean != null) { - beans.add(Beans.createProducerField(producerField, declaringBean, this, - findDisposer(declaringBean, producerField, disposers))); + BeanInfo producerFieldBean = Beans.createProducerField(producerField, declaringBean, this, + findDisposer(declaringBean, producerField, disposers)); + if (producerFieldBean != null) { + beans.add(producerFieldBean); + } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index b9c17b19095fb..95d5c64e93140 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -1,5 +1,6 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.AlternativePriority; import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; import io.quarkus.arc.processor.BuildExtension.BuildContext; import io.quarkus.arc.processor.BuildExtension.Key; @@ -18,6 +19,7 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +import javax.annotation.Priority; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; @@ -79,7 +81,8 @@ private BeanProcessor(String name, IndexView index, Collection interceptorBindingRegistrars, boolean transformUnproxyableClasses, boolean jtaCapabilities, - boolean generateSources, boolean allowMocking) { + boolean generateSources, boolean allowMocking, + AlternativePriorities alternativePriorities) { this.reflectionRegistration = reflectionRegistration; this.applicationClassPredicate = applicationClassPredicate; this.name = name; @@ -103,7 +106,7 @@ private BeanProcessor(String name, IndexView index, Collection applicationClassPredicate = new Predicate() { @Override public boolean test(DotName dotName) { @@ -457,13 +462,26 @@ public Builder setGenerateSources(boolean value) { return this; } + /** + * Can be used to compute a priority of an alternative bean. A non-null computed value always + * takes precedence over the priority defined by {@link Priority}, {@link AlternativePriority} or an alternative + * stereotype. + * + * @param priorities + * @return self + */ + public Builder setAlternativePriorities(AlternativePriorities priorities) { + this.alternativePriorities = priorities; + return this; + } + public BeanProcessor build() { return new BeanProcessor(name, index, additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals, reflectionRegistration, annotationTransformers, injectionPointTransformers, observerTransformers, resourceAnnotations, beanRegistrars, observerRegistrars, contextRegistrars, beanDeploymentValidators, applicationClassPredicate, removeUnusedBeans, removalExclusions, additionalStereotypes, additionalInterceptorBindingRegistrars, transformUnproxyableClasses, jtaCapabilities, generateSources, - allowMocking); + allowMocking, alternativePriorities); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index bfe94a1281043..5ee9328d35314 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -129,14 +129,15 @@ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployme if (name == null) { name = initStereotypeName(stereotypes, beanClass); } - if (isAlternative && alternativePriority == null) { - alternativePriority = initStereotypeAlternativePriority(stereotypes); - // after all attempts, priority is still null, bean will be ignored + if (isAlternative) { + alternativePriority = initAlternativePriority(beanClass, alternativePriority, stereotypes, beanDeployment); if (alternativePriority == null) { - throw new IllegalStateException("Bean defined via class " + beanClass.name() - + " is declared as an @Alternative, " + - "but has no @Priority. Either declare a @Priority or leverage @io.quarkus.arc.AlernativePriority annotation."); + // after all attempts, priority is still null, bean will be ignored + LOGGER.infof( + "Ignoring bean defined via %s - declared as an @Alternative but not selected by @Priority, @AlernativePriority or quarkus.arc.selected-alternatives", + beanClass.name()); + return null; } } @@ -251,23 +252,19 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari if (name == null) { name = initStereotypeName(stereotypes, producerMethod); } - if (isAlternative && alternativePriority == null) { - alternativePriority = declaringBean.getAlternativePriority(); - if (alternativePriority == null) { - // Declaring bean itself does not have to be an alternative and can only have @Priority - alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream() - .filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() - .map(a -> a.value().asInt()).orElse(null); - } + + if (isAlternative) { if (alternativePriority == null) { - alternativePriority = initStereotypeAlternativePriority(stereotypes); + alternativePriority = declaringBean.getAlternativePriority(); } + alternativePriority = initAlternativePriority(producerMethod, alternativePriority, stereotypes, beanDeployment); // after all attempts, priority is still null if (alternativePriority == null) { - throw new IllegalStateException("Declaring bean " + declaringBean + - " contains a producer method " + producerMethod + " declaring an @Alternative, " + - "but without @Priority. Either make sure @Priority annotation gets inherited, or replace" + - "@Alternative annotation with @io.quarkus.arc.AlternativePriority annotation."); + // after all attempts, priority is still null, bean will be ignored + LOGGER.infof( + "Ignoring producer method %s - declared as an @Alternative but not selected by @Priority, @AlernativePriority or quarkus.arc.selected-alternatives", + declaringBean.getTarget().get().asClass().name() + "#" + producerMethod.name()); + return null; } } @@ -354,23 +351,18 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB if (name == null) { name = initStereotypeName(stereotypes, producerField); } - if (isAlternative && alternativePriority == null) { - alternativePriority = declaringBean.getAlternativePriority(); - if (alternativePriority == null) { - // Declaring bean itself does not have to be an alternative and can only have @Priority - alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream() - .filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() - .map(a -> a.value().asInt()).orElse(null); - } + + if (isAlternative) { if (alternativePriority == null) { - alternativePriority = initStereotypeAlternativePriority(stereotypes); + alternativePriority = declaringBean.getAlternativePriority(); } + alternativePriority = initAlternativePriority(producerField, alternativePriority, stereotypes, beanDeployment); // after all attempts, priority is still null if (alternativePriority == null) { - throw new IllegalStateException("Declaring bean " + declaringBean + - " contains a producer field " + producerField + " declaring an @Alternative, " + - "but without @Priority. Either make sure @Priority annotation gets inherited, or replace " + - "@Alternative annotation with @io.quarkus.arc.AlternativePriority annotation."); + LOGGER.debugf( + "Ignoring producer field %s - declared as an @Alternative but not selected by @Priority, @AlernativePriority or quarkus.arc.selected-alternatives", + producerField); + return null; } } @@ -831,6 +823,24 @@ private static String getDefaultName(MethodInfo producerMethod) { } } + private static Integer initAlternativePriority(AnnotationTarget target, Integer alternativePriority, + List stereotypes, BeanDeployment deployment) { + if (alternativePriority == null) { + // No @Priority or @AlernativePriority used - try stereotypes + alternativePriority = initStereotypeAlternativePriority(stereotypes); + } + Integer computedPriority = deployment.computeAlternativePriority(target, stereotypes); + if (computedPriority != null) { + if (alternativePriority != null) { + LOGGER.infof( + "Computed priority [%s] overrides the priority [%s] declared via @Priority or @AlernativePriority", + computedPriority, alternativePriority); + } + alternativePriority = computedPriority; + } + return alternativePriority; + } + static class FinalClassTransformFunction implements BiFunction { @Override diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index e2f578f5fd91b..f36931cd5a1fb 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -32,14 +32,12 @@ public void testGetTypeClosure() throws IOException { DotName producerName = DotName.createSimple(Producer.class.getName()); ClassInfo fooClass = index.getClassByName(fooName); Map> resolvedTypeVariables = new HashMap<>(); + BeanDeployment dummyDeployment = dummyDeployment(index); // Baz, Foo, Object Set bazTypes = Types.getTypeClosure(index.getClassByName(bazName), null, Collections.emptyMap(), - new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false), + dummyDeployment, resolvedTypeVariables::put); assertEquals(3, bazTypes.size()); assertTrue(bazTypes.contains(Type.create(bazName, Kind.CLASS))); @@ -54,10 +52,7 @@ public void testGetTypeClosure() throws IOException { resolvedTypeVariables.clear(); // Foo, Object Set fooTypes = Types.getClassBeanTypeClosure(fooClass, - new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false)); + dummyDeployment); assertEquals(2, fooTypes.size()); for (Type t : fooTypes) { if (t.kind().equals(Kind.PARAMETERIZED_TYPE)) { @@ -71,22 +66,23 @@ public void testGetTypeClosure() throws IOException { MethodInfo producerMethod = producerClass.method(producersName); // Object is the sole type Set producerMethodTypes = Types.getProducerMethodTypeClosure(producerMethod, - new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false)); + dummyDeployment); assertEquals(1, producerMethodTypes.size()); // Object is the sole type FieldInfo producerField = producerClass.field(producersName); Set producerFieldTypes = Types.getProducerFieldTypeClosure(producerField, - new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false)); + dummyDeployment); assertEquals(1, producerFieldTypes.size()); } + BeanDeployment dummyDeployment(IndexView index) { + return new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), null, + false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false, false, null); + } + static class Foo { T field; diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java index 9a262710b7eff..4c2747814b401 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/ArcTestContainer.java @@ -3,6 +3,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ComponentsProvider; import io.quarkus.arc.ResourceReferenceProvider; +import io.quarkus.arc.processor.AlternativePriorities; import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanArchives; import io.quarkus.arc.processor.BeanDeploymentValidator; @@ -76,6 +77,7 @@ public static class Builder { private boolean shouldFail = false; private boolean removeUnusedBeans = false; private final List> exclusions; + private AlternativePriorities alternativePriorities; public Builder() { resourceReferenceProviders = new ArrayList<>(); @@ -163,11 +165,17 @@ public Builder shouldFail() { return this; } + public Builder alternativePriorities(AlternativePriorities priorities) { + this.alternativePriorities = priorities; + return this; + } + public ArcTestContainer build() { return new ArcTestContainer(resourceReferenceProviders, beanClasses, resourceAnnotations, beanRegistrars, observerRegistrars, contextRegistrars, interceptorBindingRegistrars, annotationsTransformers, injectionsPointsTransformers, - observerTransformers, beanDeploymentValidators, shouldFail, removeUnusedBeans, exclusions); + observerTransformers, beanDeploymentValidators, shouldFail, removeUnusedBeans, exclusions, + alternativePriorities); } } @@ -200,12 +208,14 @@ public ArcTestContainer build() { private final boolean removeUnusedBeans; private final List> exclusions; + private final AlternativePriorities alternativePriorities; + public ArcTestContainer(Class... beanClasses) { this(Collections.emptyList(), Arrays.asList(beanClasses), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, false, - Collections.emptyList()); + Collections.emptyList(), null); } public ArcTestContainer(List> resourceReferenceProviders, List> beanClasses, @@ -216,7 +226,8 @@ public ArcTestContainer(List> resourceReferenceProviders, List List annotationsTransformers, List ipTransformers, List observerTransformers, List beanDeploymentValidators, boolean shouldFail, boolean removeUnusedBeans, - List> exclusions) { + List> exclusions, + AlternativePriorities alternativePriorities) { this.resourceReferenceProviders = resourceReferenceProviders; this.beanClasses = beanClasses; this.resourceAnnotations = resourceAnnotations; @@ -232,6 +243,7 @@ public ArcTestContainer(List> resourceReferenceProviders, List this.shouldFail = shouldFail; this.removeUnusedBeans = removeUnusedBeans; this.exclusions = exclusions; + this.alternativePriorities = alternativePriorities; } // this is where we start Arc, we operate on a per-method basis @@ -362,6 +374,7 @@ public void writeResource(Resource resource) throws IOException { for (Predicate exclusion : exclusions) { builder.addRemovalExclusion(exclusion); } + builder.setAlternativePriorities(alternativePriorities); BeanProcessor beanProcessor = builder.build(); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AbstractAlternativeNoPriorityTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AbstractAlternativeNoPriorityTest.java deleted file mode 100644 index eb049aa89f363..0000000000000 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AbstractAlternativeNoPriorityTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package io.quarkus.arc.test.alternatives; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.quarkus.arc.test.ArcTestContainer; -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.Alternative; -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.Vetoed; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** - * Tests that in Arc alternatives (class beans, producer methods/fields) without @Priority cause an exception. - * Has three subclasses that separately test the same with bean class, producer field and producer method. - */ -public abstract class AbstractAlternativeNoPriorityTest { - - protected ArcTestContainer.Builder sharedBuilder = ArcTestContainer.builder().shouldFail().beanClasses(MyInterface.class, - Foo.class); - - protected abstract ArcTestContainer buildContainer(ArcTestContainer.Builder sharedBuilder); - - @RegisterExtension - ArcTestContainer container = buildContainer(sharedBuilder); - - @Test - public void testExceptionWasThrown() { - Throwable t = container.getFailure(); - assertNotNull(t); - assertTrue(t instanceof IllegalStateException); - } - - static interface MyInterface { - void ping(); - } - - @ApplicationScoped - static class Foo implements MyInterface { - - @Override - public void ping() { - - } - } - - @ApplicationScoped - // deliberately doesn't have priority - @Alternative - static class AlternativeClassBean implements MyInterface { - - @Override - public void ping() { - - } - } - - // producers aren't really enabled, but it still simulates the error - @Dependent - static class ProducerWithField { - @Produces - @Alternative - @ApplicationScoped - AlternativeProducerFieldBean bar = new AlternativeProducerFieldBean(); - } - - // producers aren't really enabled, but it still simulates the error - @Dependent - static class ProducerWithMethod { - - @Produces - @Alternative - @ApplicationScoped - public AlternativeProducerMethodBean createBar() { - return new AlternativeProducerMethodBean(); - } - } - - @Vetoed - static class AlternativeProducerFieldBean implements MyInterface { - - @Override - public void ping() { - - } - } - - @Vetoed - static class AlternativeProducerMethodBean implements MyInterface { - - @Override - public void ping() { - - } - } -} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityBeanClassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityBeanClassTest.java deleted file mode 100644 index 34ad069bcdae4..0000000000000 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityBeanClassTest.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.quarkus.arc.test.alternatives; - -import io.quarkus.arc.test.ArcTestContainer; - -public class AlternativeNoPriorityBeanClassTest extends AbstractAlternativeNoPriorityTest { - @Override - protected ArcTestContainer buildContainer(ArcTestContainer.Builder sharedBuilder) { - return sharedBuilder.beanClasses(AbstractAlternativeNoPriorityTest.AlternativeClassBean.class).build(); - } -} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerFieldTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerFieldTest.java deleted file mode 100644 index 0cd585a5866c5..0000000000000 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerFieldTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.quarkus.arc.test.alternatives; - -import io.quarkus.arc.test.ArcTestContainer; - -public class AlternativeNoPriorityProducerFieldTest extends AbstractAlternativeNoPriorityTest { - @Override - protected ArcTestContainer buildContainer(ArcTestContainer.Builder sharedBuilder) { - return sharedBuilder.beanClasses(AbstractAlternativeNoPriorityTest.AlternativeProducerFieldBean.class, - AbstractAlternativeNoPriorityTest.ProducerWithField.class).build(); - } -} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerMethodTest.java deleted file mode 100644 index 6b5ed2d3fd3c3..0000000000000 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativeNoPriorityProducerMethodTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.quarkus.arc.test.alternatives; - -import io.quarkus.arc.test.ArcTestContainer; - -public class AlternativeNoPriorityProducerMethodTest extends AbstractAlternativeNoPriorityTest { - @Override - protected ArcTestContainer buildContainer(ArcTestContainer.Builder sharedBuilder) { - return sharedBuilder.beanClasses(AbstractAlternativeNoPriorityTest.AlternativeProducerMethodBean.class, - AbstractAlternativeNoPriorityTest.ProducerWithMethod.class).build(); - } -} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/priority/ComputedAlternativePriorityTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/priority/ComputedAlternativePriorityTest.java new file mode 100644 index 0000000000000..3043758f453bf --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/priority/ComputedAlternativePriorityTest.java @@ -0,0 +1,77 @@ +package io.quarkus.arc.test.alternatives.priority; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.test.ArcTestContainer; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.enterprise.inject.Produces; +import org.jboss.jandex.AnnotationTarget; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Tests {@link io.quarkus.arc.AlternativePriority} annotation. + */ +public class ComputedAlternativePriorityTest { + + @RegisterExtension + ArcTestContainer testContainer = ArcTestContainer.builder().beanClasses(MyInterface.class, Foo.class, Producers.class) + .alternativePriorities((target, stereotypes) -> { + if (target.kind() == AnnotationTarget.Kind.CLASS) { + if (target.asClass().name().toString().equals(Foo.class.getName())) { + return 100; + } + } else if (target.kind() == AnnotationTarget.Kind.FIELD || target.kind() == AnnotationTarget.Kind.METHOD) { + return 10; + } + return null; + }).build(); + + @Test + public void testComputedPriority() { + InstanceHandle myInterface = Arc.container().instance(MyInterface.class); + assertTrue(myInterface.isAvailable()); + assertEquals(Foo.class.getSimpleName(), myInterface.get().ping()); + + InstanceHandle bravo = Arc.container().instance(String.class); + assertTrue(bravo.isAvailable()); + assertEquals("bravo", bravo.get()); + + InstanceHandle charlie = Arc.container().instance(Integer.class); + assertTrue(charlie.isAvailable()); + assertEquals(10, charlie.get()); + } + + static interface MyInterface { + String ping(); + } + + @Alternative + @ApplicationScoped + static class Foo implements MyInterface { + + @Override + public String ping() { + return Foo.class.getSimpleName(); + } + } + + @ApplicationScoped + static class Producers { + + @Alternative + @Produces + static final int CHARLIE = 10; + + @Produces + @Alternative + public String bravo() { + return "bravo"; + } + } + +}