Skip to content

Commit

Permalink
Merge pull request #26264 from Ladicek/stereotype-improvements
Browse files Browse the repository at this point in the history
Improve stereotypes support
  • Loading branch information
mkouba authored Aug 31, 2022
2 parents df3b78a + 320681c commit 56e0b56
Show file tree
Hide file tree
Showing 18 changed files with 607 additions and 53 deletions.
22 changes: 22 additions & 0 deletions docs/src/main/asciidoc/cdi-integration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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<DotName> 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 <<annotations_transformer_build_item,annotation transformation>> to add them.

[[injection_point_transformation]]
== Use Case - Injection Point Transformation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DotName, Collection<AnnotationInstance>> stereotypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -156,6 +157,25 @@ AdditionalBeanBuildItem quarkusApplication(CombinedIndexBuildItem combinedIndex)
.build();
}

@BuildStep
StereotypeRegistrarBuildItem convertLegacyAdditionalStereotypes(List<AdditionalStereotypeBuildItem> buildItems) {
return new StereotypeRegistrarBuildItem(new StereotypeRegistrar() {
@Override
public Set<DotName> getAdditionalStereotypes() {
Set<DotName> 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(
Expand All @@ -169,7 +189,7 @@ public ContextRegistrationPhaseBuildItem initialize(
List<ObserverTransformerBuildItem> observerTransformers,
List<InterceptorBindingRegistrarBuildItem> interceptorBindingRegistrars,
List<QualifierRegistrarBuildItem> qualifierRegistrars,
List<AdditionalStereotypeBuildItem> additionalStereotypeBuildItems,
List<StereotypeRegistrarBuildItem> stereotypeRegistrars,
List<ApplicationClassPredicateBuildItem> applicationClassPredicates,
List<AdditionalBeanBuildItem> additionalBeans,
List<ResourceAnnotationBuildItem> resourceAnnotations,
Expand Down Expand Up @@ -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<DotName, Collection<AnnotationInstance>> 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
Expand All @@ -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<BeanInfo>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -32,20 +32,20 @@ public class AutoProducerMethodsProcessor {
@BuildStep
void annotationTransformer(ArcConfig config, BeanArchiveIndexBuildItem beanArchiveIndex,
CustomScopeAnnotationsBuildItem scopes,
List<AdditionalStereotypeBuildItem> additionalStereotypes,
List<StereotypeRegistrarBuildItem> stereotypeRegistrars,
BuildProducer<AnnotationsTransformerBuildItem> annotationsTransformer) throws Exception {
if (!config.autoProducerMethods) {
return;
}
List<DotName> qualifiersAndStereotypes = new ArrayList<>();
Set<DotName> 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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<DotName> SPRING_STEREOTYPE_ANNOTATIONS = Arrays.stream(new DotName[] {
private static final Set<DotName> 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");
Expand Down Expand Up @@ -120,18 +120,24 @@ SpringBeanNameToDotNameBuildItem createBeanNamesMap(BeanArchiveIndexBuildItem be
@BuildStep
AnnotationsTransformerBuildItem beanTransformer(
final BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
final BuildProducer<AdditionalStereotypeBuildItem> additionalStereotypeBuildItemBuildProducer) {
final BuildProducer<StereotypeRegistrarBuildItem> stereotypeRegistrarProducer) {
final IndexView index = beanArchiveIndexBuildItem.getIndex();
final Map<DotName, Set<DotName>> stereotypeScopes = getStereotypeScopes(index);
final Map<DotName, Collection<AnnotationInstance>> instances = new HashMap<>();
final Set<DotName> 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<DotName> getAdditionalStereotypes() {
return stereotypeAnnotations;
}
}));
return new AnnotationsTransformerBuildItem(context -> {
final Collection<AnnotationInstance> annotations = context.getAnnotations();
if (annotations.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -52,6 +76,7 @@ public void done() {
.qualifiers(qualifiers)
.alternative(alternative)
.priority(priority)
.stereotypes(stereotypes)
.name(name)
.creator(creatorConsumer)
.destroyer(destroyerConsumer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,7 +34,8 @@ public abstract class BeanConfiguratorBase<THIS extends BeanConfiguratorBase<THI
protected final Set<Type> types;
protected final Set<AnnotationInstance> qualifiers;
protected ScopeInfo scope;
protected boolean alternative;
protected Boolean alternative;
protected final List<StereotypeInfo> stereotypes;
protected String name;
protected Consumer<MethodCreator> creatorConsumer;
protected Consumer<MethodCreator> destroyerConsumer;
Expand All @@ -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;
}

Expand All @@ -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);
Expand Down Expand Up @@ -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)}.
Expand Down
Loading

0 comments on commit 56e0b56

Please sign in to comment.