Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve stereotypes support #26264

Merged
merged 1 commit into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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());
Ladicek marked this conversation as resolved.
Show resolved Hide resolved
}
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