Skip to content

Commit

Permalink
ArC: treat additional scope annotations correctly, even if there's no…
Browse files Browse the repository at this point in the history
… corresponding context

It makes no sense to register an additional scope annotation without
registering a corresponding context, but the CDI TCK uses that and
it isn't terribly hard to support.

With this commit, ArC treats additional bean defining annotations
that are meta-annotated `@Scope` or `@NormalScope` as scope annotations,
even if there is no corresponding context.

This commit also fixes the type discovery implementations to also discover
scope annotations and register them as additional bean defining annotations.
  • Loading branch information
Ladicek committed May 19, 2023
1 parent 806b8dc commit 087b2c4
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ public class BeanDeployment {

this.excludeTypes = builder.excludeTypes != null ? new ArrayList<>(builder.excludeTypes) : Collections.emptyList();

findScopeAnnotations(DotNames.SCOPE, beanDefiningAnnotations);
findScopeAnnotations(DotNames.NORMAL_SCOPE, beanDefiningAnnotations);

qualifierNonbindingMembers = new HashMap<>();
qualifiers = findQualifiers();
for (QualifierRegistrar registrar : builder.qualifierRegistrars) {
Expand Down Expand Up @@ -721,10 +724,33 @@ private void buildContextPut(String key, Object value) {
}

private boolean isRuntimeAnnotationType(ClassInfo annotationType) {
if (!annotationType.isAnnotation()) {
return false;
}
AnnotationInstance retention = annotationType.declaredAnnotation(Retention.class);
return retention != null && "RUNTIME".equals(retention.value().asEnum());
}

// this method finds and registers custom scope annotations prior to registering custom contexts;
// context registration will later overwrite the corresponding entries in the map, which leaves
// custom scope annotations without a context, and that makes no sense _except_ for the CDI TCK
private void findScopeAnnotations(DotName scopeMetaAnnotation,
Map<DotName, BeanDefiningAnnotation> beanDefiningAnnotations) {
for (AnnotationInstance scope : beanArchiveImmutableIndex.getAnnotations(scopeMetaAnnotation)) {
ClassInfo scopeClass = scope.target().asClass();
if (!isRuntimeAnnotationType(scopeClass)) {
continue;
}
if (isExcluded(scopeClass)) {
continue;
}
DotName scopeName = scopeClass.name();
if (BuiltinScope.from(scopeName) == null && !beanDefiningAnnotations.containsKey(scopeName)) {
beanDefiningAnnotations.put(scopeName, new BeanDefiningAnnotation(scopeName));
}
}
}

private Map<DotName, ClassInfo> findQualifiers() {
Map<DotName, ClassInfo> qualifiers = new HashMap<>();
for (AnnotationInstance qualifier : beanArchiveImmutableIndex.getAnnotations(DotNames.QUALIFIER)) {
Expand Down Expand Up @@ -870,7 +896,7 @@ private Map<DotName, StereotypeInfo> findStereotypes(Map<DotName, ClassInfo> int
return stereotypes;
}

private static ScopeInfo getScope(DotName scopeAnnotationName,
private ScopeInfo getScope(DotName scopeAnnotationName,
Map<ScopeInfo, Function<MethodCreator, ResultHandle>> customContexts) {
BuiltinScope builtin = BuiltinScope.from(scopeAnnotationName);
if (builtin != null) {
Expand All @@ -881,6 +907,19 @@ private static ScopeInfo getScope(DotName scopeAnnotationName,
return customScope;
}
}
if (beanDefiningAnnotations.containsKey(scopeAnnotationName)) {
// custom scope annotations without a registered context make no sense,
// but the CDI TCK uses them
ClassInfo clazz = beanArchiveImmutableIndex.getClassByName(scopeAnnotationName);
if (clazz != null) {
boolean inherited = clazz.hasDeclaredAnnotation(DotNames.INHERITED);
if (clazz.hasDeclaredAnnotation(DotNames.SCOPE)) {
return new ScopeInfo(scopeAnnotationName, false, inherited);
} else if (clazz.hasDeclaredAnnotation(DotNames.NORMAL_SCOPE)) {
return new ScopeInfo(scopeAnnotationName, true, inherited);
}
}
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import jakarta.decorator.Delegate;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Initialized;
import jakarta.enterprise.context.NormalScope;
import jakarta.enterprise.context.control.ActivateRequestContext;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.event.Observes;
Expand Down Expand Up @@ -44,6 +45,7 @@
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.inject.Qualifier;
import jakarta.inject.Scope;
import jakarta.inject.Singleton;
import jakarta.interceptor.AroundConstruct;
import jakarta.interceptor.AroundInvoke;
Expand Down Expand Up @@ -104,6 +106,8 @@ public final class DotNames {
public static final DotName EVENT_METADATA = create(EventMetadata.class);
public static final DotName ALTERNATIVE = create(Alternative.class);
public static final DotName DEFAULT_BEAN = create(DefaultBean.class);
public static final DotName SCOPE = create(Scope.class);
public static final DotName NORMAL_SCOPE = create(NormalScope.class);
public static final DotName SINGLETON = create(Singleton.class);
public static final DotName APPLICATION_SCOPED = create(ApplicationScoped.class);
public static final DotName STEREOTYPE = create(Stereotype.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
Expand All @@ -19,10 +18,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import jakarta.enterprise.inject.Stereotype;
import jakarta.inject.Qualifier;
import jakarta.interceptor.InterceptorBinding;

import org.jboss.arquillian.container.spi.client.container.DeploymentException;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
Expand All @@ -44,6 +39,7 @@
import io.quarkus.arc.processor.BeanArchives;
import io.quarkus.arc.processor.BeanDefiningAnnotation;
import io.quarkus.arc.processor.BeanProcessor;
import io.quarkus.arc.processor.DotNames;
import io.quarkus.arc.processor.bcextensions.ExtensionsEntryPoint;

final class Deployer {
Expand Down Expand Up @@ -133,8 +129,8 @@ private void generate() throws IOException, ExecutionException, InterruptedExcep
.setTransformUnproxyableClasses(false)
.setRemoveUnusedBeans(false)
.setBuildCompatibleExtensions(buildCompatibleExtensions)
.setAdditionalBeanDefiningAnnotations(Set.of(new BeanDefiningAnnotation(
DotName.createSimple(ExtraBean.class.getName()), null)))
.setAdditionalBeanDefiningAnnotations(Set.of(
new BeanDefiningAnnotation(DotName.createSimple(ExtraBean.class))))
.addAnnotationTransformer(new AnnotationsTransformer() {
@Override
public boolean appliesTo(AnnotationTarget.Kind kind) {
Expand Down Expand Up @@ -231,12 +227,12 @@ private IndexView buildImmutableBeanArchiveIndex(Index applicationIndex, Set<Str
}
}

// 4. CDI-related annotations (qualifiers, interceptor bindings, stereotypes)
// 4. CDI-related annotations (scopes, qualifiers, interceptor bindings, stereotypes)
// CDI recognizes them even if they come from an archive that is not a bean archive
Set<Class<? extends Annotation>> metaAnnotations = Set.of(Qualifier.class, InterceptorBinding.class, Stereotype.class);
for (Class<? extends Annotation> metaAnnotation : metaAnnotations) {
DotName metaAnnotationName = DotName.createSimple(metaAnnotation.getName());
for (AnnotationInstance annotation : applicationIndex.getAnnotations(metaAnnotationName)) {
Set<DotName> metaAnnotations = Set.of(DotNames.SCOPE, DotNames.NORMAL_SCOPE, DotNames.QUALIFIER,
DotNames.INTERCEPTOR_BINDING, DotNames.STEREOTYPE);
for (DotName metaAnnotation : metaAnnotations) {
for (AnnotationInstance annotation : applicationIndex.getAnnotations(metaAnnotation)) {
if (annotation.target().kind().equals(AnnotationTarget.Kind.CLASS)) {
String annotationClass = annotation.target().asClass().name().toString();
String classFile = annotationClass.replace('.', '/') + ".class";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@
</class>
<class name="org.jboss.cdi.tck.tests.definition.scope.ScopeDefinitionTest">
<methods>
<exclude name="testScopeTypeHasCorrectTarget"/>
<exclude name="testScopeTypeDeclaresScopeTypeAnnotation"/>
<exclude name="testScopeTypesAreExtensible"/>
<exclude name="testScopeTypeDeclaredInheritedIsBlockedByIntermediateScopeTypeNotMarkedInherited"/>
</methods>
</class>
Expand Down Expand Up @@ -106,11 +103,6 @@
<exclude name="testInterceptorsImplementInterceptorInterface"/>
</methods>
</class>
<class name="org.jboss.cdi.tck.tests.definition.scope.inOtherBda.ScopeDefinedInOtherBDATest">
<methods>
<exclude name="testCustomScopeInOtherBDAisBeanDefiningAnnotation"/>
</methods>
</class>
<class name="org.jboss.cdi.tck.tests.lookup.dynamic.handle.InstanceHandleTest">
<methods>
<exclude name="testGetHandle"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.quarkus.arc.test.bean.scope;

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

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.enterprise.context.NormalScope;
import jakarta.enterprise.inject.spi.Bean;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.inject.Scope;

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

import io.quarkus.arc.Arc;
import io.quarkus.arc.test.ArcTestContainer;

public class CustomScopeWithoutContextTest {
@RegisterExtension
ArcTestContainer container = new ArcTestContainer(MyScope.class, MyNormalScope.class, MyBean.class, MyNormalBean.class);

@Test
public void pseudoScope() {
BeanManager bm = Arc.container().beanManager();
Bean<MyBean> bean = (Bean<MyBean>) bm.resolve(bm.getBeans(MyBean.class));
assertEquals(MyScope.class, bean.getScope());
}

@Test
public void normalScope() {
BeanManager bm = Arc.container().beanManager();
Bean<MyNormalBean> bean = (Bean<MyNormalBean>) bm.resolve(bm.getBeans(MyNormalBean.class));
assertEquals(MyNormalScope.class, bean.getScope());
}

@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Scope
@interface MyScope {
}

@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@NormalScope
@interface MyNormalScope {
}

@MyScope
static class MyBean {
}

@MyNormalScope
static class MyNormalBean {
}
}

0 comments on commit 087b2c4

Please sign in to comment.