diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java index 4b4709bb310b1..cd4a70d3f406b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/EventImpl.java @@ -27,6 +27,7 @@ import jakarta.enterprise.event.ObserverException; import jakarta.enterprise.event.TransactionPhase; import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Default; import jakarta.enterprise.inject.spi.EventContext; import jakarta.enterprise.inject.spi.EventMetadata; import jakarta.enterprise.inject.spi.InjectionPoint; @@ -68,10 +69,7 @@ class EventImpl implements Event { EventImpl(Type eventType, Set qualifiers, InjectionPoint injectionPoint) { this.eventType = initEventType(eventType); this.injectionPointTypeHierarchy = new HierarchyDiscovery(this.eventType); - Set eventQualifiers = new HashSet<>(); - eventQualifiers.addAll(qualifiers); - eventQualifiers.add(Any.Literal.INSTANCE); - this.qualifiers = Set.copyOf(eventQualifiers); + this.qualifiers = Set.copyOf(qualifiers); this.notifiers = new ConcurrentHashMap<>(DEFAULT_CACHE_CAPACITY); this.injectionPoint = injectionPoint; } @@ -172,9 +170,16 @@ static Notifier createNotifier(Class runtimeType, Type eventType, Set< static Notifier createNotifier(Class runtimeType, Type eventType, Set qualifiers, ArcContainerImpl container, boolean activateRequestContext, InjectionPoint injectionPoint) { - EventMetadata metadata = new EventMetadataImpl(qualifiers, eventType, injectionPoint); + // all events should have `@Any` qualifiers + // if there was no other explicit qualifier added, also add @Default + Set normalizedQualifiers = new HashSet<>(qualifiers); + if (normalizedQualifiers.isEmpty()) { + normalizedQualifiers.add(Default.Literal.INSTANCE); + } + normalizedQualifiers.add(Any.Literal.INSTANCE); + EventMetadata metadata = new EventMetadataImpl(normalizedQualifiers, eventType, injectionPoint); List> notifierObserverMethods = new ArrayList<>( - container.resolveObservers(eventType, qualifiers)); + container.resolveObservers(eventType, normalizedQualifiers)); return new Notifier<>(runtimeType, notifierObserverMethods, metadata, activateRequestContext); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/event/qualifier/EventDefaultQualifierTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/event/qualifier/EventDefaultQualifierTest.java new file mode 100644 index 0000000000000..c35781be70467 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/event/qualifier/EventDefaultQualifierTest.java @@ -0,0 +1,165 @@ +package io.quarkus.arc.test.event.qualifier; + +import java.lang.annotation.Annotation; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.event.ObservesAsync; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.EventMetadata; + +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.test.ArcTestContainer; + +public class EventDefaultQualifierTest { + + @RegisterExtension + public ArcTestContainer container = ArcTestContainer.builder().beanClasses(ObservingBean.class).build(); + + @Test + public void testDefaultQualifierPresent() + throws ExecutionException, InterruptedException, TimeoutException { + BeanManager bm = Arc.container().beanManager(); + ObservingBean bean = Arc.container().select(ObservingBean.class).get(); + bean.reset(); + + Set expectedQualifiers = Set.of(Any.Literal.INSTANCE, Default.Literal.INSTANCE); + + // just get event fire right away - @Default should be included + bm.getEvent().fire(new Payload()); + Assertions.assertEquals(1, bean.getDefaultObjectNotified()); + Assertions.assertEquals(1, bean.getDefaultPayloadNotified()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultObjectQualifiers()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultPayloadQualifiers()); + + // select Payload and fire - @Default should be included + bm.getEvent().select(Payload.class).fire(new Payload()); + Assertions.assertEquals(2, bean.getDefaultObjectNotified()); + Assertions.assertEquals(2, bean.getDefaultPayloadNotified()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultObjectQualifiers()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultPayloadQualifiers()); + + // select Payload and explicitly add @Any qualifier, then fire - @Default should *not* be included + // therefore no notifications should occur + bm.getEvent().select(Payload.class, Any.Literal.INSTANCE).fire(new Payload()); + Assertions.assertEquals(2, bean.getDefaultObjectNotified()); + Assertions.assertEquals(2, bean.getDefaultPayloadNotified()); + + // same in async variant + // just get event fire right away - @Default should be included + bm.getEvent().fireAsync(new Payload()).toCompletableFuture().get(2, TimeUnit.SECONDS); + Assertions.assertEquals(1, bean.getDefaultObjectAsyncNotified()); + Assertions.assertEquals(1, bean.getDefaultPayloadAsyncNotified()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultObjectAsyncQualifiers()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultPayloadAsyncQualifiers()); + + // select Payload and fire - @Default should be included + bm.getEvent().select(Payload.class).fireAsync(new Payload()).toCompletableFuture().get(2, TimeUnit.SECONDS); + Assertions.assertEquals(2, bean.getDefaultObjectAsyncNotified()); + Assertions.assertEquals(2, bean.getDefaultPayloadAsyncNotified()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultObjectAsyncQualifiers()); + Assertions.assertEquals(expectedQualifiers, bean.getDefaultPayloadAsyncQualifiers()); + + // select Payload and explicitly add @Any qualifier, then fire - @Default should *not* be included + // therefore no notifications should occur + bm.getEvent().select(Payload.class, Any.Literal.INSTANCE).fireAsync(new Payload()).toCompletableFuture().get(2, + TimeUnit.SECONDS); + Assertions.assertEquals(2, bean.getDefaultObjectAsyncNotified()); + Assertions.assertEquals(2, bean.getDefaultPayloadAsyncNotified()); + } + + public static class Payload { + } + + @ApplicationScoped + public static class ObservingBean { + + private volatile int defaultObjectNotified = 0; + private volatile int defaultObjectAsyncNotified = 0; + private volatile int defaultPayloadNotified = 0; + private volatile int defaultPayloadAsyncNotified = 0; + private volatile Set defaultObjectQualifiers; + private volatile Set defaultObjectAsyncQualifiers; + private volatile Set defaultPayloadQualifiers; + private volatile Set defaultPayloadAsyncQualifiers; + + public void observeDefaultObject(@Observes @Default Object payload, EventMetadata em) { + // object type is very broad, only look for Payload runtime type + if (em.getType().equals(Payload.class)) { + this.defaultObjectNotified++; + this.defaultObjectQualifiers = em.getQualifiers(); + } + } + + public void observeDefaultPayload(@Observes @Default Payload payload, EventMetadata em) { + this.defaultPayloadNotified++; + this.defaultPayloadQualifiers = em.getQualifiers(); + } + + public void observeDefaultObjectAsync(@ObservesAsync @Default Object payload, EventMetadata em) { + // object type is very broad, only look for Payload runtime type + if (em.getType().equals(Payload.class)) { + this.defaultObjectAsyncNotified++; + this.defaultObjectAsyncQualifiers = em.getQualifiers(); + } + } + + public void observeDefaultPayloadAsync(@ObservesAsync @Default Payload payload, EventMetadata em) { + this.defaultPayloadAsyncNotified++; + this.defaultPayloadAsyncQualifiers = em.getQualifiers(); + } + + public int getDefaultObjectNotified() { + return defaultObjectNotified; + } + + public int getDefaultPayloadNotified() { + return defaultPayloadNotified; + } + + public Set getDefaultObjectQualifiers() { + return defaultObjectQualifiers; + } + + public Set getDefaultPayloadQualifiers() { + return defaultPayloadQualifiers; + } + + public int getDefaultObjectAsyncNotified() { + return defaultObjectAsyncNotified; + } + + public int getDefaultPayloadAsyncNotified() { + return defaultPayloadAsyncNotified; + } + + public Set getDefaultObjectAsyncQualifiers() { + return defaultObjectAsyncQualifiers; + } + + public Set getDefaultPayloadAsyncQualifiers() { + return defaultPayloadAsyncQualifiers; + } + + public void reset() { + this.defaultPayloadNotified = 0; + this.defaultPayloadAsyncNotified = 0; + this.defaultObjectNotified = 0; + this.defaultObjectAsyncNotified = 0; + this.defaultObjectQualifiers = null; + this.defaultObjectAsyncQualifiers = null; + this.defaultPayloadQualifiers = null; + this.defaultPayloadAsyncQualifiers = null; + } + } +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java index acfa2bbf0c4fd..fefaeae6e0fce 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java @@ -38,8 +38,10 @@ public void testMetadata() { Arc.container().beanManager().getEvent().fire(BigDecimal.ONE); EventMetadata metadata = BigObserver.METADATA.get(); assertNotNull(metadata); - assertEquals(1, metadata.getQualifiers().size()); - assertEquals(Any.class, metadata.getQualifiers().iterator().next().annotationType()); + assertEquals(2, metadata.getQualifiers().size()); + for (Annotation qualifier : metadata.getQualifiers()) { + assertTrue(qualifier.annotationType().equals(Any.class) || qualifier.annotationType().equals(Default.class)); + } assertEquals(BigDecimal.class, metadata.getType()); assertNull(metadata.getInjectionPoint());