diff --git a/docs/src/main/asciidoc/cdi-integration.adoc b/docs/src/main/asciidoc/cdi-integration.adoc index 12317c0764a08..73cfe4d4fa845 100644 --- a/docs/src/main/asciidoc/cdi-integration.adoc +++ b/docs/src/main/asciidoc/cdi-integration.adoc @@ -478,6 +478,28 @@ QualifierRegistrarBuildItem addQualifiers() { } ---- +== Use Case - Additional Stereotypes + +It is sometimes useful to register an existing annotation that is not annotated with `@javax.enterprise.inject.Stereotype` as a CDI stereotype. +This is similar to what CDI achieves through `BeforeBeanDiscovery#addStereotype()`. +We are going to use `StereotypeRegistrarBuildItem` to get it done. + +.`StereotypeRegistrarBuildItem` Example +[source,java] +---- +@BuildStep +StereotypeRegistrarBuildItem addStereotypes() { + return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() { + @Override + public Set getAdditionalStereotypes() { + return Collections.singleton(DotName.createSimple(NotAStereotype.class.getName())); + } + }); +} +---- + +If the newly registered stereotype annotation doesn't have the appropriate meta-annotations, such as scope or interceptor bindings, use an <> to add them. + [[injection_point_transformation]] == Use Case - Injection Point Transformation diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java index 979825edea548..f70fd4624f4e5 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java @@ -9,8 +9,13 @@ import io.quarkus.builder.item.MultiBuildItem; /** - * A map of additional stereotype classes to their instances that we want to process. + * A map of additional annotation types (that have the same meaning as the {@code @Stereotype} meta-annotation) + * to their occurences on other annotations (that become custom stereotypes). + * + * @deprecated use {@link StereotypeRegistrarBuildItem}; + * this class will be removed at some time after Quarkus 3.0 */ +@Deprecated public final class AdditionalStereotypeBuildItem extends MultiBuildItem { private final Map> stereotypes; 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 ee1a2ef822daa..f07140fff2de6 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 @@ -73,6 +73,7 @@ import io.quarkus.arc.processor.ReflectionRegistration; import io.quarkus.arc.processor.ResourceOutput; import io.quarkus.arc.processor.StereotypeInfo; +import io.quarkus.arc.processor.StereotypeRegistrar; import io.quarkus.arc.processor.Transformation; import io.quarkus.arc.processor.Types; import io.quarkus.arc.runtime.AdditionalBean; @@ -156,6 +157,25 @@ AdditionalBeanBuildItem quarkusApplication(CombinedIndexBuildItem combinedIndex) .build(); } + @BuildStep + StereotypeRegistrarBuildItem convertLegacyAdditionalStereotypes(List buildItems) { + return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() { + @Override + public Set getAdditionalStereotypes() { + Set result = new HashSet<>(); + for (AdditionalStereotypeBuildItem buildItem : buildItems) { + result.addAll(buildItem.getStereotypes() + .values() + .stream() + .flatMap(Collection::stream) + .map(AnnotationInstance::name) + .collect(Collectors.toSet())); + } + return result; + } + }); + } + // PHASE 1 - build BeanProcessor @BuildStep public ContextRegistrationPhaseBuildItem initialize( @@ -169,7 +189,7 @@ public ContextRegistrationPhaseBuildItem initialize( List observerTransformers, List interceptorBindingRegistrars, List qualifierRegistrars, - List additionalStereotypeBuildItems, + List stereotypeRegistrars, List applicationClassPredicates, List additionalBeans, List resourceAnnotations, @@ -253,11 +273,6 @@ public void transform(TransformationContext transformationContext) { .map((s) -> new BeanDefiningAnnotation(s.getName(), s.getDefaultScope())).collect(Collectors.toList()); beanDefiningAnnotations.add(new BeanDefiningAnnotation(ADDITIONAL_BEAN, null)); builder.setAdditionalBeanDefiningAnnotations(beanDefiningAnnotations); - final Map> additionalStereotypes = new HashMap<>(); - for (final AdditionalStereotypeBuildItem item : additionalStereotypeBuildItems) { - additionalStereotypes.putAll(item.getStereotypes()); - } - builder.setAdditionalStereotypes(additionalStereotypes); builder.addResourceAnnotations( resourceAnnotations.stream().map(ResourceAnnotationBuildItem::getName).collect(Collectors.toList())); // register all annotation transformers @@ -280,6 +295,10 @@ public void transform(TransformationContext transformationContext) { for (QualifierRegistrarBuildItem registrar : qualifierRegistrars) { builder.addQualifierRegistrar(registrar.getQualifierRegistrar()); } + // register additional stereotypes + for (StereotypeRegistrarBuildItem registrar : stereotypeRegistrars) { + builder.addStereotypeRegistrar(registrar.getStereotypeRegistrar()); + } builder.setRemoveUnusedBeans(arcConfig.shouldEnableBeanRemoval()); if (arcConfig.shouldOnlyKeepAppBeans()) { builder.addRemovalExclusion(new AbstractCompositeApplicationClassesPredicate( diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoProducerMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoProducerMethodsProcessor.java index 9390adae83828..a1cf21c30d99e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoProducerMethodsProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoProducerMethodsProcessor.java @@ -3,7 +3,7 @@ import static io.quarkus.arc.processor.Annotations.contains; import static io.quarkus.arc.processor.Annotations.containsAny; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -32,20 +32,20 @@ public class AutoProducerMethodsProcessor { @BuildStep void annotationTransformer(ArcConfig config, BeanArchiveIndexBuildItem beanArchiveIndex, CustomScopeAnnotationsBuildItem scopes, - List additionalStereotypes, + List stereotypeRegistrars, BuildProducer annotationsTransformer) throws Exception { if (!config.autoProducerMethods) { return; } - List qualifiersAndStereotypes = new ArrayList<>(); + Set qualifiersAndStereotypes = new HashSet<>(); for (AnnotationInstance qualifier : beanArchiveIndex.getIndex().getAnnotations(DotNames.QUALIFIER)) { qualifiersAndStereotypes.add(qualifier.target().asClass().name()); } - for (AnnotationInstance qualifier : beanArchiveIndex.getIndex().getAnnotations(DotNames.STEREOTYPE)) { - qualifiersAndStereotypes.add(qualifier.target().asClass().name()); + for (AnnotationInstance stereotype : beanArchiveIndex.getIndex().getAnnotations(DotNames.STEREOTYPE)) { + qualifiersAndStereotypes.add(stereotype.target().asClass().name()); } - for (AdditionalStereotypeBuildItem additionalStereotype : additionalStereotypes) { - qualifiersAndStereotypes.addAll(additionalStereotype.getStereotypes().keySet()); + for (StereotypeRegistrarBuildItem stereotypeRegistrar : stereotypeRegistrars) { + qualifiersAndStereotypes.addAll(stereotypeRegistrar.getStereotypeRegistrar().getAdditionalStereotypes()); } LOGGER.debugf("Add missing @Produces to methods annotated with %s", qualifiersAndStereotypes); annotationsTransformer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StereotypeRegistrarBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StereotypeRegistrarBuildItem.java new file mode 100644 index 0000000000000..578850c253ffd --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/StereotypeRegistrarBuildItem.java @@ -0,0 +1,21 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.arc.processor.StereotypeRegistrar; +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Makes it possible to register annotations that should be considered stereotypes but are not annotated with + * {@code javax.enterprise.inject.Stereotype}. + */ +public final class StereotypeRegistrarBuildItem extends MultiBuildItem { + + private final StereotypeRegistrar registrar; + + public StereotypeRegistrarBuildItem(StereotypeRegistrar registrar) { + this.registrar = registrar; + } + + public StereotypeRegistrar getStereotypeRegistrar() { + return registrar; + } +} diff --git a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java index 3ea4143b196ac..aaac3d5ca9b30 100644 --- a/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java +++ b/extensions/smallrye-health/deployment/src/main/java/io/quarkus/smallrye/health/deployment/SmallRyeHealthProcessor.java @@ -1,13 +1,11 @@ package io.quarkus.smallrye.health.deployment; -import static io.quarkus.arc.processor.Annotations.containsAny; import static io.quarkus.arc.processor.Annotations.getAnnotations; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -24,20 +22,15 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; -import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; -import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; -import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem; import io.quarkus.arc.deployment.SyntheticBeansRuntimeInitBuildItem; -import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BuiltinScope; -import io.quarkus.arc.processor.DotNames; import io.quarkus.deployment.Capabilities; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; @@ -158,12 +151,12 @@ void build(SmallRyeHealthRecorder recorder, // Discover the beans annotated with @Health, @Liveness, @Readiness, @Startup, @HealthGroup, // @HealthGroups and @Wellness even if no scope is defined - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(LIVENESS)); - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(READINESS)); - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(STARTUP)); - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUP)); - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUPS)); - beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(WELLNESS)); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(LIVENESS, BuiltinScope.SINGLETON.getName())); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(READINESS, BuiltinScope.SINGLETON.getName())); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(STARTUP, BuiltinScope.SINGLETON.getName())); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUP, BuiltinScope.SINGLETON.getName())); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(HEALTH_GROUPS, BuiltinScope.SINGLETON.getName())); + beanDefiningAnnotation.produce(new BeanDefiningAnnotationBuildItem(WELLNESS, BuiltinScope.SINGLETON.getName())); // Add additional beans additionalBean.produce(new AdditionalBeanBuildItem(QuarkusAsyncHealthCheckFactory.class)); @@ -350,59 +343,6 @@ ShutdownListenerBuildItem shutdownListener() { return new ShutdownListenerBuildItem(new ShutdownReadinessListener()); } - @BuildStep - AnnotationsTransformerBuildItem annotationTransformer(BeanArchiveIndexBuildItem beanArchiveIndex, - CustomScopeAnnotationsBuildItem scopes) { - - // Transform health checks that are not annotated with a scope or a stereotype with a default scope - Set stereotypeAnnotations = new HashSet<>(); - for (AnnotationInstance annotation : beanArchiveIndex.getIndex().getAnnotations(DotNames.STEREOTYPE)) { - ClassInfo annotationClass = beanArchiveIndex.getIndex().getClassByName(annotation.name()); - if (annotationClass != null && scopes.isScopeIn(annotationClass.classAnnotations())) { - // Stereotype annotation with a default scope - stereotypeAnnotations.add(annotationClass.name()); - } - } - List healthAnnotations = new ArrayList<>(5); - healthAnnotations.add(LIVENESS); - healthAnnotations.add(READINESS); - healthAnnotations.add(STARTUP); - healthAnnotations.add(HEALTH_GROUP); - healthAnnotations.add(HEALTH_GROUPS); - healthAnnotations.add(WELLNESS); - - return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { - - @Override - public boolean appliesTo(Kind kind) { - return kind == Kind.CLASS || kind == Kind.METHOD; - } - - @Override - public void transform(TransformationContext ctx) { - if (ctx.getAnnotations().isEmpty()) { - return; - } - Collection annotations; - if (ctx.isClass()) { - annotations = ctx.getAnnotations(); - if (containsAny(annotations, stereotypeAnnotations)) { - return; - } - } else { - annotations = getAnnotations(Kind.METHOD, ctx.getAnnotations()); - } - if (scopes.isScopeIn(annotations)) { - return; - } - if (containsAny(annotations, healthAnnotations)) { - ctx.transform().add(BuiltinScope.SINGLETON.getName()).done(); - } - } - - }); - } - // UI @BuildStep void registerUiExtension( diff --git a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java index a3980a13263b8..e350c767c53e5 100644 --- a/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java +++ b/extensions/spring-di/deployment/src/main/java/io/quarkus/spring/di/deployment/SpringDIProcessor.java @@ -27,21 +27,22 @@ import org.jboss.jandex.MethodParameterInfo; import org.jboss.jandex.Type; -import io.quarkus.arc.deployment.AdditionalStereotypeBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; +import io.quarkus.arc.deployment.StereotypeRegistrarBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.StereotypeRegistrar; import io.quarkus.arc.processor.Transformation; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; -/* - * A simple processor that maps annotations Spring DI annotation to CDI annotation +/** + * A simple processor that maps Spring DI annotations to CDI annotations. * Arc's handling of annotation mapping (by creating an extra abstraction layer on top of the Jandex index) - * suits this sort of handling perfectly + * suits this sort of handling perfectly. */ public class SpringDIProcessor { @@ -50,11 +51,10 @@ public class SpringDIProcessor { static final DotName SPRING_COMPONENT = DotName.createSimple("org.springframework.stereotype.Component"); static final DotName SPRING_SERVICE = DotName.createSimple("org.springframework.stereotype.Service"); static final DotName SPRING_REPOSITORY = DotName.createSimple("org.springframework.stereotype.Repository"); - private static final Set SPRING_STEREOTYPE_ANNOTATIONS = Arrays.stream(new DotName[] { + private static final Set SPRING_STEREOTYPE_ANNOTATIONS = Set.of( SPRING_COMPONENT, SPRING_SERVICE, - SPRING_REPOSITORY, - }).collect(Collectors.toSet()); + SPRING_REPOSITORY); private static final DotName CONFIGURATION_ANNOTATION = DotName .createSimple("org.springframework.context.annotation.Configuration"); @@ -120,18 +120,24 @@ SpringBeanNameToDotNameBuildItem createBeanNamesMap(BeanArchiveIndexBuildItem be @BuildStep AnnotationsTransformerBuildItem beanTransformer( final BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, - final BuildProducer additionalStereotypeBuildItemBuildProducer) { + final BuildProducer stereotypeRegistrarProducer) { final IndexView index = beanArchiveIndexBuildItem.getIndex(); final Map> stereotypeScopes = getStereotypeScopes(index); - final Map> instances = new HashMap<>(); + final Set stereotypeAnnotations = new HashSet<>(); for (final DotName name : stereotypeScopes.keySet()) { - instances.put(name, index.getAnnotations(name) + stereotypeAnnotations.addAll(index.getAnnotations(name) .stream() .filter(it -> it.target().kind() == AnnotationTarget.Kind.CLASS && it.target().asClass().isAnnotation()) + .map(AnnotationInstance::name) .collect(Collectors.toSet())); } - additionalStereotypeBuildItemBuildProducer.produce(new AdditionalStereotypeBuildItem(instances)); + stereotypeRegistrarProducer.produce(new StereotypeRegistrarBuildItem(new StereotypeRegistrar() { + @Override + public Set getAdditionalStereotypes() { + return stereotypeAnnotations; + } + })); return new AnnotationsTransformerBuildItem(context -> { final Collection annotations = context.getAnnotations(); if (annotations.isEmpty()) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java index b9a83ed191036..45a7f939608fb 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfigurator.java @@ -43,6 +43,30 @@ public void done() { if (implClass == null) { throw new IllegalStateException("Unable to find the bean class in the index: " + implClazz); } + + ScopeInfo scope = this.scope; + if (scope == null) { + scope = Beans.initStereotypeScope(stereotypes, implClass, beanDeployment); + } + if (scope == null) { + scope = BuiltinScope.DEPENDENT.getInfo(); + } + + String name = this.name; + if (name == null) { + name = Beans.initStereotypeName(stereotypes, implClass); + } + + Boolean alternative = this.alternative; + if (alternative == null) { + alternative = Beans.initStereotypeAlternative(stereotypes); + } + + Integer priority = this.priority; + if (priority == null) { + priority = Beans.initStereotypeAlternativePriority(stereotypes); + } + beanConsumer.accept(new BeanInfo.Builder() .implClazz(implClass) .providerType(providerType) @@ -52,6 +76,7 @@ public void done() { .qualifiers(qualifiers) .alternative(alternative) .priority(priority) + .stereotypes(stereotypes) .name(name) .creator(creatorConsumer) .destroyer(destroyerConsumer) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java index ebdc449f3101e..5e325dfab60aa 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanConfiguratorBase.java @@ -9,8 +9,10 @@ import io.quarkus.gizmo.ResultHandle; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; @@ -32,7 +34,8 @@ public abstract class BeanConfiguratorBase types; protected final Set qualifiers; protected ScopeInfo scope; - protected boolean alternative; + protected Boolean alternative; + protected final List stereotypes; protected String name; protected Consumer creatorConsumer; protected Consumer destroyerConsumer; @@ -47,7 +50,7 @@ protected BeanConfiguratorBase(DotName implClazz) { this.implClazz = implClazz; this.types = new HashSet<>(); this.qualifiers = new HashSet<>(); - this.scope = BuiltinScope.DEPENDENT.getInfo(); + this.stereotypes = new ArrayList<>(); this.removable = true; } @@ -68,6 +71,8 @@ public THIS read(BeanConfiguratorBase base) { scope(base.scope); alternative = base.alternative; priority = base.priority; + stereotypes.clear(); + stereotypes.addAll(base.stereotypes); name(base.name); creator(base.creatorConsumer); destroyer(base.destroyerConsumer); @@ -197,6 +202,16 @@ public THIS priority(int value) { return self(); } + public THIS addStereotype(StereotypeInfo stereotype) { + this.stereotypes.add(stereotype); + return self(); + } + + public THIS stereotypes(StereotypeInfo... stereotypes) { + Collections.addAll(this.stereotypes, stereotypes); + return self(); + } + /** * The provider type is the "real" type of the bean instance created via * {@link InjectableReferenceProvider#get(CreationalContext)}. 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 111c694670db6..6ec9288e33dfb 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 @@ -175,8 +175,13 @@ public class BeanDeployment { repeatingInterceptorBindingAnnotations = findContainerAnnotations(interceptorBindings, this.beanArchiveIndex); buildContextPut(Key.INTERCEPTOR_BINDINGS.asString(), Collections.unmodifiableMap(interceptorBindings)); + Set additionalStereotypes = new HashSet<>(); + for (StereotypeRegistrar stereotypeRegistrar : builder.stereotypeRegistrars) { + additionalStereotypes.addAll(stereotypeRegistrar.getAdditionalStereotypes()); + } + this.stereotypes = findStereotypes(this.beanArchiveIndex, interceptorBindings, beanDefiningAnnotations, customContexts, - builder.additionalStereotypes, annotationStore); + additionalStereotypes, annotationStore); buildContextPut(Key.STEREOTYPES.asString(), Collections.unmodifiableMap(stereotypes)); this.transitiveInterceptorBindings = findTransitiveInterceptorBindings(interceptorBindings.keySet(), @@ -735,19 +740,17 @@ private static Set recursiveBuild(DotName name, private Map findStereotypes(IndexView index, Map interceptorBindings, Collection additionalBeanDefiningAnnotations, Map> customContexts, - Map> additionalStereotypes, AnnotationStore annotationStore) { + Set additionalStereotypes, AnnotationStore annotationStore) { Map stereotypes = new HashMap<>(); - final List stereotypeAnnotations = new ArrayList<>(index.getAnnotations(DotNames.STEREOTYPE)); - for (final Collection annotations : additionalStereotypes.values()) { - stereotypeAnnotations.addAll(annotations); - } + Set stereotypeNames = new HashSet<>(); - for (AnnotationInstance stereotype : stereotypeAnnotations) { - stereotypeNames.add(stereotype.target().asClass().name()); + for (AnnotationInstance annotation : index.getAnnotations(DotNames.STEREOTYPE)) { + stereotypeNames.add(annotation.target().asClass().name()); } - for (AnnotationInstance stereotype : stereotypeAnnotations) { - final DotName stereotypeName = stereotype.target().asClass().name(); + stereotypeNames.addAll(additionalStereotypes); + + for (DotName stereotypeName : stereotypeNames) { ClassInfo stereotypeClass = getClassByName(index, stereotypeName); if (stereotypeClass != null && !isExcluded(stereotypeClass)) { @@ -775,6 +778,11 @@ private Map findStereotypes(IndexView index, Map findStereotypes(IndexView index, Map additionalBeanDefiningAnnotations; - Map> additionalStereotypes; ResourceOutput output; ReflectionRegistration reflectionRegistration; @@ -453,6 +452,7 @@ public static class Builder { final List contextRegistrars; final List qualifierRegistrars; final List interceptorBindingRegistrars; + final List stereotypeRegistrars; final List beanDeploymentValidators; final List>> suppressConditionGenerators; @@ -473,7 +473,6 @@ public static class Builder { public Builder() { name = DEFAULT_NAME; additionalBeanDefiningAnnotations = Collections.emptySet(); - additionalStereotypes = Collections.emptyMap(); reflectionRegistration = ReflectionRegistration.NOOP; resourceAnnotations = new ArrayList<>(); annotationTransformers = new ArrayList<>(); @@ -484,6 +483,7 @@ public Builder() { contextRegistrars = new ArrayList<>(); qualifierRegistrars = new ArrayList<>(); interceptorBindingRegistrars = new ArrayList<>(); + stereotypeRegistrars = new ArrayList<>(); beanDeploymentValidators = new ArrayList<>(); suppressConditionGenerators = new ArrayList<>(); @@ -538,9 +538,23 @@ public Builder setAdditionalBeanDefiningAnnotations( return this; } + /** + * @deprecated use {@link #addStereotypeRegistrar(StereotypeRegistrar)}; + * this method will be removed at some time after Quarkus 3.0 + */ + @Deprecated public Builder setAdditionalStereotypes(Map> additionalStereotypes) { Objects.requireNonNull(additionalStereotypes); - this.additionalStereotypes = additionalStereotypes; + this.stereotypeRegistrars.add(new StereotypeRegistrar() { + @Override + public Set getAdditionalStereotypes() { + return additionalStereotypes.values() + .stream() + .flatMap(Collection::stream) + .map(AnnotationInstance::name) + .collect(Collectors.toSet()); + } + }); return this; } @@ -554,6 +568,11 @@ public Builder addInterceptorBindingRegistrar(InterceptorBindingRegistrar bindin return this; } + public Builder addStereotypeRegistrar(StereotypeRegistrar stereotypeRegistrar) { + this.stereotypeRegistrars.add(stereotypeRegistrar); + return this; + } + public Builder setOutput(ResourceOutput output) { this.output = output; return this; 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 81b2d54213511..32df9ebe228aa 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 @@ -287,7 +287,7 @@ private static DefinitionException multipleScopesFound(String baseMessage, List< + scopes.stream().map(s -> s.getDotName().toString()).collect(Collectors.joining(", "))); } - private static ScopeInfo initStereotypeScope(List stereotypes, AnnotationTarget target, + static ScopeInfo initStereotypeScope(List stereotypes, AnnotationTarget target, BeanDeployment beanDeployment) { if (stereotypes.isEmpty()) { return null; @@ -316,7 +316,7 @@ private static ScopeInfo initStereotypeScope(List stereotypes, A return BeanDeployment.getValidScope(stereotypeScopes.isEmpty() ? additionalBDAScopes : stereotypeScopes, target); } - private static boolean initStereotypeAlternative(List stereotypes) { + static boolean initStereotypeAlternative(List stereotypes) { if (stereotypes.isEmpty()) { return false; } @@ -328,7 +328,7 @@ private static boolean initStereotypeAlternative(List stereotype return false; } - private static Integer initStereotypeAlternativePriority(List stereotypes) { + static Integer initStereotypeAlternativePriority(List stereotypes) { if (stereotypes.isEmpty()) { return null; } @@ -340,7 +340,7 @@ private static Integer initStereotypeAlternativePriority(List st return null; } - private static String initStereotypeName(List stereotypes, AnnotationTarget target) { + static String initStereotypeName(List stereotypes, AnnotationTarget target) { if (stereotypes.isEmpty()) { return null; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java index ad7c96239bcce..f76e0a38e6f56 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java @@ -17,12 +17,12 @@ public class StereotypeInfo { private final ClassInfo target; // allows to differentiate between standard stereotype and one that is in fact additional bean defining annotation private final boolean isAdditionalBeanDefiningAnnotation; - // allows to differentiate between standard stereotype and one that was added through an AdditionalStereotypeBuildItem - private final boolean isAdditionalStereotypeBuildItem; + // allows to differentiate between standard stereotype and one that was added through StereotypeRegistrarBuildItem + private final boolean isAdditionalStereotype; public StereotypeInfo(ScopeInfo defaultScope, List interceptorBindings, boolean alternative, Integer alternativePriority, - boolean isNamed, boolean isAdditionalBeanDefiningAnnotation, boolean isAdditionalStereotypeBuildItem, + boolean isNamed, boolean isAdditionalBeanDefiningAnnotation, boolean isAdditionalStereotype, ClassInfo target, boolean isInherited, List parentStereotypes) { this.defaultScope = defaultScope; this.interceptorBindings = interceptorBindings; @@ -31,7 +31,7 @@ public StereotypeInfo(ScopeInfo defaultScope, List intercept this.isNamed = isNamed; this.target = target; this.isAdditionalBeanDefiningAnnotation = isAdditionalBeanDefiningAnnotation; - this.isAdditionalStereotypeBuildItem = isAdditionalStereotypeBuildItem; + this.isAdditionalStereotype = isAdditionalStereotype; this.isInherited = isInherited; this.parentStereotypes = parentStereotypes; } @@ -79,12 +79,21 @@ public boolean isAdditionalBeanDefiningAnnotation() { return isAdditionalBeanDefiningAnnotation; } + /** + * @deprecated use {@link #isAdditionalStereotype()}; + * this method will be removed at some time after Quarkus 3.0 + */ + @Deprecated public boolean isAdditionalStereotypeBuildItem() { - return isAdditionalStereotypeBuildItem; + return isAdditionalStereotype; + } + + public boolean isAdditionalStereotype() { + return isAdditionalStereotype; } public boolean isGenuine() { - return !isAdditionalBeanDefiningAnnotation && !isAdditionalStereotypeBuildItem; + return !isAdditionalBeanDefiningAnnotation && !isAdditionalStereotype; } public List getParentStereotypes() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeRegistrar.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeRegistrar.java new file mode 100644 index 0000000000000..1f4f5e5a37c4a --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeRegistrar.java @@ -0,0 +1,17 @@ +package io.quarkus.arc.processor; + +import java.util.Set; +import javax.enterprise.inject.Stereotype; +import org.jboss.jandex.DotName; + +/** + * Makes it possible to turn an annotation into a stereotype without adding a {@link Stereotype} annotation to it. + */ +public interface StereotypeRegistrar extends BuildExtension { + + /** + * Returns a set of annotation types (their names) that should be treated as stereotypes. + * To modify (meta-)annotations on these annotations, use {@link AnnotationsTransformer}. + */ + Set getAdditionalStereotypes(); +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java index f6aa7645fa9dc..cf3688e34ef9b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AlternativePriority.java @@ -10,7 +10,7 @@ * If a bean is annotated with this annotation, it is considered an enabled alternative with given priority. * Effectively, this is a shortcut for {@code Alternative} plus {@code Priority} annotations. * - * This annotation can be used not only on bean classes, but also method and field producers (unlike pure {@code Priority}. + * This annotation can be used not only on bean classes, but also method and field producers (unlike pure {@code Priority}). * * @deprecated Use {@link Alternative} and {@link io.quarkus.arc.Priority}/{@link javax.annotation.Priority} instead */ 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 16ddeb30d70a6..ad9c6a70e8e55 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 @@ -17,6 +17,7 @@ import io.quarkus.arc.processor.ObserverTransformer; import io.quarkus.arc.processor.QualifierRegistrar; import io.quarkus.arc.processor.ResourceOutput; +import io.quarkus.arc.processor.StereotypeRegistrar; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -74,6 +75,7 @@ public static class Builder { private final List contextRegistrars; private final List qualifierRegistrars; private final List interceptorBindingRegistrars; + private final List stereotypeRegistrars; private final List annotationsTransformers; private final List injectionsPointsTransformers; private final List observerTransformers; @@ -93,6 +95,7 @@ public Builder() { contextRegistrars = new ArrayList<>(); qualifierRegistrars = new ArrayList<>(); interceptorBindingRegistrars = new ArrayList<>(); + stereotypeRegistrars = new ArrayList<>(); annotationsTransformers = new ArrayList<>(); injectionsPointsTransformers = new ArrayList<>(); observerTransformers = new ArrayList<>(); @@ -161,6 +164,11 @@ public Builder interceptorBindingRegistrars(InterceptorBindingRegistrar... regis return this; } + public Builder stereotypeRegistrars(StereotypeRegistrar... registrars) { + Collections.addAll(this.stereotypeRegistrars, registrars); + return this; + } + public Builder beanDeploymentValidators(BeanDeploymentValidator... validators) { Collections.addAll(this.beanDeploymentValidators, validators); return this; @@ -204,6 +212,7 @@ public ArcTestContainer build() { private final List contextRegistrars; private final List qualifierRegistrars; private final List interceptorBindingRegistrars; + private final List stereotypeRegistrars; private final List annotationsTransformers; private final List injectionPointsTransformers; private final List observerTransformers; @@ -226,6 +235,7 @@ public ArcTestContainer(Class... beanClasses) { this.observerRegistrars = Collections.emptyList(); this.contextRegistrars = Collections.emptyList(); this.interceptorBindingRegistrars = Collections.emptyList(); + this.stereotypeRegistrars = Collections.emptyList(); this.qualifierRegistrars = Collections.emptyList(); this.annotationsTransformers = Collections.emptyList(); this.injectionPointsTransformers = Collections.emptyList(); @@ -248,6 +258,7 @@ public ArcTestContainer(Builder builder) { this.contextRegistrars = builder.contextRegistrars; this.qualifierRegistrars = builder.qualifierRegistrars; this.interceptorBindingRegistrars = builder.interceptorBindingRegistrars; + this.stereotypeRegistrars = builder.stereotypeRegistrars; this.annotationsTransformers = builder.annotationsTransformers; this.injectionPointsTransformers = builder.injectionsPointsTransformers; this.observerTransformers = builder.observerTransformers; @@ -370,6 +381,7 @@ private ClassLoader init(ExtensionContext context) { contextRegistrars.forEach(builder::addContextRegistrar); qualifierRegistrars.forEach(builder::addQualifierRegistrar); interceptorBindingRegistrars.forEach(builder::addInterceptorBindingRegistrar); + stereotypeRegistrars.forEach(builder::addStereotypeRegistrar); annotationsTransformers.forEach(builder::addAnnotationTransformer); injectionPointsTransformers.forEach(builder::addInjectionPointTransformer); observerTransformers.forEach(builder::addObserverTransformer); diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/beans/SyntheticBeanWithStereotypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/beans/SyntheticBeanWithStereotypeTest.java new file mode 100644 index 0000000000000..d9c02b0f9c30d --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/beans/SyntheticBeanWithStereotypeTest.java @@ -0,0 +1,128 @@ +package io.quarkus.arc.test.buildextension.beans; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.AlternativePriority; +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.BeanRegistrar; +import io.quarkus.arc.processor.StereotypeInfo; +import io.quarkus.arc.processor.StereotypeRegistrar; +import io.quarkus.arc.test.ArcTestContainer; +import io.quarkus.gizmo.MethodDescriptor; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Set; +import javax.annotation.Priority; +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class SyntheticBeanWithStereotypeTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(ToBeStereotype.class, SimpleBinding.class, SimpleInterceptor.class) + .additionalClasses(SomeBean.class) + .stereotypeRegistrars(new MyStereotypeRegistrar()) + .annotationsTransformers(new MyAnnotationTrasnformer()) + .beanRegistrars(new MyBeanRegistrar()) + .build(); + + @Test + public void test() { + InstanceHandle bean = Arc.container().select(SomeBean.class).getHandle(); + assertEquals(ApplicationScoped.class, bean.getBean().getScope()); + assertEquals("someBean", bean.getBean().getName()); + assertTrue(bean.getBean().isAlternative()); + assertEquals(11, bean.getBean().getPriority()); + + SomeBean instance = bean.get(); + assertNotNull(instance); + assertEquals("hello", instance.hello()); + // interceptors are _not_ applied to synthetic beans + } + + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + @interface ToBeStereotype { + } + + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + @InterceptorBinding + @interface SimpleBinding { + } + + @Interceptor + @Priority(1) + @SimpleBinding + static class SimpleInterceptor { + @AroundInvoke + public Object invoke(InvocationContext context) throws Exception { + return "intercepted: " + context.proceed(); + } + } + + @ToBeStereotype + static class SomeBean { + public String hello() { + return "hello"; + } + } + + static class MyStereotypeRegistrar implements StereotypeRegistrar { + @Override + public Set getAdditionalStereotypes() { + return Set.of(DotName.createSimple(ToBeStereotype.class.getName())); + } + } + + static class MyAnnotationTrasnformer implements AnnotationsTransformer { + @Override + public boolean appliesTo(AnnotationTarget.Kind kind) { + return kind == AnnotationTarget.Kind.CLASS; + } + + @Override + public void transform(TransformationContext transformationContext) { + if (transformationContext.getTarget().asClass().name() + .equals(DotName.createSimple(ToBeStereotype.class.getName()))) { + transformationContext.transform() + .add(ApplicationScoped.class) + .add(SimpleBinding.class) + .add(Named.class) + .add(AlternativePriority.class, AnnotationValue.createIntegerValue("value", 11)) + .done(); + } + } + } + + static class MyBeanRegistrar implements BeanRegistrar { + @Override + public void register(RegistrationContext context) { + StereotypeInfo stereotype = context.get(Key.STEREOTYPES).get(DotName.createSimple(ToBeStereotype.class.getName())); + + context.configure(SomeBean.class) + .types(SomeBean.class) + .stereotypes(stereotype) + .creator(mc -> mc.returnValue(mc.newInstance(MethodDescriptor.ofConstructor(SomeBean.class)))) + .done(); + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/stereotypes/AdditionalStereotypesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/stereotypes/AdditionalStereotypesTest.java new file mode 100644 index 0000000000000..104c4bb87b01d --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/buildextension/stereotypes/AdditionalStereotypesTest.java @@ -0,0 +1,111 @@ +package io.quarkus.arc.test.buildextension.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.StereotypeRegistrar; +import io.quarkus.arc.test.ArcTestContainer; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.Set; +import javax.annotation.Priority; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.inject.Named; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class AdditionalStereotypesTest { + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder() + .beanClasses(ToBeStereotype.class, SimpleBinding.class, SimpleInterceptor.class, SomeBean.class) + .stereotypeRegistrars(new MyStereotypeRegistrar()) + .annotationsTransformers(new MyAnnotationTrasnformer()) + .build(); + + @Test + public void test() { + InstanceHandle bean = Arc.container().select(SomeBean.class).getHandle(); + assertEquals(ApplicationScoped.class, bean.getBean().getScope()); + assertEquals("someBean", bean.getBean().getName()); + assertTrue(bean.getBean().isAlternative()); + assertEquals(11, bean.getBean().getPriority()); + + SomeBean instance = bean.get(); + assertNotNull(instance); + assertEquals("intercepted: hello", instance.hello()); + } + + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + @interface ToBeStereotype { + } + + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + @InterceptorBinding + @interface SimpleBinding { + } + + @Interceptor + @Priority(1) + @SimpleBinding + static class SimpleInterceptor { + @AroundInvoke + public Object invoke(InvocationContext context) throws Exception { + return "intercepted: " + context.proceed(); + } + } + + @ToBeStereotype + static class SomeBean { + public String hello() { + return "hello"; + } + } + + static class MyStereotypeRegistrar implements StereotypeRegistrar { + @Override + public Set getAdditionalStereotypes() { + return Set.of(DotName.createSimple(ToBeStereotype.class.getName())); + } + } + + static class MyAnnotationTrasnformer implements AnnotationsTransformer { + @Override + public boolean appliesTo(AnnotationTarget.Kind kind) { + return kind == AnnotationTarget.Kind.CLASS; + } + + @Override + public void transform(TransformationContext transformationContext) { + if (transformationContext.getTarget().asClass().name() + .equals(DotName.createSimple(ToBeStereotype.class.getName()))) { + transformationContext.transform() + .add(ApplicationScoped.class) + .add(SimpleBinding.class) + .add(Named.class) + .add(Alternative.class) + .add(Priority.class, AnnotationValue.createIntegerValue("value", 11)) + .done(); + } + } + + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeArcPriorityTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeArcPriorityTest.java new file mode 100644 index 0000000000000..79bbb23b75b92 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeArcPriorityTest.java @@ -0,0 +1,137 @@ +package io.quarkus.arc.test.stereotypes; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.AlternativePriority; +import io.quarkus.arc.Arc; +import io.quarkus.arc.Priority; +import io.quarkus.arc.test.ArcTestContainer; +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; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +// copy of `StereotypeAlternativeTest` that uses ArC `@Priority` and `@AlternativePriority` +// instead of Jakarta Common Annotations `@Priority` +public class StereotypeAlternativeArcPriorityTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(BeAlternative.class, BeAlternativeWithPriority.class, + NonAlternative.class, IamAlternative.class, NotAtAllAlternative.class, IamAlternativeWithPriority.class, + ToBeOverridenFoo.class, MockedFoo.class, MockedFooWithExplicitPriority.class, Mock.class); + + @Test + public void testStereotype() { + assertEquals("OK", Arc.container().instance(NonAlternative.class).get().getId()); + assertEquals("OK", Arc.container().instance(NotAtAllAlternative.class).get().getId()); + + assertEquals(MockedFooWithExplicitPriority.class.getSimpleName(), + Arc.container().instance(ToBeOverridenFoo.class).get().ping()); + assertEquals(MockedFoo.class.getSimpleName(), Arc.container().instance(MockedFoo.class).get().ping()); + } + + @Alternative + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface BeAlternative { + } + + @AlternativePriority(1) + @Stereotype + @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD }) + @Retention(RetentionPolicy.RUNTIME) + public @interface BeAlternativeWithPriority { + } + + @Dependent + static class NonAlternative { + + public String getId() { + return "NOK"; + } + + } + + @Priority(1) + @BeAlternative + static class IamAlternative extends NonAlternative { + + @Override + public String getId() { + return "OK"; + } + + } + + @Dependent + static class NotAtAllAlternative { + + public String getId() { + return "NOK"; + } + + } + + @BeAlternativeWithPriority + static class IamAlternativeWithPriority extends NotAtAllAlternative { + + @Override + public String getId() { + return "OK"; + } + + } + + @Dependent + static class ToBeOverridenFoo { + + public String ping() { + return ToBeOverridenFoo.class.getSimpleName(); + } + } + + @Mock + // should not be selected because of lower priority (has 1) + static class MockedFoo extends ToBeOverridenFoo { + + @Override + public String ping() { + return MockedFoo.class.getSimpleName(); + } + + } + + @Mock + @Priority(2) + static class MockedFooWithExplicitPriority extends ToBeOverridenFoo { + + @Override + public String ping() { + return MockedFooWithExplicitPriority.class.getSimpleName(); + } + } + + /** + * The built-in stereotype intended for use with mock beans injected in tests. + */ + @Priority(1) + @Dependent + @Alternative + @Stereotype + @Target({ TYPE, METHOD, FIELD }) + @Retention(RUNTIME) + public @interface Mock { + + } + +}