Skip to content

Commit

Permalink
Merge pull request quarkusio#37075 from manovotn/issue37042
Browse files Browse the repository at this point in the history
Arc - @default beans can now be ordered via @priority
  • Loading branch information
manovotn authored Nov 15, 2023
2 parents d9ace85 + 8d2ea95 commit 63f2f71
Show file tree
Hide file tree
Showing 6 changed files with 266 additions and 5 deletions.
19 changes: 19 additions & 0 deletions docs/src/main/asciidoc/cdi-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,25 @@ public class CustomTracerConfiguration {
`@DefaultBean` allows extensions (or any other code for that matter) to provide defaults while backing off if beans of that type are supplied in any
way Quarkus supports.

Default beans can optionally declare `@jakarta.annotation.Priority`.
If there is no priority defined, `@Priority(0)` is assumed.
Priority value is used for bean ordering and during typesafe resolution to disambiguate multiple matching default beans.

[source,java]
----
@Dependent
public class CustomizedDefaultConfiguration {
@Produces
@DefaultBean
@Priority(100)
public Configuration customizedConfiguration(){
// create a customized default Configuration
// this will have priority over previously defined default bean
}
}
----

[[enable_build_profile]]
=== Enabling Beans for Quarkus Build Profile

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@ private static void addStandardErroneousDependencyMessage(InjectionTargetInfo ta

static BeanInfo resolveAmbiguity(Collection<BeanInfo> resolved) {
List<BeanInfo> resolvedAmbiguity = new ArrayList<>(resolved);
BeanInfo selected = null;
// First eliminate default beans
for (Iterator<BeanInfo> iterator = resolvedAmbiguity.iterator(); iterator.hasNext();) {
BeanInfo beanInfo = iterator.next();
Expand All @@ -520,9 +521,22 @@ static BeanInfo resolveAmbiguity(Collection<BeanInfo> resolved) {
}
if (resolvedAmbiguity.size() == 1) {
return resolvedAmbiguity.get(0);
} else if (resolvedAmbiguity.isEmpty()) {
// all beans were default beans, we attempt to sort them based on priority
resolvedAmbiguity = new ArrayList<>(resolved);
resolvedAmbiguity.sort(Beans::compareDefaultBeans);
Integer highest = getDefaultBeanPriority(resolvedAmbiguity.get(0));
for (Iterator<BeanInfo> iterator = resolvedAmbiguity.iterator(); iterator.hasNext();) {
if (!highest.equals(getDefaultBeanPriority(iterator.next()))) {
iterator.remove();
}
}
if (resolvedAmbiguity.size() == 1) {
selected = resolvedAmbiguity.get(0);
}
return selected;
}

BeanInfo selected = null;
for (Iterator<BeanInfo> iterator = resolvedAmbiguity.iterator(); iterator.hasNext();) {
BeanInfo beanInfo = iterator.next();
// Eliminate beans that are not alternatives, except for producer methods and fields of beans that are alternatives
Expand Down Expand Up @@ -581,6 +595,23 @@ private static int compareAlternativeBeans(BeanInfo bean1, BeanInfo bean2) {
return priority2.compareTo(priority1);
}

// gets bean priority or fall back to using default value of 0 for ordering purposes
private static Integer getDefaultBeanPriority(BeanInfo bean) {
Integer beanPriority = bean.getPriority();
if (beanPriority == null) {
beanPriority = 0;
}
return beanPriority;
}

private static int compareDefaultBeans(BeanInfo bean1, BeanInfo bean2) {
// The highest priority wins
Integer priority1, priority2;
priority2 = bean2.getPriority() == null ? 0 : bean2.getPriority();
priority1 = bean1.getPriority() == null ? 0 : bean1.getPriority();
return priority2.compareTo(priority1);
}

static boolean hasQualifiers(BeanInfo bean, Iterable<AnnotationInstance> required) {
for (AnnotationInstance requiredQualifier : required) {
if (!hasQualifier(bean, requiredQualifier)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@
* </pre>
*
* Then the result of {@code override()} will be used as MyBean in all injection points.
* <p>
* Default beans can optionally declare {@link jakarta.annotation.Priority}.
* In case there is no priority defined, {@code @Priority(0)} is assumed.
* Priority value is used for bean ordering and during typesafe resolution to disambiguate multiple matching default beans.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -691,17 +692,37 @@ private static Set<InjectableBean<?>> resolve(List<InjectableBean<?>> matching)

// First remove the default beans
List<InjectableBean<?>> nonDefault = new ArrayList<>(matching);
nonDefault.removeIf(InjectableBean::isDefaultBean);
for (Iterator<InjectableBean<?>> iterator = nonDefault.iterator(); iterator.hasNext();) {
if (iterator.next().isDefaultBean()) {
iterator.remove();
}
}
if (nonDefault.isEmpty()) {
// All the matching beans were default
return Set.copyOf(matching);
// Sort them by priority, uses 0 when no priority was defined
List<InjectableBean<?>> priorityDefaultBeans = new ArrayList<>(matching);
priorityDefaultBeans.sort(ArcContainerImpl::compareDefaultBeans);
Integer highest = priorityDefaultBeans.get(0).getPriority();
for (Iterator<InjectableBean<?>> iterator = priorityDefaultBeans.iterator(); iterator.hasNext();) {
if (!highest.equals(iterator.next().getPriority())) {
iterator.remove();
}
}
if (priorityDefaultBeans.size() == 1) {
return Set.of(priorityDefaultBeans.get(0));
}
return Set.copyOf(priorityDefaultBeans);
} 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<InjectableBean<?>> priorityBeans = new ArrayList<>(nonDefault);
priorityBeans.removeIf(not(ArcContainerImpl::isAlternativeOrDeclaredOnAlternative));
for (Iterator<InjectableBean<?>> iterator = priorityBeans.iterator(); iterator.hasNext();) {
if (!isAlternativeOrDeclaredOnAlternative(iterator.next())) {
iterator.remove();
}
}
if (priorityBeans.isEmpty()) {
// No alternative/priority beans are present
return Set.copyOf(nonDefault);
Expand All @@ -711,7 +732,11 @@ private static Set<InjectableBean<?>> resolve(List<InjectableBean<?>> matching)
// Keep only the highest priorities
priorityBeans.sort(ArcContainerImpl::compareAlternativeBeans);
Integer highest = getAlternativePriority(priorityBeans.get(0));
priorityBeans.removeIf(bean -> !highest.equals(getAlternativePriority(bean)));
for (Iterator<InjectableBean<?>> iterator = priorityBeans.iterator(); iterator.hasNext();) {
if (!highest.equals(getAlternativePriority(iterator.next()))) {
iterator.remove();
}
}
if (priorityBeans.size() == 1) {
return Set.of(priorityBeans.get(0));
}
Expand Down Expand Up @@ -837,6 +862,14 @@ private static int compareAlternativeBeans(InjectableBean<?> bean1, InjectableBe
return priority2.compareTo(priority1);
}

// Used to compare default beans; disregards alternative, only looks at priority value
private static int compareDefaultBeans(InjectableBean<?> bean1, InjectableBean<?> bean2) {
// The highest priority wins
Integer priority2 = bean2.getPriority();
Integer priority1 = bean1.getPriority();
return priority2.compareTo(priority1);
}

@SuppressWarnings("unchecked")
<T> List<InjectableObserverMethod<? super T>> resolveObservers(Type eventType, Set<Annotation> eventQualifiers) {
registeredQualifiers.verify(eventQualifiers);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.quarkus.arc.test.defaultbean;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

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

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

public class DefaultBeanPriorityTest {

@RegisterExtension
public ArcTestContainer container = new ArcTestContainer(MyBean.class, FooInterface.class, FooImpl1.class, FooImpl2.class,
BarInterface.class, BarImpl1.class, BarImpl2.class);

@Test
public void testInjection() {
MyBean myBean = Arc.container().select(MyBean.class).get();
Assertions.assertEquals(FooImpl2.class.getSimpleName(), myBean.getFoo().ping());
Assertions.assertEquals(BarImpl2.class.getSimpleName(), myBean.getBar().ping());
}

@Test
public void testSelect() {
FooInterface foo = CDI.current().select(FooInterface.class).get();
Assertions.assertEquals(FooImpl2.class.getSimpleName(), foo.ping());

BarInterface bar = CDI.current().select(BarInterface.class).get();
Assertions.assertEquals(BarImpl2.class.getSimpleName(), bar.ping());
}

@Singleton
static class MyBean {

@Inject
FooInterface foo;

@Inject
BarInterface bar;

FooInterface getFoo() {
return foo;
}

BarInterface getBar() {
return bar;
}
}

interface FooInterface {
String ping();
}

@ApplicationScoped
@DefaultBean
@Priority(10)
static class FooImpl1 implements FooInterface {

@Override
public String ping() {
return FooImpl1.class.getSimpleName();
}
}

@ApplicationScoped
@DefaultBean
@Priority(20)
static class FooImpl2 implements FooInterface {

@Override
public String ping() {
return FooImpl2.class.getSimpleName();
}
}

interface BarInterface {
String ping();
}

@ApplicationScoped
@DefaultBean
// no priority annotation
static class BarImpl1 implements BarInterface {

@Override
public String ping() {
return BarImpl1.class.getSimpleName();
}
}

@ApplicationScoped
@DefaultBean
@Priority(1)
static class BarImpl2 implements BarInterface {

@Override
public String ping() {
return BarImpl2.class.getSimpleName();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package io.quarkus.arc.test.defaultbean.ambiguous;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.spi.DeploymentException;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

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

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

public class DefaultBeanPriorityAmbiguousTest {

@RegisterExtension
public ArcTestContainer container = ArcTestContainer.builder()
.beanClasses(MyBean.class, FooInterface.class, FooImpl1.class, FooImpl2.class)
.shouldFail()
.build();

@Test
public void testAmbiguousResolution() {
Throwable error = container.getFailure();
assertNotNull(error);
assertTrue(error instanceof DeploymentException);
assertTrue(error.getMessage().contains("AmbiguousResolutionException: Ambiguous dependencies"));
}

@Singleton
static class MyBean {

@Inject
FooInterface foo;
}

interface FooInterface {
String ping();
}

@ApplicationScoped
@DefaultBean
@Priority(10)
static class FooImpl1 implements FooInterface {

@Override
public String ping() {
return FooImpl1.class.getSimpleName();
}
}

@ApplicationScoped
@DefaultBean
@Priority(10)
static class FooImpl2 implements FooInterface {

@Override
public String ping() {
return FooImpl2.class.getSimpleName();
}
}
}

0 comments on commit 63f2f71

Please sign in to comment.