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

ArC: add InjectableInstance.getActive() and listActive() #43803

Merged
merged 1 commit into from
Oct 11, 2024
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
15 changes: 15 additions & 0 deletions docs/src/main/asciidoc/cdi-integration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,21 @@ if (foo.getHandle().getBean().isActive()) {
}
----

If you want to consume only active beans, you can inject an `InjectableInstance<>` and call `getActive()` to get the single instance or `listActive()` to get all instances:

[source,java]
----
import io.quarkus.arc.InjectableInstance;

@Inject
@Any
InjectableInstance<Foo> foos;

for (Foo foo : foos.listActive())
...
}
----

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

Expand Down
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 @@ -2,11 +2,14 @@

import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.AmbiguousResolutionException;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.UnsatisfiedResolutionException;
import jakarta.enterprise.util.TypeLiteral;

/**
Expand Down Expand Up @@ -75,11 +78,37 @@ default T orElse(T other) {
* If there is exactly one bean that matches the required type and qualifiers, returns the instance, otherwise returns
* {@code null}.
*
* @param other
* @return the bean instance or {@code null}
*/
default T orNull() {
return orElse(null);
}

/**
* Returns exactly one instance of an {@linkplain InjectableBean#checkActive() active} bean that matches
* the required type and qualifiers. If no active bean matches, or if more than one active bean matches,
* throws an exception.
*
* @return the single instance of an active matching bean
*/
default T getActive() {
List<T> list = listActive();
if (list.size() == 1) {
return list.get(0);
} else if (list.isEmpty()) {
throw new UnsatisfiedResolutionException("No active bean found");
} else {
throw new AmbiguousResolutionException("More than one active bean found: " + list);
}
}

/**
* Returns the list of instances of {@linkplain InjectableBean#checkActive() active} beans that match
* the required type and qualifiers, sorter in priority order (higher priority goes first).
*
* @return the list of instances of matching active beans
*/
default List<T> listActive() {
return handlesStream().filter(it -> it.getBean().isActive()).map(InstanceHandle::get).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package io.quarkus.arc.test.instance;

import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.AmbiguousResolutionException;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.spi.InjectionPoint;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassType;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.ActiveResult;
import io.quarkus.arc.Arc;
import io.quarkus.arc.BeanCreator;
import io.quarkus.arc.BeanDestroyer;
import io.quarkus.arc.InjectableInstance;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.arc.test.ArcTestContainer;
import io.quarkus.arc.test.MyQualifier;

public class ActiveMultipleTest {
@RegisterExtension
public ArcTestContainer container = ArcTestContainer.builder()
.beanClasses(Service.class, MyQualifier.class, Consumer.class)
.beanRegistrars(context -> {
context.configure(ServiceAlpha.class)
.types(Service.class, ServiceAlpha.class)
.scope(Singleton.class)
.checkActive(AlwaysActive.class)
.creator(AlphaCreator.class)
.done();

context.configure(ServiceBravo.class)
.types(Service.class, ServiceBravo.class)
.qualifiers(AnnotationInstance.builder(MyQualifier.class).build())
.scope(Dependent.class)
.priority(5)
.addInjectionPoint(ClassType.create(InjectionPoint.class))
.checkActive(NeverActive.class)
.creator(BravoCreator.class)
.destroyer(BravoDestroyer.class)
.done();

context.configure(ServiceCharlie.class)
.types(Service.class, ServiceCharlie.class)
.scope(Singleton.class)
.checkActive(NeverActive.class)
.creator(CharlieCreator.class)
.done();

context.configure(ServiceDelta.class)
.types(Service.class, ServiceDelta.class)
.qualifiers(AnnotationInstance.builder(MyQualifier.class).build())
.scope(Dependent.class)
.priority(10)
.addInjectionPoint(ClassType.create(InjectionPoint.class))
.checkActive(AlwaysActive.class)
.creator(DeltaCreator.class)
.destroyer(DeltaDestroyer.class)
.done();
})
.build();

@Test
public void testListActive() {
Consumer consumer = Arc.container().select(Consumer.class).get();

List<Service> activeServices = consumer.services.listActive();
assertEquals(2, activeServices.size());
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(() -> activeServices.remove(0));
// ServiceDelta has higher priority
Service delta = activeServices.get(0);
assertEquals("delta", delta.ping());
assertEquals("alpha", activeServices.get(1).ping());
assertNotNull(delta.getInjectionPoint());
assertEquals(Service.class, delta.getInjectionPoint().getType());
}

@Test
public void testGetActive() {
Consumer consumer = Arc.container().select(Consumer.class).get();

assertThrows(AmbiguousResolutionException.class, () -> {
consumer.services.getActive();
});
}

@Singleton
public static class Consumer {
@Inject
@Any
InjectableInstance<Service> services;
}

interface Service {
String ping();

default InjectionPoint getInjectionPoint() {
return null;
}
}

static class ServiceAlpha implements Service {
public String ping() {
return "alpha";
}
}

static class AlphaCreator implements BeanCreator<ServiceAlpha> {
@Override
public ServiceAlpha create(SyntheticCreationalContext<ServiceAlpha> context) {
return new ServiceAlpha();
}
}

static class ServiceBravo implements Service {
private final InjectionPoint injectionPoint;

ServiceBravo(InjectionPoint injectionPoint) {
this.injectionPoint = injectionPoint;
}

public String ping() {
return "bravo";
}

@Override
public InjectionPoint getInjectionPoint() {
return injectionPoint;
}
}

static class BravoCreator implements BeanCreator<ServiceBravo> {
@Override
public ServiceBravo create(SyntheticCreationalContext<ServiceBravo> context) {
InjectionPoint ip = context.getInjectedReference(InjectionPoint.class);
return new ServiceBravo(ip);
}
}

static class BravoDestroyer implements BeanDestroyer<ServiceBravo> {
static boolean DESTROYED = false;

@Override
public void destroy(ServiceBravo instance, CreationalContext<ServiceBravo> creationalContext,
Map<String, Object> params) {
DESTROYED = true;
}
}

static class ServiceCharlie implements Service {
@Override
public String ping() {
return "charlie";
}
}

static class CharlieCreator implements BeanCreator<ServiceCharlie> {
@Override
public ServiceCharlie create(SyntheticCreationalContext<ServiceCharlie> context) {
return new ServiceCharlie();
}
}

static class ServiceDelta implements Service {
private final InjectionPoint injectionPoint;

ServiceDelta(InjectionPoint injectionPoint) {
this.injectionPoint = injectionPoint;
}

@Override
public String ping() {
return "delta";
}

@Override
public InjectionPoint getInjectionPoint() {
return injectionPoint;
}
}

static class DeltaCreator implements BeanCreator<ServiceDelta> {
@Override
public ServiceDelta create(SyntheticCreationalContext<ServiceDelta> context) {
InjectionPoint ip = context.getInjectedReference(InjectionPoint.class);
return new ServiceDelta(ip);
}
}

static class DeltaDestroyer implements BeanDestroyer<ServiceDelta> {
static boolean DESTROYED = false;

@Override
public void destroy(ServiceDelta instance, CreationalContext<ServiceDelta> creationalContext,
Map<String, Object> params) {
DESTROYED = true;
}
}

static class AlwaysActive implements Supplier<ActiveResult> {
@Override
public ActiveResult get() {
return ActiveResult.active();
}
}

static class NeverActive implements Supplier<ActiveResult> {
@Override
public ActiveResult get() {
return ActiveResult.inactive("");
}
}
}
Loading
Loading