diff --git a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/HealthCheckDefaultScopeTest.java b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/HealthCheckDefaultScopeTest.java index c7dd3f7983b3b..c7befa8178085 100644 --- a/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/HealthCheckDefaultScopeTest.java +++ b/extensions/smallrye-health/deployment/src/test/java/io/quarkus/smallrye/health/test/HealthCheckDefaultScopeTest.java @@ -6,6 +6,7 @@ import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; import java.lang.annotation.Retention; @@ -41,11 +42,11 @@ public void testHealth() { when().get("/q/health/live").then() .body("status", is("UP"), "checks.status", contains("UP", "UP"), - "checks.name", contains("noScope", "noScopeStereotype")); + "checks.name", containsInAnyOrder("noScope", "noScopeStereotype")); when().get("/q/health/live").then() .body("status", is("DOWN"), "checks.status", contains("DOWN", "DOWN"), - "checks.name", contains("noScope", "noScopeStereotype")); + "checks.name", containsInAnyOrder("noScope", "noScopeStereotype")); } finally { RestAssured.reset(); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java index 41ffcb3fceda1..ccf3b100789d9 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableInstance.java @@ -1,6 +1,7 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; +import java.util.Iterator; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Instance; import javax.enterprise.util.TypeLiteral; @@ -33,4 +34,19 @@ public interface InjectableInstance extends Instance { */ void clearCache(); + /** + * This method attempts to resolve ambiguities. + *

+ * In general, if multiple beans are eligible then the container eliminates all beans that are: + *

+ * + * @return an iterator over the contextual references of the disambiguated beans + * @see DefaultBean + */ + @Override + Iterator iterator(); + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 11b52ed2909a6..5681fa2b86bb6 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -565,30 +565,38 @@ private static Set> resolve(List> matching) if (matching.isEmpty()) { return Collections.emptySet(); } else if (matching.size() == 1) { - return Collections.singleton(matching.get(0)); - } - - // Try to resolve the ambiguity - List> resolved = new ArrayList<>(matching); - - resolved.removeIf(InjectableBean::isDefaultBean); - if (resolved.size() == 1) { - return Collections.singleton(resolved.get(0)); - } - - resolved.removeIf(not(ArcContainerImpl::isAlternativeOrDeclaredOnAlternative)); - if (resolved.size() == 1) { - return Collections.singleton(resolved.get(0)); - } else if (resolved.size() > 1) { - resolved.sort(ArcContainerImpl::compareAlternativeBeans); - // Keep only the highest priorities - Integer highest = getAlternativePriority(resolved.get(0)); - resolved.removeIf(injectableBean -> !highest.equals(getAlternativePriority(injectableBean))); - if (resolved.size() == 1) { - return Collections.singleton(resolved.get(0)); + return Set.of(matching.get(0)); + } + // Try to resolve the ambiguity and return the set of disambiguated beans + + // First remove the default beans + List> nonDefault = new ArrayList<>(matching); + nonDefault.removeIf(InjectableBean::isDefaultBean); + if (nonDefault.isEmpty()) { + // All the matching beans were default + return Set.copyOf(matching); + } else if (nonDefault.size() == 1) { + return Set.of(nonDefault.get(0)); + } + + // More than one non-default bean remains - eliminate beans that don't have a priority + List> priorityBeans = new ArrayList<>(nonDefault); + priorityBeans.removeIf(not(ArcContainerImpl::isAlternativeOrDeclaredOnAlternative)); + if (priorityBeans.isEmpty()) { + // No alternative/priority beans are present + return Set.copyOf(nonDefault); + } else if (priorityBeans.size() == 1) { + return Set.of(priorityBeans.get(0)); + } else { + // Keep only the highest priorities + priorityBeans.sort(ArcContainerImpl::compareAlternativeBeans); + Integer highest = getAlternativePriority(priorityBeans.get(0)); + priorityBeans.removeIf(bean -> !highest.equals(getAlternativePriority(bean))); + if (priorityBeans.size() == 1) { + return Set.of(priorityBeans.get(0)); } + return Set.copyOf(priorityBeans); } - return new HashSet<>(matching); } private static boolean isAlternativeOrDeclaredOnAlternative(InjectableBean bean) { diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultClassBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultClassBeanTest.java index eb5b24fa5de1f..8f34f2ec435b2 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultClassBeanTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultClassBeanTest.java @@ -5,11 +5,15 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.DefaultBean; import io.quarkus.arc.test.ArcTestContainer; +import java.util.Set; +import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.CDI; import javax.inject.Inject; import javax.inject.Singleton; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -17,13 +21,24 @@ public class DefaultClassBeanTest { @RegisterExtension public ArcTestContainer container = new ArcTestContainer(Producer.class, - GreetingBean.class, Hello.class, PingBean.class); + GreetingBean.class, Hello.class, PingBean.class, Author.class, SciFi.class, Fantasy.class, Detective.class); @Test public void testInjection() { Hello hello = Arc.container().instance(Hello.class).get(); assertEquals("hello", hello.hello()); assertEquals("pong", hello.ping()); + var result = hello.instance(); + StringBuilder sb = new StringBuilder(); + for (var i : result) { + i.write(sb); + } + Assertions.assertTrue(sb.toString().contains("SciFi")); + Assertions.assertTrue(sb.toString().contains("Fantasy")); + Assertions.assertFalse(sb.toString().contains("Detective")); + Set detectives = Arc.container().beanManager().createInstance().select(Detective.class).stream() + .collect(Collectors.toSet()); + Assertions.assertEquals(1, detectives.size()); } @Test @@ -40,6 +55,9 @@ static class Hello { @Inject PingBean ping; + @Inject + Instance instance; + String hello() { return bean.greet(); } @@ -48,6 +66,9 @@ String ping() { return ping.ping(); } + Instance instance() { + return instance; + } } @DefaultBean // This one is overriden by Producer.greetingBean() @@ -85,4 +106,35 @@ String greet() { } + interface Author { + void write(StringBuilder sb); + } + + @Singleton + static class SciFi implements Author { + + @Override + public void write(StringBuilder sb) { + sb.append("SciFi"); + } + } + + @Singleton + static class Fantasy implements Author { + + @Override + public void write(StringBuilder sb) { + sb.append("Fantasy"); + } + } + + @Singleton + @DefaultBean + static class Detective implements Author { + + @Override + public void write(StringBuilder sb) { + sb.append("Detective"); + } + } } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultProducerFieldTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultProducerFieldTest.java index 6853c7922d557..e86d047bb4e82 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultProducerFieldTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/defaultbean/DefaultProducerFieldTest.java @@ -1,11 +1,15 @@ package io.quarkus.arc.test.defaultbean; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.quarkus.arc.Arc; import io.quarkus.arc.DefaultBean; import io.quarkus.arc.test.ArcTestContainer; +import java.util.List; +import java.util.stream.Collectors; import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Instance; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.CDI; import javax.inject.Inject; @@ -17,7 +21,7 @@ public class DefaultProducerFieldTest { @RegisterExtension public ArcTestContainer container = new ArcTestContainer(Producer.class, - GreetingBean.class, Hello.class); + GreetingBean.class, Hello.class, Fantasy.class); @Test public void testInjection() { @@ -29,16 +33,32 @@ public void testSelect() { assertEquals("hola", CDI.current().select(GreetingBean.class).get().greet()); } + @Test + public void testInstanceIterator() { + List authors = Arc.container().instance(Hello.class).get().instance().stream().collect(Collectors.toList()); + assertEquals(2, authors.size()); + String result = authors.stream().map(Author::get).collect(Collectors.joining()); + assertTrue(result.contains("SciFi")); + assertTrue(result.contains("Fantasy")); + } + @ApplicationScoped static class Hello { @Inject GreetingBean bean; + @Inject + Instance instance; + String hello() { return bean.greet(); } + Instance instance() { + return instance; + } + } @Singleton @@ -63,6 +83,31 @@ String greet() { }; + @Produces + @Singleton + @DefaultBean + Author sciFi = new Author() { + + @Override + public String get() { + return "SciFi"; + } + }; + + } + + interface Author { + String get(); + } + + @Singleton + @DefaultBean + static class Fantasy implements Author { + + @Override + public String get() { + return "Fantasy"; + } } }