Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,32 @@ public T withBuildCompatibleExtensions(Class<? extends BuildCompatibleExtension>
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/<service class>} whose content is {@code <service provider class>}.
*
* @param serviceClass
* @param serviceProviderClass
* @return
* @param <SP>
*/
public <SP> T withServiceProvider(Class<SP> serviceClass, Class<? extends SP> 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/<service class>} whose content is {@code <service provider classes>}.
*
* @param serviceClass
* @param serviceProviderClasses
* @return
* @param <SP>
*/
public <SP> T withServiceProvider(Class<SP> serviceClass, List<Class<? extends SP>> serviceProviderClasses) {
return withServiceProvider(new ServiceProviderDescriptor(serviceClass, serviceProviderClasses.toArray(new Class[0])));
}

/**
* Add service provider.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Invoker<?, ?>> invokers = new HashMap<>();

public void registration(@Observes ProcessManagedBean<MyBean> pmb) {
Set<String> methods = Set.of("helloCS", "helloCF", "helloFP");
for (AnnotatedMethod<? super MyBean> method : pmb.getAnnotatedBeanClass().getMethods()) {
if (methods.contains(method.getJavaMember().getName())) {
Invoker<MyBean, ?> 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 <T, R> Invoker<T, R> getInvoker(String name) {
return (Invoker<T, R>) 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<MyBean, CompletionStage<String>> hello = getCurrentManager()
.getExtension(TestExtension.class).getInvoker("helloCS");
CompletableFuture<String> future = new CompletableFuture<>();

assertEquals(MyDependentBean.destroyedCounter.get(), 0);

CompletionStage<String> 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<MyBean, CompletableFuture<String>> hello = getCurrentManager()
.getExtension(TestExtension.class).getInvoker("helloCF");
CompletableFuture<String> future = new CompletableFuture<>();

assertEquals(MyDependentBean.destroyedCounter.get(), 0);

CompletableFuture<String> 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<MyBean, Flow.Publisher<String>> hello = getCurrentManager()
.getExtension(TestExtension.class).getInvoker("helloFP");
CompletableFuture<String> future = new CompletableFuture<>();

assertEquals(MyDependentBean.destroyedCounter.get(), 0);

Flow.Publisher<String> result = hello.invoke(null, new Object[] { null, future });

AtomicReference<String> value = new AtomicReference<>();
AtomicReference<Throwable> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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<T> implements Flow.Publisher<T> {
private final CompletableFuture<T> future;

public CompletableFuturePublisher(CompletableFuture<T> future) {
this.future = Objects.requireNonNull(future);
}

@Override
public void subscribe(Flow.Subscriber<? super T> subscriber) {
Objects.requireNonNull(subscriber);
subscriber.onSubscribe(new CompletableFutureSubscription<>(future, subscriber));
}

private static final class CompletableFutureSubscription<T> 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<T> future;
private final Flow.Subscriber<? super T> subscriber;
private final AtomicInteger state = new AtomicInteger(STATE_NEW);

CompletableFutureSubscription(CompletableFuture<T> future, Flow.Subscriber<? super T> 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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<String> helloCS(MyDependentBean bean, CompletableFuture<String> future) {
return helloCF(bean, future);
}

public CompletableFuture<String> helloCF(MyDependentBean bean, CompletableFuture<String> future) {
CompletableFuture<String> result = new CompletableFuture<>();
future.whenComplete((value, error) -> {
if (error == null) {
result.complete(value);
} else {
result.completeExceptionally(error);
}
});
return result;
}

public Flow.Publisher<String> helloFP(MyDependentBean bean, CompletableFuture<String> future) {
return new CompletableFuturePublisher<>(future);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading