diff --git a/impl/src/main/java/org/jboss/cdi/tck/shrinkwrap/ArchiveBuilder.java b/impl/src/main/java/org/jboss/cdi/tck/shrinkwrap/ArchiveBuilder.java index f6b3951c6..be1d17bb7 100644 --- a/impl/src/main/java/org/jboss/cdi/tck/shrinkwrap/ArchiveBuilder.java +++ b/impl/src/main/java/org/jboss/cdi/tck/shrinkwrap/ArchiveBuilder.java @@ -250,6 +250,32 @@ public T withBuildCompatibleExtensions(Class return withServiceProvider(new ServiceProviderDescriptor(BuildCompatibleExtension.class, extensionClasses)); } + /** + * Add a service provider class for given service class. In other words, add a file + * {@code META-INF/services/} whose content is {@code }. + * + * @param serviceClass + * @param serviceProviderClass + * @return + * @param + */ + public T withServiceProvider(Class serviceClass, Class serviceProviderClass) { + return withServiceProvider(new ServiceProviderDescriptor(serviceClass, serviceProviderClass)); + } + + /** + * Add a service provider class for given service class. In other words, add a file + * {@code META-INF/services/} whose content is {@code }. + * + * @param serviceClass + * @param serviceProviderClasses + * @return + * @param + */ + public T withServiceProvider(Class serviceClass, List> serviceProviderClasses) { + return withServiceProvider(new ServiceProviderDescriptor(serviceClass, serviceProviderClasses.toArray(new Class[0]))); + } + /** * Add service provider. * diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/AsyncHandlerBuiltinTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/AsyncHandlerBuiltinTest.java new file mode 100644 index 000000000..d160ad4a6 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/AsyncHandlerBuiltinTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.builtin; + +import static org.jboss.cdi.tck.TestGroups.CDI_FULL; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +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; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +@Test(groups = CDI_FULL) +public class AsyncHandlerBuiltinTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerBuiltinTest.class) + .withExtension(TestExtension.class) + .build(); + } + + public static class TestExtension implements Extension { + private final Map> invokers = new HashMap<>(); + + public void registration(@Observes ProcessManagedBean pmb) { + Set methods = Set.of("helloCS", "helloCF", "helloFP"); + for (AnnotatedMethod method : pmb.getAnnotatedBeanClass().getMethods()) { + if (methods.contains(method.getJavaMember().getName())) { + Invoker invoker = pmb.createInvoker(method) + .withInstanceLookup() + .withArgumentLookup(0) + .build(); + invokers.put(method.getJavaMember().getName(), invoker); + } + } + } + + public void validation(@Observes AfterDeploymentValidation adv) { + adv.ensureAsyncHandlerExists(CompletionStage.class); + adv.ensureAsyncHandlerExists(CompletableFuture.class); + adv.ensureAsyncHandlerExists(Flow.Publisher.class); + } + + @SuppressWarnings("unchecked") + public Invoker getInvoker(String name) { + return (Invoker) invokers.get(name); + } + } + + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ja") + public void testCompletionStage() throws Exception { + MyDependentBean.reset(); + + Invoker> hello = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("helloCS"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + CompletionStage result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.toCompletableFuture().isDone()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.toCompletableFuture().isDone()); + assertEquals(result.toCompletableFuture().getNow(null), "hello"); + } + + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "jb") + public void testCompletableFuture() throws Exception { + MyDependentBean.reset(); + + Invoker> hello = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("helloCF"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + CompletableFuture result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isDone()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isDone()); + assertEquals(result.getNow(null), "hello"); + } + + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "jc") + public void testFlowPublisher() throws Exception { + MyDependentBean.reset(); + + Invoker> hello = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("helloFP"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + Flow.Publisher result = hello.invoke(null, new Object[] { null, future }); + + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean done = new AtomicBoolean(false); + + result.subscribe(new Flow.Subscriber<>() { + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.request(1); + } + + @Override + public void onNext(String item) { + value.set(item); + } + + @Override + public void onError(Throwable throwable) { + error.set(throwable); + } + + @Override + public void onComplete() { + done.set(true); + } + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertNull(value.get()); + assertNull(error.get()); + assertFalse(done.get()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertEquals(value.get(), "hello"); + assertNull(error.get()); + assertTrue(done.get()); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/CompletableFuturePublisher.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/CompletableFuturePublisher.java new file mode 100644 index 000000000..e6de5c1d2 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/CompletableFuturePublisher.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.builtin; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicInteger; + +public final class CompletableFuturePublisher implements Flow.Publisher { + private final CompletableFuture future; + + public CompletableFuturePublisher(CompletableFuture future) { + this.future = Objects.requireNonNull(future); + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + Objects.requireNonNull(subscriber); + subscriber.onSubscribe(new CompletableFutureSubscription<>(future, subscriber)); + } + + private static final class CompletableFutureSubscription implements Flow.Subscription { + private static final int STATE_NEW = 0; + private static final int STATE_PENDING = 1; + private static final int STATE_FINISHED = 2; + + private final CompletableFuture future; + private final Flow.Subscriber subscriber; + private final AtomicInteger state = new AtomicInteger(STATE_NEW); + + CompletableFutureSubscription(CompletableFuture future, Flow.Subscriber subscriber) { + this.future = future; + this.subscriber = subscriber; + } + + @Override + public void request(long n) { + if (n <= 0L) { + cancel(); + subscriber.onError(new IllegalArgumentException("Negative request: " + n)); + return; + } + + if (state.compareAndSet(STATE_NEW, STATE_PENDING)) { + future.whenComplete((value, error) -> { + if (state.compareAndSet(STATE_PENDING, STATE_FINISHED)) { + if (error != null) { + subscriber.onError(error); + } else if (value == null) { + subscriber.onError(new NullPointerException("CompletableFuture produced null")); + } else { + subscriber.onNext(value); + subscriber.onComplete(); + } + } + }); + } + } + + @Override + public void cancel() { + if (state.getAndSet(STATE_FINISHED) != STATE_FINISHED) { + future.cancel(false); + } + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyBean.java new file mode 100644 index 000000000..7bfce919f --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyBean.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.builtin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public CompletionStage helloCS(MyDependentBean bean, CompletableFuture future) { + return helloCF(bean, future); + } + + public CompletableFuture helloCF(MyDependentBean bean, CompletableFuture future) { + CompletableFuture result = new CompletableFuture<>(); + future.whenComplete((value, error) -> { + if (error == null) { + result.complete(value); + } else { + result.completeExceptionally(error); + } + }); + return result; + } + + public Flow.Publisher helloFP(MyDependentBean bean, CompletableFuture future) { + return new CompletableFuturePublisher<>(future); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyDependentBean.java new file mode 100644 index 000000000..4dfe2ed61 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/builtin/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.builtin; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/AsyncHandlerParamTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/AsyncHandlerParamTypeTest.java new file mode 100644 index 000000000..278a72a64 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/AsyncHandlerParamTypeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +import static org.jboss.cdi.tck.TestGroups.CDI_FULL; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +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.AsyncHandler; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +@Test(groups = CDI_FULL) +public class AsyncHandlerParamTypeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerParamTypeTest.class) + .withServiceProvider(AsyncHandler.ParameterType.class, MyAsyncTypeHandler.class) + .withExtension(TestExtension.class) + .build(); + } + + public static class TestExtension implements Extension { + private final Map> invokers = new HashMap<>(); + + public void registration(@Observes ProcessManagedBean pmb) { + Set methods = Set.of("hello", "helloThrow"); + for (AnnotatedMethod method : pmb.getAnnotatedBeanClass().getMethods()) { + if (methods.contains(method.getJavaMember().getName())) { + Invoker invoker = pmb.createInvoker(method) + .withInstanceLookup() + .withArgumentLookup(0) + .build(); + invokers.put(method.getJavaMember().getName(), invoker); + } + } + } + + public void validation(@Observes AfterDeploymentValidation adv) { + adv.ensureAsyncHandlerExists(MyAsyncType.class); + } + + @SuppressWarnings("unchecked") + public Invoker getInvoker(String name) { + return (Invoker) invokers.get(name); + } + } + + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "b") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "g") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "h") + public void test() throws Exception { + MyDependentBean.reset(); + + Invoker> hello = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("hello"); + CompletableFuture future = new CompletableFuture<>(); + MyAsyncType result = MyAsyncType.createSuspended(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + hello.invoke(null, new Object[] { null, future, result }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isComplete()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + // this test verifies both that dependent instances created for the invocation are destroyed + // in case of a synchronous exception and that a "secondary completion" is ignored + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "b") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + public void testSyncThrow() { + MyDependentBean.reset(); + + Invoker> helloThrow = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("helloThrow"); + CompletableFuture future = new CompletableFuture<>(); + MyAsyncType result = MyAsyncType.createSuspended(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + assertThrows(IllegalArgumentException.class, () -> { + helloThrow.invoke(null, new Object[] { null, future, result }); + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + + // calls the completion callback, which must be a noop + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncType.java new file mode 100644 index 000000000..0166b3d72 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +public interface MyAsyncType { + boolean isComplete(); + + T getIfComplete(); + + void whenComplete(Runnable callback); + + void resume(T value); + + static MyAsyncType createSuspended() { + return new MyAsyncTypeImpl<>(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeHandler.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeHandler.java new file mode 100644 index 000000000..ff00b5932 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class MyAsyncTypeHandler implements AsyncHandler.ParameterType> { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + original.whenComplete(completion); + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeImpl.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeImpl.java new file mode 100644 index 000000000..802ccd310 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyAsyncTypeImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +import java.util.concurrent.atomic.AtomicReference; + +final class MyAsyncTypeImpl implements MyAsyncType { + private final AtomicReference value = new AtomicReference<>(null); + private final AtomicReference callback = new AtomicReference<>(null); + + MyAsyncTypeImpl() { + } + + @Override + public boolean isComplete() { + return value.get() != null; + } + + @Override + public T getIfComplete() { + T value = this.value.get(); + if (value != null) { + return value; + } else { + throw new IllegalStateException("not yet complete"); + } + } + + @Override + public void whenComplete(Runnable callback) { + if (!this.callback.compareAndSet(null, callback)) { + throw new IllegalStateException("only one callback possible"); + } + } + + @Override + public void resume(T value) { + if (value == null) { + throw new IllegalArgumentException("must resume with non-null value"); + } + if (this.value.compareAndSet(null, value)) { + Runnable callback = this.callback.get(); + if (callback != null) { + callback.run(); + } + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyBean.java new file mode 100644 index 000000000..9cd20bf09 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyBean.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +import static org.testng.Assert.assertNull; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public void hello(MyDependentBean bean, CompletableFuture future, MyAsyncType async) { + future.whenComplete((value, error) -> { + assertNull(error); + async.resume(value); + }); + } + + public void helloThrow(MyDependentBean bean, CompletableFuture future, MyAsyncType async) { + future.whenComplete((value, error) -> { + assertNull(error); + async.resume(value); + }); + throw new IllegalArgumentException("synchronous throw"); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyDependentBean.java new file mode 100644 index 000000000..710854d01 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/paramtype/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.paramtype; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/AsyncHandlerReturnTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/AsyncHandlerReturnTypeTest.java new file mode 100644 index 000000000..6c8e37705 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/AsyncHandlerReturnTypeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +import static org.jboss.cdi.tck.TestGroups.CDI_FULL; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +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.AsyncHandler; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +@Test(groups = CDI_FULL) +public class AsyncHandlerReturnTypeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerReturnTypeTest.class) + .withServiceProvider(AsyncHandler.ReturnType.class, MyAsyncTypeHandler.class) + .withExtension(TestExtension.class) + .build(); + } + + public static class TestExtension implements Extension { + private final Map> invokers = new HashMap<>(); + + public void registration(@Observes ProcessManagedBean pmb) { + Set methods = Set.of("hello", "helloThrow"); + for (AnnotatedMethod method : pmb.getAnnotatedBeanClass().getMethods()) { + if (methods.contains(method.getJavaMember().getName())) { + Invoker invoker = pmb.createInvoker(method) + .withInstanceLookup() + .withArgumentLookup(0) + .build(); + invokers.put(method.getJavaMember().getName(), invoker); + } + } + } + + public void validation(@Observes AfterDeploymentValidation adv) { + adv.ensureAsyncHandlerExists(MyAsyncType.class); + } + + @SuppressWarnings("unchecked") + public Invoker getInvoker(String name) { + return (Invoker) invokers.get(name); + } + } + + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "b") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "g") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "h") + public void test() throws Exception { + MyDependentBean.reset(); + + Invoker> hello = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("hello"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + MyAsyncType result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isComplete()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + // this test only verifies that dependent instances created for the invocation are destroyed in case of a synchronous + // exception, but it does not (and cannot) verify that a "secondary completion" is ignored, because an async handler + // for return type runs _after_ the method is invoked, and if the invoked method throws an exception synchronously, + // there's no object the async handler could transform and so no "secondary completion" can possibly occur + @Test + @SpecAssertion(section = Sections.USING_INVOKER_BUILDER_FULL, id = "a") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "b") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + public void testSyncThrow() { + MyDependentBean.reset(); + + Invoker> helloThrow = getCurrentManager() + .getExtension(TestExtension.class).getInvoker("helloThrow"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + assertThrows(IllegalArgumentException.class, () -> { + helloThrow.invoke(null, new Object[] { null, future }); + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + + // doesn't do anything + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncType.java new file mode 100644 index 000000000..29c48c904 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +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/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeHandler.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeHandler.java new file mode 100644 index 000000000..b1c6940ba --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeHandler.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +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/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeImpl.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeImpl.java new file mode 100644 index 000000000..e3bbf1d08 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyAsyncTypeImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +import java.util.concurrent.CompletableFuture; + +final class MyAsyncTypeImpl implements MyAsyncType { + private final CompletableFuture future; + + MyAsyncTypeImpl(CompletableFuture future) { + this.future = future; + } + + @Override + public boolean isComplete() { + return future.isDone(); + } + + @Override + public T getIfComplete() { + if (future.isDone()) { + return future.getNow(null); + } else { + throw new IllegalStateException("not yet complete"); + } + } + + @Override + public MyAsyncType whenComplete(Runnable callback) { + CompletableFuture newFuture = new CompletableFuture<>(); + future.whenComplete((value, error) -> { + callback.run(); + + if (error == null) { + newFuture.complete(value); + } else { + newFuture.completeExceptionally(error); + } + }); + return new MyAsyncTypeImpl<>(newFuture); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyBean.java new file mode 100644 index 000000000..bbd997ffc --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyBean.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public MyAsyncType hello(MyDependentBean bean, CompletableFuture future) { + return MyAsyncType.from(future); + } + + public MyAsyncType helloThrow(MyDependentBean bean, CompletableFuture future) { + MyAsyncType ignored = MyAsyncType.from(future); + throw new IllegalArgumentException("synchronous throw"); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyDependentBean.java new file mode 100644 index 000000000..9391099ac --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/full/invokers/async/returntype/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.full.invokers.async.returntype; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/AsyncHandlerBuiltinTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/AsyncHandlerBuiltinTest.java new file mode 100644 index 000000000..19265c0ea --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/AsyncHandlerBuiltinTest.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.builtin; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.InvokerFactory; +import jakarta.enterprise.inject.build.compatible.spi.InvokerValidation; +import jakarta.enterprise.inject.build.compatible.spi.Registration; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; +import jakarta.enterprise.inject.build.compatible.spi.Validation; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderCreator; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderExtensionBase; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerBuiltinTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerBuiltinTest.class) + .withClasses(InvokerHolder.class, InvokerHolderCreator.class, InvokerHolderExtensionBase.class) + .withBuildCompatibleExtension(TestExtension.class) + .build(); + } + + public static class TestExtension extends InvokerHolderExtensionBase implements BuildCompatibleExtension { + @Registration(types = MyBean.class) + public void registration(BeanInfo bean, InvokerFactory invokers) { + registerInvokers(bean, invokers, Set.of("helloCS", "helloCF", "helloFP"), builder -> { + builder.withInstanceLookup(); + builder.withArgumentLookup(0); + }); + } + + @Synthesis + public void synthesis(SyntheticComponents syn) { + synthesizeInvokerHolder(syn); + } + + @Validation + public void validation(InvokerValidation invokers) { + invokers.ensureAsyncHandlerExists(CompletionStage.class); + invokers.ensureAsyncHandlerExists(CompletableFuture.class); + invokers.ensureAsyncHandlerExists(Flow.Publisher.class); + } + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "la") + public void testCompletionStage(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloCS"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + CompletionStage result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.toCompletableFuture().isDone()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.toCompletableFuture().isDone()); + assertEquals(result.toCompletableFuture().getNow(null), "hello"); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "lb") + public void testCompletableFuture(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloCF"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + CompletableFuture result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isDone()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isDone()); + assertEquals(result.getNow(null), "hello"); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "lc") + public void testFlowPublisher(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloFP"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + Flow.Publisher result = hello.invoke(null, new Object[] { null, future }); + + AtomicReference value = new AtomicReference<>(); + AtomicReference error = new AtomicReference<>(); + AtomicBoolean done = new AtomicBoolean(false); + + result.subscribe(new Flow.Subscriber<>() { + @Override + public void onSubscribe(Flow.Subscription subscription) { + subscription.request(1); + } + + @Override + public void onNext(String item) { + value.set(item); + } + + @Override + public void onError(Throwable throwable) { + error.set(throwable); + } + + @Override + public void onComplete() { + done.set(true); + } + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertNull(value.get()); + assertNull(error.get()); + assertFalse(done.get()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertEquals(value.get(), "hello"); + assertNull(error.get()); + assertTrue(done.get()); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/CompletableFuturePublisher.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/CompletableFuturePublisher.java new file mode 100644 index 000000000..e8a2eb3ae --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/CompletableFuturePublisher.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.builtin; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicInteger; + +public final class CompletableFuturePublisher implements Flow.Publisher { + private final CompletableFuture future; + + public CompletableFuturePublisher(CompletableFuture future) { + this.future = Objects.requireNonNull(future); + } + + @Override + public void subscribe(Flow.Subscriber subscriber) { + Objects.requireNonNull(subscriber); + subscriber.onSubscribe(new CompletableFutureSubscription<>(future, subscriber)); + } + + private static final class CompletableFutureSubscription implements Flow.Subscription { + private static final int STATE_NEW = 0; + private static final int STATE_PENDING = 1; + private static final int STATE_FINISHED = 2; + + private final CompletableFuture future; + private final Flow.Subscriber subscriber; + private final AtomicInteger state = new AtomicInteger(STATE_NEW); + + CompletableFutureSubscription(CompletableFuture future, Flow.Subscriber subscriber) { + this.future = future; + this.subscriber = subscriber; + } + + @Override + public void request(long n) { + if (n <= 0L) { + cancel(); + subscriber.onError(new IllegalArgumentException("Negative request: " + n)); + return; + } + + if (state.compareAndSet(STATE_NEW, STATE_PENDING)) { + future.whenComplete((value, error) -> { + if (state.compareAndSet(STATE_PENDING, STATE_FINISHED)) { + if (error != null) { + subscriber.onError(error); + } else if (value == null) { + subscriber.onError(new NullPointerException("CompletableFuture produced null")); + } else { + subscriber.onNext(value); + subscriber.onComplete(); + } + } + }); + } + } + + @Override + public void cancel() { + if (state.getAndSet(STATE_FINISHED) != STATE_FINISHED) { + future.cancel(false); + } + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyBean.java new file mode 100644 index 000000000..7ec1654b4 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyBean.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.builtin; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Flow; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public CompletionStage helloCS(MyDependentBean bean, CompletableFuture future) { + return helloCF(bean, future); + } + + public CompletableFuture helloCF(MyDependentBean bean, CompletableFuture future) { + CompletableFuture result = new CompletableFuture<>(); + future.whenComplete((value, error) -> { + if (error == null) { + result.complete(value); + } else { + result.completeExceptionally(error); + } + }); + return result; + } + + public Flow.Publisher helloFP(MyDependentBean bean, CompletableFuture future) { + return new CompletableFuturePublisher<>(future); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyDependentBean.java new file mode 100644 index 000000000..cf158ab8a --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/builtin/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.builtin; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKinds.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKinds.java new file mode 100644 index 000000000..1a849bc59 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKinds.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerBothKinds + implements AsyncHandler.ReturnType>, AsyncHandler.ParameterType> { + @Override + public MyAsyncType transform(MyAsyncType original, Runnable completion) { + return original; + } + + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKindsTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKindsTest.java new file mode 100644 index 000000000..cf3de1012 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/AsyncHandlerBothKindsTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerBothKindsTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerBothKindsTest.class) + .withClasses(MyAsyncType.class, AsyncHandlerBothKinds.class) + .withServiceProvider(AsyncHandler.ReturnType.class, AsyncHandlerBothKinds.class) + .withServiceProvider(AsyncHandler.ParameterType.class, AsyncHandlerBothKinds.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "d") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/MyAsyncType.java new file mode 100644 index 000000000..6d5549b6d --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/invalid/MyAsyncType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.invalid; + +public interface MyAsyncType { + // no methods needed, this is only used to test error cases +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/AsyncHandlerParamTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/AsyncHandlerParamTypeTest.java new file mode 100644 index 000000000..3d14eb02a --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/AsyncHandlerParamTypeTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.InvokerFactory; +import jakarta.enterprise.inject.build.compatible.spi.InvokerValidation; +import jakarta.enterprise.inject.build.compatible.spi.Registration; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; +import jakarta.enterprise.inject.build.compatible.spi.Validation; +import jakarta.enterprise.invoke.AsyncHandler; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderCreator; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderExtensionBase; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerParamTypeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerParamTypeTest.class) + .withClasses(InvokerHolder.class, InvokerHolderCreator.class, InvokerHolderExtensionBase.class) + .withServiceProvider(AsyncHandler.ParameterType.class, MyAsyncTypeHandler.class) + .withBuildCompatibleExtension(TestExtension.class) + .build(); + } + + public static class TestExtension extends InvokerHolderExtensionBase implements BuildCompatibleExtension { + @Registration(types = MyBean.class) + public void registration(BeanInfo bean, InvokerFactory invokers) { + registerInvokers(bean, invokers, Set.of("helloSync", "helloAsync", "helloThrow"), builder -> { + builder.withInstanceLookup(); + builder.withArgumentLookup(0); + }); + } + + @Synthesis + public void synthesis(SyntheticComponents syn) { + synthesizeInvokerHolder(syn); + } + + @Validation + public void validation(InvokerValidation invokers) { + invokers.ensureAsyncHandlerExists(MyAsyncType.class); + } + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cf") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ha") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "j") + public void testSync(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloSync"); + MyAsyncType result = MyAsyncType.createSuspended(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + hello.invoke(null, new Object[] { null, result }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cf") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "hb") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "j") + public void testAsync(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloAsync"); + CompletableFuture future = new CompletableFuture<>(); + MyAsyncType result = MyAsyncType.createSuspended(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + hello.invoke(null, new Object[] { null, future, result }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isComplete()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + // this test verifies both that dependent instances created for the invocation are destroyed + // in case of a synchronous exception and that a "secondary completion" is ignored + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cf") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "hc") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "k") + public void testSyncThrow(InvokerHolder invokers) { + MyDependentBean.reset(); + + Invoker> helloThrow = invokers.get("helloThrow"); + CompletableFuture future = new CompletableFuture<>(); + MyAsyncType result = MyAsyncType.createSuspended(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + assertThrows(IllegalArgumentException.class, () -> { + helloThrow.invoke(null, new Object[] { null, future, result }); + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + + // calls the completion callback, which must be a noop + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncType.java new file mode 100644 index 000000000..3926a34b6 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +public interface MyAsyncType { + boolean isComplete(); + + T getIfComplete(); + + void whenComplete(Runnable callback); + + void resume(T value); + + static MyAsyncType createSuspended() { + return new MyAsyncTypeImpl<>(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeHandler.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeHandler.java new file mode 100644 index 000000000..a4a019776 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeHandler.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class MyAsyncTypeHandler implements AsyncHandler.ParameterType> { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + original.whenComplete(completion); + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeImpl.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeImpl.java new file mode 100644 index 000000000..57eb4fbec --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyAsyncTypeImpl.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +import java.util.concurrent.atomic.AtomicReference; + +final class MyAsyncTypeImpl implements MyAsyncType { + private final AtomicReference value = new AtomicReference<>(null); + private final AtomicReference callback = new AtomicReference<>(null); + + MyAsyncTypeImpl() { + } + + @Override + public boolean isComplete() { + return value.get() != null; + } + + @Override + public T getIfComplete() { + T value = this.value.get(); + if (value != null) { + return value; + } else { + throw new IllegalStateException("not yet complete"); + } + } + + @Override + public void whenComplete(Runnable callback) { + if (callback == null) { + throw new IllegalArgumentException("callback may not be null"); + } + if (!this.callback.compareAndSet(null, callback)) { + throw new IllegalStateException("only one callback possible"); + } + } + + @Override + public void resume(T value) { + if (value == null) { + throw new IllegalArgumentException("must resume with non-null value"); + } + if (this.value.compareAndSet(null, value)) { + Runnable callback = this.callback.get(); + if (callback != null) { + callback.run(); + } + } + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyBean.java new file mode 100644 index 000000000..7559a60fa --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyBean.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +import static org.testng.Assert.assertNull; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public void helloSync(MyDependentBean bean, MyAsyncType async) { + async.resume("hello"); + } + + public void helloAsync(MyDependentBean bean, CompletableFuture future, MyAsyncType async) { + future.whenComplete((value, error) -> { + assertNull(error); + async.resume(value); + }); + } + + public void helloThrow(MyDependentBean bean, CompletableFuture future, MyAsyncType async) { + future.whenComplete((value, error) -> { + assertNull(error); + async.resume(value); + }); + throw new IllegalArgumentException("synchronous throw"); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyDependentBean.java new file mode 100644 index 000000000..6b58777c0 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler1.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler1.java new file mode 100644 index 000000000..16c7d4a97 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler1.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandler1 implements AsyncHandler.ParameterType> { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler2.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler2.java new file mode 100644 index 000000000..9a4e28567 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandler2.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandler2 implements AsyncHandler.ParameterType> { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArray.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArray.java new file mode 100644 index 000000000..cc41ead75 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArray.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerArray implements AsyncHandler.ParameterType { + @Override + public String[] transformArgument(String[] original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArrayTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArrayTest.java new file mode 100644 index 000000000..36fdc1896 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerArrayTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerArrayTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerArrayTest.class) + .withClass(AsyncHandlerArray.class) + .withServiceProvider(AsyncHandler.ParameterType.class, AsyncHandlerArray.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cc") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirect.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirect.java new file mode 100644 index 000000000..a47c4b274 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirect.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +public class AsyncHandlerIndirect extends AsyncHandlerSubclass> { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirectTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirectTest.java new file mode 100644 index 000000000..5ffdc0aef --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerIndirectTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerIndirectTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerIndirectTest.class) + .withClasses(MyAsyncType.class, AsyncHandlerSubclass.class, AsyncHandlerIndirect.class) + .withServiceProvider(AsyncHandler.ParameterType.class, AsyncHandlerIndirect.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cd") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRaw.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRaw.java new file mode 100644 index 000000000..b833409df --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRaw.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerRaw implements AsyncHandler.ParameterType { + @Override + public Object transformArgument(Object original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRawTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRawTest.java new file mode 100644 index 000000000..7d7b66f82 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerRawTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerRawTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerRawTest.class) + .withClass(AsyncHandlerRaw.class) + .withServiceProvider(AsyncHandler.ParameterType.class, AsyncHandlerRaw.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ca") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerSubclass.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerSubclass.java new file mode 100644 index 000000000..48c02c43f --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerSubclass.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public abstract class AsyncHandlerSubclass implements AsyncHandler.ParameterType { +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariable.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariable.java new file mode 100644 index 000000000..f79ccf1ec --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariable.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerTypeVariable implements AsyncHandler.ParameterType { + @Override + public T transformArgument(T original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariableTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariableTest.java new file mode 100644 index 000000000..88b744566 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/AsyncHandlerTypeVariableTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerTypeVariableTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerTypeVariableTest.class) + .withClass(AsyncHandlerTypeVariable.class) + .withServiceProvider(AsyncHandler.ParameterType.class, AsyncHandlerTypeVariable.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "cb") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MultipleAsyncHandlersForOneParamTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MultipleAsyncHandlersForOneParamTypeTest.java new file mode 100644 index 000000000..185e4a706 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MultipleAsyncHandlersForOneParamTypeTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +import java.util.List; + +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class MultipleAsyncHandlersForOneParamTypeTest extends AbstractTest { + @Deployment + @ShouldThrowException(DeploymentException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(MultipleAsyncHandlersForOneParamTypeTest.class) + .withClasses(MyAsyncType.class, AsyncHandler1.class, AsyncHandler2.class) + .withServiceProvider(AsyncHandler.ParameterType.class, List.of(AsyncHandler1.class, AsyncHandler2.class)) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "m") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MyAsyncType.java new file mode 100644 index 000000000..7c115fec2 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/invalid/MyAsyncType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.invalid; + +public interface MyAsyncType { + // no methods needed, this is only used to test error cases +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/AsyncHandlerMultipleParamsMatchTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/AsyncHandlerMultipleParamsMatchTest.java new file mode 100644 index 000000000..eec8838db --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/AsyncHandlerMultipleParamsMatchTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.multiple; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.InvokerFactory; +import jakarta.enterprise.inject.build.compatible.spi.InvokerValidation; +import jakarta.enterprise.inject.build.compatible.spi.Registration; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; +import jakarta.enterprise.inject.build.compatible.spi.Validation; +import jakarta.enterprise.invoke.AsyncHandler; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderCreator; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderExtensionBase; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerMultipleParamsMatchTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerMultipleParamsMatchTest.class) + .withClasses(InvokerHolder.class, InvokerHolderCreator.class, InvokerHolderExtensionBase.class) + .withServiceProvider(AsyncHandler.ParameterType.class, MyAsyncTypeHandler.class) + .withBuildCompatibleExtension(TestExtension.class) + .build(); + } + + public static class TestExtension extends InvokerHolderExtensionBase implements BuildCompatibleExtension { + @Registration(types = MyBean.class) + public void registration(BeanInfo bean, InvokerFactory invokers) { + registerInvokers(bean, invokers, Set.of("hello"), builder -> { + builder.withInstanceLookup(); + builder.withArgumentLookup(0); + }); + } + + @Synthesis + public void synthesis(SyntheticComponents syn) { + synthesizeInvokerHolder(syn); + } + + @Validation + public void validation(InvokerValidation invokers) { + invokers.ensureAsyncHandlerExists(MyAsyncType.class); + } + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ce") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "f") + public void test(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker hello = invokers.get("hello"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + hello.invoke(null, new Object[] { null, future, null, null }); + + // _not_ async (multiple matching parameters), so destroyed synchronously + assertFalse(MyBean.futureComplete); + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + + future.complete("hello"); + + assertTrue(MyBean.futureComplete); + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncType.java new file mode 100644 index 000000000..ba5aedd43 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.multiple; + +public interface MyAsyncType { + // no methods needed, this is only used to test error cases +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncTypeHandler.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncTypeHandler.java new file mode 100644 index 000000000..63bbc181d --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyAsyncTypeHandler.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.multiple; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class MyAsyncTypeHandler implements AsyncHandler.ParameterType { + @Override + public MyAsyncType transformArgument(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyBean.java new file mode 100644 index 000000000..00c7d6cba --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyBean.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.multiple; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + static boolean futureComplete = false; + + public void hello(MyDependentBean bean, CompletableFuture future, MyAsyncType async1, MyAsyncType async2) { + future.whenComplete((value, error) -> { + futureComplete = true; + }); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyDependentBean.java new file mode 100644 index 000000000..58a7c4330 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/paramtype/multiple/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.paramtype.multiple; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/AsyncHandlerReturnTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/AsyncHandlerReturnTypeTest.java new file mode 100644 index 000000000..45e48c98e --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/AsyncHandlerReturnTypeTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.InvokerFactory; +import jakarta.enterprise.inject.build.compatible.spi.InvokerValidation; +import jakarta.enterprise.inject.build.compatible.spi.Registration; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; +import jakarta.enterprise.inject.build.compatible.spi.Validation; +import jakarta.enterprise.invoke.AsyncHandler; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolder; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderCreator; +import org.jboss.cdi.tck.tests.invokers.InvokerHolderExtensionBase; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerReturnTypeTest extends AbstractTest { + @Deployment + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClassPackage(AsyncHandlerReturnTypeTest.class) + .withClasses(InvokerHolder.class, InvokerHolderCreator.class, InvokerHolderExtensionBase.class) + .withServiceProvider(AsyncHandler.ReturnType.class, MyAsyncTypeHandler.class) + .withBuildCompatibleExtension(TestExtension.class) + .build(); + } + + public static class TestExtension extends InvokerHolderExtensionBase implements BuildCompatibleExtension { + @Registration(types = MyBean.class) + public void registration(BeanInfo bean, InvokerFactory invokers) { + registerInvokers(bean, invokers, Set.of("helloSync", "helloAsync", "helloThrow"), builder -> { + builder.withInstanceLookup(); + builder.withArgumentLookup(0); + }); + } + + @Synthesis + public void synthesis(SyntheticComponents syn) { + synthesizeInvokerHolder(syn); + } + + @Validation + public void validation(InvokerValidation invokers) { + invokers.ensureAsyncHandlerExists(MyAsyncType.class); + } + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "be") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ga") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "j") + public void testSync(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloSync"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + MyAsyncType result = hello.invoke(null, new Object[] { null }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "be") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "gb") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "i") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "j") + public void testAsync(InvokerHolder invokers) throws Exception { + MyDependentBean.reset(); + + Invoker> hello = invokers.get("helloAsync"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + MyAsyncType result = hello.invoke(null, new Object[] { null, future }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + assertFalse(result.isComplete()); + + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + assertTrue(result.isComplete()); + assertEquals(result.getIfComplete(), "hello"); + } + + // this test only verifies that dependent instances created for the invocation are destroyed in case of a synchronous + // exception, but it does not (and cannot) verify that a "secondary completion" is ignored, because a return type + // async handler runs _after_ the method is invoked, and if the invoked method throws an exception synchronously, + // there's no object the async handler could transform and so no "secondary completion" can possibly occur + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "be") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "gc") + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "k") + public void testSyncThrow(InvokerHolder invokers) { + MyDependentBean.reset(); + + Invoker> helloThrow = invokers.get("helloThrow"); + CompletableFuture future = new CompletableFuture<>(); + + assertEquals(MyDependentBean.destroyedCounter.get(), 0); + + assertThrows(IllegalArgumentException.class, () -> { + helloThrow.invoke(null, new Object[] { null, future }); + }); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + + // doesn't do anything + future.complete("hello"); + + assertEquals(MyDependentBean.destroyedCounter.get(), 1); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncType.java new file mode 100644 index 000000000..1ddfd30ce --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncType.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +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/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeHandler.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeHandler.java new file mode 100644 index 000000000..4e38bb48d --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeHandler.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +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/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeImpl.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeImpl.java new file mode 100644 index 000000000..9720cce56 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyAsyncTypeImpl.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +import java.util.concurrent.CompletableFuture; + +final class MyAsyncTypeImpl implements MyAsyncType { + private final CompletableFuture future; + + MyAsyncTypeImpl(CompletableFuture future) { + this.future = future; + } + + @Override + public boolean isComplete() { + return future.isDone(); + } + + @Override + public T getIfComplete() { + if (future.isDone()) { + return future.getNow(null); + } else { + throw new IllegalStateException("not yet complete"); + } + } + + @Override + public MyAsyncType whenComplete(Runnable callback) { + CompletableFuture newFuture = new CompletableFuture<>(); + future.whenComplete((value, error) -> { + callback.run(); + + if (error == null) { + newFuture.complete(value); + } else { + newFuture.completeExceptionally(error); + } + }); + return new MyAsyncTypeImpl<>(newFuture); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyBean.java new file mode 100644 index 000000000..97fb1b249 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyBean.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +import java.util.concurrent.CompletableFuture; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class MyBean { + public MyAsyncType helloSync(MyDependentBean bean) { + return MyAsyncType.from(CompletableFuture.completedFuture("hello")); + } + + public MyAsyncType helloAsync(MyDependentBean bean, CompletableFuture future) { + return MyAsyncType.from(future); + } + + public MyAsyncType helloThrow(MyDependentBean bean, CompletableFuture future) { + MyAsyncType ignored = MyAsyncType.from(future); + throw new IllegalArgumentException("synchronous throw"); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyDependentBean.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyDependentBean.java new file mode 100644 index 000000000..6d70c1c78 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/MyDependentBean.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype; + +import java.util.concurrent.atomic.AtomicInteger; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyDependentBean { + public static AtomicInteger destroyedCounter = new AtomicInteger(0); + + public static void reset() { + destroyedCounter.set(0); + } + + @PreDestroy + public void destroy() { + destroyedCounter.incrementAndGet(); + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler1.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler1.java new file mode 100644 index 000000000..8c1c2f92a --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler1.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandler1 implements AsyncHandler.ReturnType> { + @Override + public MyAsyncType transform(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler2.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler2.java new file mode 100644 index 000000000..990bb8de6 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandler2.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandler2 implements AsyncHandler.ReturnType> { + @Override + public MyAsyncType transform(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArray.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArray.java new file mode 100644 index 000000000..245455671 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArray.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerArray implements AsyncHandler.ReturnType { + @Override + public String[] transform(String[] original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArrayTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArrayTest.java new file mode 100644 index 000000000..78ed6f956 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerArrayTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerArrayTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerArrayTest.class) + .withClass(AsyncHandlerArray.class) + .withServiceProvider(AsyncHandler.ReturnType.class, AsyncHandlerArray.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "bc") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirect.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirect.java new file mode 100644 index 000000000..be065c965 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirect.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +public class AsyncHandlerIndirect extends AsyncHandlerSubclass> { + @Override + public MyAsyncType transform(MyAsyncType original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirectTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirectTest.java new file mode 100644 index 000000000..0e652d9c2 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerIndirectTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerIndirectTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerIndirectTest.class) + .withClasses(MyAsyncType.class, AsyncHandlerSubclass.class, AsyncHandlerIndirect.class) + .withServiceProvider(AsyncHandler.ReturnType.class, AsyncHandlerIndirect.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "bd") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRaw.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRaw.java new file mode 100644 index 000000000..63bb53cb2 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRaw.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerRaw implements AsyncHandler.ReturnType { + @Override + public Object transform(Object original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRawTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRawTest.java new file mode 100644 index 000000000..92a171306 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerRawTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerRawTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerRawTest.class) + .withClass(AsyncHandlerRaw.class) + .withServiceProvider(AsyncHandler.ReturnType.class, AsyncHandlerRaw.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "ba") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerSubclass.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerSubclass.java new file mode 100644 index 000000000..87d1a359e --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerSubclass.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public abstract class AsyncHandlerSubclass implements AsyncHandler.ReturnType { +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariable.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariable.java new file mode 100644 index 000000000..aba458e51 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariable.java @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.invoke.AsyncHandler; + +public class AsyncHandlerTypeVariable implements AsyncHandler.ReturnType { + @Override + public T transform(T original, Runnable completion) { + return original; + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariableTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariableTest.java new file mode 100644 index 000000000..ecc9df3f7 --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/AsyncHandlerTypeVariableTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import jakarta.enterprise.inject.spi.DefinitionException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class AsyncHandlerTypeVariableTest extends AbstractTest { + @Deployment + @ShouldThrowException(DefinitionException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(AsyncHandlerTypeVariableTest.class) + .withClass(AsyncHandlerTypeVariable.class) + .withServiceProvider(AsyncHandler.ReturnType.class, AsyncHandlerTypeVariable.class) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "bb") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MultipleAsyncHandlersForOneReturnTypeTest.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MultipleAsyncHandlersForOneReturnTypeTest.java new file mode 100644 index 000000000..3d6b463cd --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MultipleAsyncHandlersForOneReturnTypeTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +import java.util.List; + +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.enterprise.invoke.AsyncHandler; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.cdi.tck.AbstractTest; +import org.jboss.cdi.tck.cdi.Sections; +import org.jboss.cdi.tck.shrinkwrap.WebArchiveBuilder; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.test.audit.annotations.SpecAssertion; +import org.jboss.test.audit.annotations.SpecVersion; +import org.testng.annotations.Test; + +@SpecVersion(spec = "cdi", version = "5.0") +public class MultipleAsyncHandlersForOneReturnTypeTest extends AbstractTest { + @Deployment + @ShouldThrowException(DeploymentException.class) + public static WebArchive createTestArchive() { + return new WebArchiveBuilder() + .withTestClass(MultipleAsyncHandlersForOneReturnTypeTest.class) + .withClasses(MyAsyncType.class, AsyncHandler1.class, AsyncHandler2.class) + .withServiceProvider(AsyncHandler.ReturnType.class, List.of(AsyncHandler1.class, AsyncHandler2.class)) + .build(); + } + + @Test(dataProvider = ARQUILLIAN_DATA_PROVIDER) + @SpecAssertion(section = Sections.INVOKER_ASYNCHRONOUS_METHODS, id = "m") + public void trigger() { + } +} diff --git a/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MyAsyncType.java b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MyAsyncType.java new file mode 100644 index 000000000..f9bd01c6d --- /dev/null +++ b/impl/src/main/java/org/jboss/cdi/tck/tests/invokers/lookup/dependent/async/returntype/invalid/MyAsyncType.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * Apache Software License 2.0 which is available at: + * https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.jboss.cdi.tck.tests.invokers.lookup.dependent.async.returntype.invalid; + +public interface MyAsyncType { + // no methods needed, this is only used to test error cases +} diff --git a/impl/src/main/resources/tck-audit-cdi.xml b/impl/src/main/resources/tck-audit-cdi.xml index fef5513c9..349cbfccf 100644 --- a/impl/src/main/resources/tck-audit-cdi.xml +++ b/impl/src/main/resources/tck-audit-cdi.xml @@ -6437,7 +6437,7 @@ -
+
The |InvokerBuilder| allows specifying that the |instance| or any of the |arguments| passed to |Invoker.invoke()| should be ignored and a value should be looked up from the CDI container instead. @@ -6521,6 +6521,154 @@
+
+ + + Sometimes, the action started by the method does not necessarily finish when the method returns; the _completion_ of the action is asynchronous to the method call. + Such methods are called _asynchronous_ methods. + Method invokers have special support for asynchronous methods when it comes to destroying instances of looked up |@Dependent| beans. + + An _async handler_ is either a return type async handler or a parameter type async handler. + + Introductory text, no requirements. + + + + A _return type async handler_ is a service provider of |jakarta.enterprise.invoke.AsyncHandler.ReturnType| declared in |META-INF/services|. + A return type async handler must have a direct superinterface type that is a parameterized type whose generic interface is |AsyncHandler.ReturnType| and its sole type argument is a class type, interface type or parameterized type, otherwise definition error occurs. + The erasure of the type argument to |AsyncHandler.ReturnType| is called the _async type_ of the return type async handler. + The target method matches a return type async handler if the erasure of the method's return type is identical to the async type of the async handler. + + + Test with raw async handler. + + + Test with type variable as the type argument to async handler. + + + Test with array type as the type argument to async handler. + + + Test with indirect implementation of async handler. + + + No error. + + + + + A _parameter type async handler_ is a service provider of |jakarta.enterprise.invoke.AsyncHandler.ParameterType| declared in |META-INF/services|. + A parameter type async handler must have a direct superinterface type that is a parameterized type whose generic interface is |AsyncHandler.ParameterType| and its sole type argument is a class type, interface type or parameterized type, otherwise definition error occurs. + The erasure of the type argument to |AsyncHandler.ParameterType| is called the _async type_ of the parameter type async handler. + The target method matches a parameter type async handler if it declares exactly one parameter whose type's erasure is identical to the async type of the async handler; this parameter is called the _async parameter_. + + + Test with raw async handler. + + + Test with type variable as the type argument to async handler. + + + Test with array type as the type argument to async handler. + + + Test with indirect implementation of async handler. + + + Test with two parameters that match an async handler. + + + No error. + + + + If an async handler is both a return type async handler and parameter type async handler, definition error occurs. + + + An async handler must be stateless (and therefore thread safe); there are no guarantees about its lifecycle. + The only requirement stated here (statelessness) is a requirement on outside code, not on the container. + + + If the target method matches exactly one async handler, the invoker becomes asynchronous. + Test with multiple matching async handlers. + + + + If the matching async handler is a return type async handler, an asynchronous invoker calls its |transform()| method once during |invoke()|, after the target method returns, with the return value of the target method as the |original|, and returns the result to the caller. + When the target method throws an exception synchronously, the |transform()| method is not called. + + + Test synchronous completion. + + + Test asynchronous completion. + + + Test synchronously thrown exception. + + + + + If the matching async handler is a parameter type async handler, an asynchronous invoker: + + calls its |transformArgument()| method once during |invoke()|, before the target method is called, with the given argument value of the async parameter as the |original|, and passes the result to the target method as the argument value of the async parameter; + + calls its |transformReturnValue()| method once during |invoke()|, after the target method returns, with the return value of the target method as the |original|, and returns the result to the caller. + + When the target method throws an exception synchronously, the |transformReturnValue()| method is not called. + + + Test synchronous completion. + + + Test asynchronous completion. + + + Test synchronously thrown exception. + + + + The async handler signals completion of the asynchronous action by calling |completion.run()|. + + + + The requirement to destroy instances of |@Dependent| looked up beans is different for asynchronous invokers. + The instances of |@Dependent| looked up beans are destroyed when the asynchronous action completes, as signaled by the async handler. + + + + + If an asynchronous target method throws synchronously, instances of |@Dependent| looked up beans are destroyed + before |invoke()| rethrows the exception; in this case, the async handler is still permitted to call + |completion.run()|, but the call must be ignored. + + + + The CDI container must include return type async handlers for the following types: + + |java.util.concurrent.CompletionStage| + + + |java.util.concurrent.CompletableFuture| + + + |java.util.concurrent.Flow.Publisher| + + + + If two async handlers have the same async type, by default a deployment problem occurs. + + + + Containers are required to provide an implementation-defined means of configuring the async handler that should be used for a particular async type. + If such configuration is present, other async handlers for the given async type are ignored; the target method only matches the configured async handler. + If the configured async handler does not exist or its async type is different from the configured one, deployment problem occurs. + It is possible to configure a custom async handler for the built-in types listed above. + + Implementation-defined behavior. + +
+