From 825bc43e37c0571cf52e7124f993ff3f757b1c2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 19:17:38 +0000 Subject: [PATCH 1/3] Bump cdi.tck.version from 5.0.0.Alpha2 to 5.0.0.Alpha5 Bumps `cdi.tck.version` from 5.0.0.Alpha2 to 5.0.0.Alpha5. Updates `jakarta.cdi:jakarta.cdi-tck-ext-lib` from 5.0.0.Alpha2 to 5.0.0.Alpha5 - [Release notes](https://github.com/eclipse-ee4j/cdi-tck/releases) - [Commits](https://github.com/eclipse-ee4j/cdi-tck/compare/5.0.0.Alpha2...5.0.0.Alpha5) Updates `jakarta.cdi:jakarta.cdi-tck-api` from 5.0.0.Alpha2 to 5.0.0.Alpha5 - [Release notes](https://github.com/eclipse-ee4j/cdi-tck/releases) - [Commits](https://github.com/eclipse-ee4j/cdi-tck/compare/5.0.0.Alpha2...5.0.0.Alpha5) Updates `jakarta.cdi:jakarta.cdi-tck-lang-model` from 5.0.0.Alpha2 to 5.0.0.Alpha5 - [Release notes](https://github.com/eclipse-ee4j/cdi-tck/releases) - [Commits](https://github.com/eclipse-ee4j/cdi-tck/compare/5.0.0.Alpha2...5.0.0.Alpha5) Updates `jakarta.cdi:jakarta.cdi-tck-core-impl` from 5.0.0.Alpha2 to 5.0.0.Alpha5 - [Release notes](https://github.com/eclipse-ee4j/cdi-tck/releases) - [Commits](https://github.com/eclipse-ee4j/cdi-tck/compare/5.0.0.Alpha2...5.0.0.Alpha5) --- updated-dependencies: - dependency-name: jakarta.cdi:jakarta.cdi-tck-api dependency-version: 5.0.0.Alpha5 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: jakarta.cdi:jakarta.cdi-tck-core-impl dependency-version: 5.0.0.Alpha5 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: jakarta.cdi:jakarta.cdi-tck-ext-lib dependency-version: 5.0.0.Alpha5 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: jakarta.cdi:jakarta.cdi-tck-lang-model dependency-version: 5.0.0.Alpha5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 902131ab95..ede4e42bfc 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 1.2.4.Final 2.0.1 - 5.0.0.Alpha2 + 5.0.0.Alpha5 11.0.3 From d3fb0176468e1439979cb2c0f85b6c9945e095ce Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 19 May 2026 16:44:12 +0200 Subject: [PATCH 2/3] Update cdi.tck.version to latest 5.0.0.Alpha7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ede4e42bfc..07fad42500 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ 1.2.4.Final 2.0.1 - 5.0.0.Alpha5 + 5.0.0.Alpha7 11.0.3 From a69b9e72e44d6300877867aeb1381bde382c119a Mon Sep 17 00:00:00 2001 From: Matej Novotny Date: Tue, 19 May 2026 18:03:35 +0200 Subject: [PATCH 3/3] Fix duplicate async handler detection in WAR deployments --- .../weld/invokable/AsyncHandlerRegistry.java | 13 ++- .../invokable/async/wardedup/LibraryBean.java | 10 +++ .../invokable/async/wardedup/MyAsyncType.java | 15 ++++ .../async/wardedup/MyAsyncTypeHandler.java | 10 +++ .../async/wardedup/MyAsyncTypeImpl.java | 36 +++++++++ .../wardedup/WarDedupAsyncHandlerTest.java | 79 +++++++++++++++++++ .../async/wardedup/WarDedupBean.java | 14 ++++ .../async/wardedup/WarDedupExtension.java | 35 ++++++++ 8 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/LibraryBean.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncType.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeHandler.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeImpl.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupAsyncHandlerTest.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupBean.java create mode 100644 tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupExtension.java diff --git a/impl/src/main/java/org/jboss/weld/invokable/AsyncHandlerRegistry.java b/impl/src/main/java/org/jboss/weld/invokable/AsyncHandlerRegistry.java index 202235e36c..402936a8fa 100644 --- a/impl/src/main/java/org/jboss/weld/invokable/AsyncHandlerRegistry.java +++ b/impl/src/main/java/org/jboss/weld/invokable/AsyncHandlerRegistry.java @@ -69,7 +69,7 @@ private void validateAndRegisterReturnType(AsyncHandler.ReturnType handler) { validateDirectImplementation(handlerClass, AsyncHandler.ReturnType.class); Class asyncType = extractAsyncType(handlerClass, AsyncHandler.ReturnType.class); checkDuplicate(asyncType, handlerClass, true); - handlers.put(asyncType, HandlerInfo.returnType(handler, asyncType)); + handlers.putIfAbsent(asyncType, HandlerInfo.returnType(handler, asyncType)); } private void validateAndRegisterParameterType(AsyncHandler.ParameterType handler) { @@ -77,7 +77,7 @@ private void validateAndRegisterParameterType(AsyncHandler.ParameterType hand validateDirectImplementation(handlerClass, AsyncHandler.ParameterType.class); Class asyncType = extractAsyncType(handlerClass, AsyncHandler.ParameterType.class); checkDuplicate(asyncType, handlerClass, false); - handlers.put(asyncType, HandlerInfo.parameterType(handler, asyncType)); + handlers.putIfAbsent(asyncType, HandlerInfo.parameterType(handler, asyncType)); } private void validateDirectImplementation(Class handlerClass, Class targetInterface) { @@ -92,6 +92,15 @@ private void validateDirectImplementation(Class handlerClass, Class target private void checkDuplicate(Class asyncType, Class handlerClass, boolean isReturnType) { HandlerInfo existing = handlers.get(asyncType); if (existing != null && !existing.isBuiltin()) { + // In a WAR with multiple BDAs (e.g. WEB-INF/classes + JARs in WEB-INF/lib), + // all BDAs share the same classloader. Since discovery runs per BDA, the same + // service file is found multiple times, yielding the same handler class. + // This is not an error — skip re-registration. In an EAR with isolated module + // classloaders, different Class objects would be loaded, so this identity check + // does not suppress genuine duplicates across modules. + if (existing.getHandlerClass() == handlerClass && existing.isReturnType() == isReturnType) { + return; + } if (existing.getHandlerClass() == handlerClass && existing.isReturnType() != isReturnType) { throw InvokerLogger.LOG.asyncHandlerBothKinds(handlerClass, asyncType); } diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/LibraryBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/LibraryBean.java new file mode 100644 index 0000000000..13834321dc --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/LibraryBean.java @@ -0,0 +1,10 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class LibraryBean { + public String ping() { + return "pong"; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncType.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncType.java new file mode 100644 index 0000000000..0035e92207 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncType.java @@ -0,0 +1,15 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import java.util.concurrent.CompletableFuture; + +public interface MyAsyncType { + boolean isComplete(); + + T getIfComplete(); + + MyAsyncType whenComplete(Runnable callback); + + static MyAsyncType from(CompletableFuture future) { + return new MyAsyncTypeImpl<>(future); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeHandler.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeHandler.java new file mode 100644 index 0000000000..f70ea92a1b --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeHandler.java @@ -0,0 +1,10 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class MyAsyncTypeHandler implements AsyncHandler.ReturnType> { + @Override + public MyAsyncType transform(MyAsyncType original, Runnable completion) { + return original.whenComplete(completion); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeImpl.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeImpl.java new file mode 100644 index 0000000000..9fc00f40bc --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/MyAsyncTypeImpl.java @@ -0,0 +1,36 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import java.util.concurrent.CompletableFuture; + +final class MyAsyncTypeImpl implements MyAsyncType { + private final CompletableFuture future; + private Runnable callback; + + MyAsyncTypeImpl(CompletableFuture future) { + this.future = future; + } + + @Override + public boolean isComplete() { + return future.isDone(); + } + + @Override + public T getIfComplete() { + if (future.isDone()) { + return future.getNow(null); + } + throw new IllegalStateException("not yet complete"); + } + + @Override + public MyAsyncType whenComplete(Runnable callback) { + this.callback = callback; + future.whenComplete((v, e) -> { + if (this.callback != null) { + this.callback.run(); + } + }); + return this; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupAsyncHandlerTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupAsyncHandlerTest.java new file mode 100644 index 0000000000..a5b5fdb72e --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupAsyncHandlerTest.java @@ -0,0 +1,79 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.invoke.AsyncHandler; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.weld.test.util.Utils; +import org.jboss.weld.tests.category.Integration; +import org.jboss.weld.tests.invokable.async.DependentBean; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +/** + * Verifies that async handler discovery works in a WAR with multiple BDAs + * sharing the same classloader. The service file in WEB-INF/classes is visible + * to all BDAs, so the handler must be deduplicated rather than rejected. + */ +@RunWith(Arquillian.class) +@Category(Integration.class) +public class WarDedupAsyncHandlerTest { + + @Deployment + public static Archive deploy() { + // The library JAR creates a second BDA within the WAR. Both BDAs share + // the WAR classloader, so async handler discovery (which runs per BDA) + // finds the same META-INF/services/ file twice. + JavaArchive lib = ShrinkWrap.create(BeanArchive.class) + .addClass(LibraryBean.class); + + return ShrinkWrap + .create(WebArchive.class, Utils.getDeploymentNameAsHash(WarDedupAsyncHandlerTest.class, Utils.ARCHIVE_TYPE.WAR)) + .addClasses(WarDedupBean.class, WarDedupExtension.class, + MyAsyncType.class, MyAsyncTypeImpl.class, MyAsyncTypeHandler.class, + DependentBean.class) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") + .addAsServiceProvider(Extension.class, WarDedupExtension.class) + .addAsServiceProvider(AsyncHandler.ReturnType.class, MyAsyncTypeHandler.class) + .addAsLibrary(lib); + } + + @Inject + WarDedupExtension extension; + + @Test + @SuppressWarnings("unchecked") + public void testCustomReturnTypeHandlerInWar() throws Exception { + DependentBean.reset(); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(0, DependentBean.destroyedCounter.get()); + + MyAsyncType result = (MyAsyncType) extension.getInvoker() + .invoke(null, new Object[] { null, future }); + + assertEquals(0, DependentBean.destroyedCounter.get()); + assertFalse(result.isComplete()); + + future.complete("dedup"); + + assertEquals(1, DependentBean.destroyedCounter.get()); + assertTrue(result.isComplete()); + assertEquals("dedup", result.getIfComplete()); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupBean.java new file mode 100644 index 0000000000..fa8be0b512 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupBean.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.jboss.weld.tests.invokable.async.DependentBean; + +@ApplicationScoped +public class WarDedupBean { + public MyAsyncType hello(DependentBean dep, CompletableFuture future) { + return MyAsyncType.from(future); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupExtension.java new file mode 100644 index 0000000000..4890c92e6e --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/async/wardedup/WarDedupExtension.java @@ -0,0 +1,35 @@ +package org.jboss.weld.tests.invokable.async.wardedup; + +import java.util.Collection; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AfterDeploymentValidation; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; + +public class WarDedupExtension implements Extension { + + private Invoker invoker; + + public void observeBean(@Observes ProcessManagedBean pmb) { + Collection> methods = pmb.getAnnotatedBeanClass().getMethods(); + for (AnnotatedMethod m : methods) { + if ("hello".equals(m.getJavaMember().getName())) { + invoker = pmb.createInvoker(m) + .withInstanceLookup() + .withArgumentLookup(0) + .build(); + } + } + } + + public void validate(@Observes AfterDeploymentValidation adv) { + adv.ensureAsyncHandlerExists(MyAsyncType.class); + } + + public Invoker getInvoker() { + return invoker; + } +}