Skip to content

Commit

Permalink
ArC: add the @Active qualifier for List<> injection points
Browse files Browse the repository at this point in the history
This is similar to the `@All` qualifier, both in spirit and implementation,
it just filters out inactive beans.

This commit also adds the `ArcContainer.listActive()` methods, again similar
to the `listAll()` methods, just with additional filtering.
  • Loading branch information
Ladicek committed Oct 10, 2024
1 parent 30880a4 commit 614630b
Show file tree
Hide file tree
Showing 17 changed files with 668 additions and 46 deletions.
18 changes: 18 additions & 0 deletions docs/src/main/asciidoc/cdi-integration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ public class TestRecorder {
----
<1> Pass a contextual reference of `Bar` to the constructor of `Foo`.

[[inactive_synthetic_beans]]
=== Inactive Synthetic Beans

In the case when one needs to register multiple synthetic beans at build time but only wants a subset of them active at runtime, it is useful to be able to mark a synthetic bean as _inactive_.
Expand Down Expand Up @@ -486,6 +487,23 @@ if (foo.getHandle().getBean().isActive()) {
}
----

If you want to consume only active beans, you can inject a `List<>` of the beans with the `@Active` qualifier:

[source,java]
----
import io.quarkus.arc.Active;
@Inject
@Active
List<Foo> foos;
for (Foo foo : foos)
...
}
----

This is similar to the `@All` qualifier, but filters out the inactive beans.

[[synthetic_observers]]
== Use Case - Synthetic Observers

Expand Down
6 changes: 5 additions & 1 deletion docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ An _unused_ bean:
* is not eligible for injection to any injection point in the dependency tree of _unremovable_ beans, and
* does not declare any producer which is eligible for injection to any injection point in the dependency tree, and
* is not eligible for injection into any `jakarta.enterprise.inject.Instance` or `jakarta.inject.Provider` injection point, and
* is not eligible for injection into any <<injecting-multiple-bean-instances-intuitively,`@Inject @All List<>`>> injection point.
* is not eligible for injection into any <<injecting-multiple-bean-instances-intuitively,`@Inject @All List<>`>> or `@Inject @Active List<>` injection point.

Unused interceptors and decorators are not associated with any bean.

Expand Down Expand Up @@ -1054,6 +1054,10 @@ NOTE: Neither a type variable nor a wildcard is a legal type parameter for an `@

TIP: It is also possible to obtain the list of all bean instance handles programmatically via the `Arc.container().listAll()` methods.

NOTE: if you use link:cdi-integration#inactive_synthetic_beans[inactive beans], you can use the `@Active` qualifier.
It works just like the `@All` qualifier, but filters out inactive beans.
The `Arc.container().listAll()` methods also have a filtering alternative: `listActive()`.

=== Ignoring Class-Level Interceptor Bindings for Methods and Constructors

If a managed bean declares interceptor binding annotations on the class level, the corresponding `@AroundInvoke` interceptors will apply to all business methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.jboss.jandex.Type;
import org.jboss.logging.Logger;

import io.quarkus.arc.Active;
import io.quarkus.arc.All;
import io.quarkus.arc.Lock;
import io.quarkus.arc.impl.ActivateRequestContextInterceptor;
Expand Down Expand Up @@ -85,6 +86,7 @@ private static IndexView buildAdditionalIndex() {
index(indexer, Model.class.getName());
index(indexer, Lock.class.getName());
index(indexer, All.class.getName());
index(indexer, Active.class.getName());
index(indexer, Identified.class.getName());
// Arc built-in beans
index(indexer, ActivateRequestContextInterceptor.class.getName());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public enum BuiltinBean {
DotNames.OBJECT),
EVENT_METADATA(Generator.NOOP, BuiltinBean::cdiAndRawTypeMatches,
BuiltinBean::validateEventMetadata, DotNames.EVENT_METADATA),
LIST(BuiltinBean::generateListBytecode,
(ip, names) -> cdiAndRawTypeMatches(ip, DotNames.LIST) && ip.getRequiredQualifier(DotNames.ALL) != null,
LIST(BuiltinBean::generateListBytecode, (ip, names) -> cdiAndRawTypeMatches(ip, DotNames.LIST)
&& (ip.getRequiredQualifier(DotNames.ALL) != null || ip.getRequiredQualifier(DotNames.ACTIVE) != null),
BuiltinBean::validateList, DotNames.LIST),
INTERCEPTION_PROXY(BuiltinBean::generateInterceptionProxyBytecode,
BuiltinBean::cdiAndRawTypeMatches, BuiltinBean::validateInterceptionProxy,
Expand Down Expand Up @@ -375,6 +375,7 @@ private static void generateListBytecode(GeneratorContext ctx) {
// List<T> or List<InstanceHandle<T>
ResultHandle requiredType;
ResultHandle usesInstanceHandle;
ResultHandle onlyActive;
Type type = ctx.injectionPoint.getType().asParameterizedType().arguments().get(0);
if (type.name().equals(DotNames.INSTANCE_HANDLE)) {
requiredType = Types.getTypeHandle(mc, type.asParameterizedType().arguments().get(0));
Expand All @@ -383,6 +384,11 @@ private static void generateListBytecode(GeneratorContext ctx) {
requiredType = Types.getTypeHandle(mc, type);
usesInstanceHandle = mc.load(false);
}
if (ctx.injectionPoint.getRequiredQualifier(DotNames.ACTIVE) != null) {
onlyActive = mc.load(true);
} else {
onlyActive = mc.load(false);
}

ResultHandle qualifiers = BeanGenerator.collectInjectionPointQualifiers(
ctx.beanDeployment,
Expand All @@ -408,13 +414,12 @@ private static void generateListBytecode(GeneratorContext ctx) {
default:
throw new IllegalStateException("Unsupported target info: " + ctx.targetInfo);
}
ResultHandle listProvider = ctx.constructor.newInstance(
MethodDescriptor.ofConstructor(ListProvider.class, java.lang.reflect.Type.class, java.lang.reflect.Type.class,
Set.class,
InjectableBean.class, Set.class, Member.class, int.class, boolean.class, boolean.class),
ResultHandle listProvider = ctx.constructor.newInstance(MethodDescriptor.ofConstructor(ListProvider.class,
java.lang.reflect.Type.class, java.lang.reflect.Type.class, Set.class, InjectableBean.class,
Set.class, Member.class, int.class, boolean.class, boolean.class, boolean.class),
requiredType, injectionPointType, qualifiers, beanHandle, annotationsHandle, javaMemberHandle,
ctx.constructor.load(ctx.injectionPoint.getPosition()),
ctx.constructor.load(ctx.injectionPoint.isTransient()), usesInstanceHandle);
ctx.constructor.load(ctx.injectionPoint.getPosition()), ctx.constructor.load(ctx.injectionPoint.isTransient()),
usesInstanceHandle, onlyActive);
ResultHandle listProviderSupplier = ctx.constructor.newInstance(
MethodDescriptors.FIXED_VALUE_SUPPLIER_CONSTRUCTOR, listProvider);
ctx.constructor.writeInstanceField(
Expand Down Expand Up @@ -460,13 +465,20 @@ private static void validateList(ValidatorContext ctx) {
ctx.errors.accept(new DefinitionException(
"An injection point of raw type is defined: " + ctx.injectionPoint.getTargetInfo()));
} else {
String qualifier;
if (ctx.injectionPoint.getRequiredQualifier(DotNames.ALL) != null) {
qualifier = "@All";
} else {
qualifier = "@Active";
}

// Note that at this point we can be sure that the required type is List<>
Type typeParam = ctx.injectionPoint.getType().asParameterizedType().arguments().get(0);
if (typeParam.kind() == Type.Kind.WILDCARD_TYPE) {
if (ctx.injectionPoint.isSynthetic()) {
ctx.errors.accept(new DefinitionException(
"Wildcard is not a legal type argument for a synthetic @All List<?> injection point used in: "
+ ctx.injectionTarget.toString()));
"Wildcard is not a legal type argument for a synthetic " + qualifier
+ " List<?> injection point used in: " + ctx.injectionTarget.toString()));
return;
}
ClassInfo declaringClass;
Expand All @@ -477,7 +489,8 @@ private static void validateList(ValidatorContext ctx) {
}
if (isKotlinClass(declaringClass)) {
ctx.errors.accept(new DefinitionException(
"kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: "
"kotlin.collections.List cannot be used together with the " + qualifier
+ " qualifier, please use MutableList or java.util.List instead: "
+ ctx.injectionPoint.getTargetInfo()));
} else {
ctx.errors.accept(new DefinitionException(
Expand All @@ -487,6 +500,13 @@ private static void validateList(ValidatorContext ctx) {
ctx.errors.accept(new DefinitionException(
"Type variable is not a legal type argument for: " + ctx.injectionPoint.getTargetInfo()));
}

if (ctx.injectionPoint.getRequiredQualifier(DotNames.ALL) != null
&& ctx.injectionPoint.getRequiredQualifier(DotNames.ACTIVE) != null) {
ctx.errors.accept(new DefinitionException(
"Only one of @All and @Active may be declared on a List<> injection point: "
+ ctx.injectionPoint.getTargetInfo()));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import io.quarkus.arc.Active;
import io.quarkus.arc.All;
import io.quarkus.arc.ArcInvocationContext;
import io.quarkus.arc.BindingsSource;
Expand Down Expand Up @@ -139,6 +140,7 @@ public final class DotNames {
public static final DotName VETOED_PRODUCER = create(VetoedProducer.class);
public static final DotName LIST = create(List.class);
public static final DotName ALL = create(All.class);
public static final DotName ACTIVE = create(Active.class);
public static final DotName IDENTIFIED = create(Identified.class);
public static final DotName INSTANCE_HANDLE = create(InstanceHandle.class);
public static final DotName NO_CLASS_INTERCEPTORS = create(NoClassInterceptors.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ static Set<BeanInfo> findRemovableBeans(BeanResolver beanResolver, Collection<Be
// Collect all:
// - injected beans; skip delegate injection points and injection points that resolve to a built-in bean
// - Instance<> injection points
// - @All List<> injection points
// - @All/@Active List<> injection points
Set<BeanInfo> injected = new HashSet<>();
List<InjectionPointInfo> instanceInjectionPoints = new ArrayList<>();
List<TypeAndQualifiers> listAllInjectionPoints = new ArrayList<>();
Expand All @@ -58,7 +58,7 @@ static Set<BeanInfo> findRemovableBeans(BeanResolver beanResolver, Collection<Be
Set<AnnotationInstance> qualifiers = new HashSet<>(injectionPoint.getRequiredQualifiers());
for (Iterator<AnnotationInstance> it = qualifiers.iterator(); it.hasNext();) {
AnnotationInstance qualifier = it.next();
if (qualifier.name().equals(DotNames.ALL)) {
if (qualifier.name().equals(DotNames.ALL) || qualifier.name().equals(DotNames.ACTIVE)) {
it.remove();
}
}
Expand Down Expand Up @@ -112,11 +112,11 @@ static Set<BeanInfo> findRemovableBeans(BeanResolver beanResolver, Collection<Be
continue test;
}
}
// @All List<Foo>
// @All/@Active List<Foo>
for (TypeAndQualifiers tq : listAllInjectionPoints) {
if (Beans.hasQualifiers(bean, tq.qualifiers)
&& beanResolver.matchesType(bean, tq.type)) {
LOG.debugf("Unremovable - @All List: %s", bean);
LOG.debugf("Unremovable - @All/@Active List: %s", bean);
continue test;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.quarkus.arc;

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 java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.List;

import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.util.AnnotationLiteral;
import jakarta.inject.Qualifier;

/**
* The container provides a synthetic bean for an injection point with the required type {@link List} and the required qualifier
* {@link Active}. The injected instance is an immutable list of the contextual references of the disambiguated beans,
* without beans that are currently {@linkplain InjectableBean#checkActive() inactive}.
*
* <pre>
* &#064;ApplicationScoped
* public class Processor {
*
* &#064;Inject
* &#064;Active
* List&lt;Service&gt; services;
* }
* </pre>
*
* If the injection point declares no other qualifier then {@link Any} is used, i.e. the behavior is equivalent to
* {@code @Inject @Any Instance<Service> services} and subsequent filtering on the active status. The semantics is the same
* as for the {@link Instance#iterator()}, i.e. the container attempts to resolve ambiguities. In general, if multiple beans
* are eligible then the container eliminates all beans that are:
* <ul>
* <li>not alternatives, except for producer methods and fields of beans that are alternatives,</li>
* <li>default beans.</li>
* </ul>
*
* You can also inject a list of bean instances wrapped in {@link InstanceHandle}. This can be useful if you need to inspect the
* bean metadata.
*
* <pre>
* &#064;ApplicationScoped
* public class Processor {
*
* &#064;Inject
* &#064;Active
* List&lt;InstanceHandle&lt;Service&gt;&gt; services;
*
* void doSomething() {
* for (InstanceHandle&lt;Service&gt; handle : services) {
* if (handle.getBean().getScope().equals(Dependent.class)) {
* handle.get().process();
* break;
* }
* }
* }
* }
* </pre>
*
* The list is sorted by {@link InjectableBean#getPriority()}. Higher priority goes first.
*
* @see jakarta.annotation.Priority
*/
@Qualifier
@Retention(RUNTIME)
@Target({ TYPE, FIELD, METHOD, PARAMETER })
public @interface Active {
/**
* Supports inline instantiation of this qualifier.
*/
final class Literal extends AnnotationLiteral<Active> implements Active {
public static final Literal INSTANCE = new Literal();

private static final long serialVersionUID = 1L;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
*
* The list is sorted by {@link InjectableBean#getPriority()}. Higher priority goes first.
*
* @see Priority
* @see jakarta.annotation.Priority
*/
@Qualifier
@Retention(RUNTIME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,46 @@ public interface ArcContainer {
*/
<X> List<InstanceHandle<X>> listAll(Type type, Annotation... qualifiers);

/**
* List all <em>active</em> beans matching the required type and qualifiers.
* <p>
* Instances of dependent scoped beans should be explicitly destroyed with {@link InstanceHandle#destroy()}.
* <p>
* The list is sorted by {@link InjectableBean#getPriority()}. Higher priority goes first.
*
* @param <T>
* @param type
* @param qualifiers
* @return the list of handles for the disambiguated <em>active</em> beans
* @see Active
*/
<T> List<InstanceHandle<T>> listActive(Class<T> type, Annotation... qualifiers);

/**
* List all <em>active</em> beans matching the required type and qualifiers.
* <p>
* Instances of dependent scoped beans should be explicitly destroyed with {@link InstanceHandle#destroy()}.
* <p>
* The list of is sorted by {@link InjectableBean#getPriority()}. Higher priority goes first.
*
* @param <T>
* @param type
* @param qualifiers
* @return the list of handles for the disambiguated <em>active</em> beans
* @see Active
*/
<T> List<InstanceHandle<T>> listActive(TypeLiteral<T> type, Annotation... qualifiers);

/**
*
* @param <X>
* @param type
* @param qualifiers
* @return the list of handles for the disambiguated <em>active</em> beans
* @see #listActive(Class, Annotation...)
*/
<X> List<InstanceHandle<X>> listActive(Type type, Annotation... qualifiers);

/**
* Returns true if Arc container is running.
* This can be used as a quick check to determine CDI availability in Quarkus.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ default boolean isSuppressed() {
* If no priority annotation is used then a bean has the priority of value 0.
* <p>
* This priority is used to sort the resolved beans when performing programmatic lookup via
* {@link Instance} or when injecting a list of beans by means of the {@link All} qualifier.
* {@link Instance} or when injecting a list of beans by means of the {@link All} or {@link Active} qualifier.
*
* @return the priority
* @see jakarta.annotation.Priority
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,23 @@ public <T> List<InstanceHandle<T>> listAll(TypeLiteral<T> type, Annotation... qu
@Override
public <X> List<InstanceHandle<X>> listAll(Type type, Annotation... qualifiers) {
return Instances.listOfHandles(CurrentInjectionPointProvider.EMPTY_SUPPLIER, type, Set.of(qualifiers),
new CreationalContextImpl<>(null));
new CreationalContextImpl<>(null), false);
}

@Override
public <T> List<InstanceHandle<T>> listActive(Class<T> type, Annotation... qualifiers) {
return listActive((Type) type, qualifiers);
}

@Override
public <T> List<InstanceHandle<T>> listActive(TypeLiteral<T> type, Annotation... qualifiers) {
return listActive(type.getType(), qualifiers);
}

@Override
public <X> List<InstanceHandle<X>> listActive(Type type, Annotation... qualifiers) {
return Instances.listOfHandles(CurrentInjectionPointProvider.EMPTY_SUPPLIER, type, Set.of(qualifiers),
new CreationalContextImpl<>(null), true);
}

@Override
Expand Down
Loading

0 comments on commit 614630b

Please sign in to comment.