This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new 4f8c1e0af1c CAUSEWAY-3883: fixes AsyncProxyInternal#join not waiting
4f8c1e0af1c is described below
commit 4f8c1e0af1c59b16aa48ce20b92f43b979e48171
Author: Andi Huber <[email protected]>
AuthorDate: Fri Jun 27 18:20:23 2025 +0200
CAUSEWAY-3883: fixes AsyncProxyInternal#join not waiting
---
.../applib/services/wrapper/WrapperFactory.java | 9 ++--
.../core/metamodel/object/MmEntityUtils.java | 17 +++++--
.../wrapper/AsyncExecutionFinisher.java | 8 +--
.../runtimeservices/wrapper/AsyncExecutor.java | 18 +++++--
.../wrapper/AsyncProxyInternal.java | 41 +++++++++------
.../wrapper/WrapperFactoryDefault.java | 53 ++++++++------------
.../BackgroundService_IntegTestAbstract.java | 58 +++++++++++++++-------
.../integtests/WrapperFactory_async_IntegTest.java | 42 +++++++++++-----
8 files changed, 151 insertions(+), 95 deletions(-)
diff --git
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrapperFactory.java
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrapperFactory.java
index d64fd649f3d..1237f360695 100644
---
a/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrapperFactory.java
+++
b/api/applib/src/main/java/org/apache/causeway/applib/services/wrapper/WrapperFactory.java
@@ -21,8 +21,9 @@
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.function.Function;
+
+import org.springframework.util.function.ThrowingConsumer;
+import org.springframework.util.function.ThrowingFunction;
import org.apache.causeway.applib.exceptions.recoverable.InteractionException;
import org.apache.causeway.applib.services.factory.FactoryService;
@@ -78,8 +79,8 @@ public interface WrapperFactory {
* @see CompletableFuture
*/
interface AsyncProxy<T> {
- AsyncProxy<Void> thenAcceptAsync(Consumer<? super T> action);
- <U> AsyncProxy<U> thenApplyAsync(Function<? super T, ? extends U> fn);
+ AsyncProxy<Void> thenAcceptAsync(ThrowingConsumer<? super T> action);
+ <U> AsyncProxy<U> thenApplyAsync(ThrowingFunction<? super T, ? extends
U> fn);
AsyncProxy<T> orTimeout(long timeout, TimeUnit unit);
T join();
}
diff --git
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmEntityUtils.java
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmEntityUtils.java
index 3a15d768b1e..2f59e567d83 100644
---
a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmEntityUtils.java
+++
b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/object/MmEntityUtils.java
@@ -21,6 +21,7 @@
import java.util.Optional;
import java.util.stream.Stream;
+import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.apache.causeway.applib.services.repository.EntityState;
@@ -29,11 +30,11 @@
import
org.apache.causeway.core.config.beans.CausewayBeanMetaData.PersistenceStack;
import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet;
import
org.apache.causeway.core.metamodel.facets.properties.property.entitychangepublishing.EntityPropertyChangePublishingPolicyFacet;
+import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
import
org.apache.causeway.core.metamodel.services.objectlifecycle.PropertyChangeRecordId;
import org.apache.causeway.core.metamodel.spec.feature.MixedIn;
import org.apache.causeway.core.metamodel.spec.feature.OneToOneAssociation;
-import org.jspecify.annotations.NonNull;
import lombok.experimental.UtilityClass;
@UtilityClass
@@ -62,18 +63,24 @@ public EntityState getEntityState(final @Nullable
ManagedObject adapter) {
public void persistInCurrentTransaction(final ManagedObject managedObject)
{
requiresEntity(managedObject);
- var spec = managedObject.objSpec();
- var entityFacet = spec.entityFacetElseFail();
+ var entityFacet = managedObject.objSpec().entityFacetElseFail();
entityFacet.persist(managedObject.getPojo());
}
public void deleteInCurrentTransaction(final ManagedObject managedObject) {
requiresEntity(managedObject);
- var spec = managedObject.objSpec();
- var entityFacet = spec.entityFacetElseFail();
+ var entityFacet = managedObject.objSpec().entityFacetElseFail();
entityFacet.delete(managedObject.getPojo());
}
+ public <T> T detachedPojo(ObjectManager objectManager, @Nullable T pojo) {
+ if(pojo == null) return null;
+ var managedObject = objectManager.adapt(pojo);
+ return isAttachedEntity(managedObject)
+ ? managedObject.objSpec().entityFacetElseFail().detach(pojo)
+ : pojo;
+ }
+
public void requiresEntity(final ManagedObject managedObject) {
if(ManagedObjects.isNullOrUnspecifiedOrEmpty(managedObject)) {
throw _Exceptions.illegalArgument("requires an entity object but
got null, unspecified or empty");
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutionFinisher.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutionFinisher.java
index e9a40b325b2..c98e6d35ca0 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutionFinisher.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutionFinisher.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.core.runtimeservices.wrapper;
+import org.jspecify.annotations.Nullable;
+
import org.apache.causeway.applib.services.repository.RepositoryService;
import org.apache.causeway.applib.services.wrapper.WrapperFactory;
import org.apache.causeway.core.metamodel.object.MmEntityUtils;
@@ -29,13 +31,13 @@ record AsyncExecutionFinisher(
ObjectManager objectManager
) {
- public <T> T finish(T t) {
+ public <T> T finish(@Nullable T t) {
+ if(t==null) return t;
var pojo = wrapperFactory.unwrap(t);
-
var domainObject = objectManager.adapt(pojo);
if(MmEntityUtils.isAttachedEntity(domainObject)) {
repositoryService.persistAndFlush(pojo);
- return repositoryService.detach(pojo);
+ pojo = repositoryService.detach(pojo);
}
return pojo;
}
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutor.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutor.java
index f85921c92b7..734a697c4da 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutor.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncExecutor.java
@@ -48,6 +48,7 @@ record AsyncExecutor(
* but does NOT throw any exceptions if a transaction exists.
*/
Optional<Propagation> propagation,
+ AsyncExecutionFinisher finisher,
ExecutorService delegate) implements ExecutorService {
@Override
@@ -119,19 +120,28 @@ public boolean isTerminated() {
private void run(ThrowingRunnable runnable) {
if(propagation.isEmpty())
- interactionService.run(interactionContext, runnable);
+ interactionService.run(interactionContext, ()->finish(runnable));
else
interactionService.run(interactionContext, ()->transactionService
- .runTransactional(propagation().get(), runnable)
+ .runTransactional(propagation().get(), ()->finish(runnable))
.ifFailureFail());
}
private <T> T call(Callable<T> callable) {
return propagation.isEmpty()
- ? interactionService.call(interactionContext, callable)
+ ? interactionService.call(interactionContext, ()->finish(callable))
: interactionService.call(interactionContext,
()->transactionService
- .callTransactional(propagation().get(), callable)
+ .callTransactional(propagation().get(), ()->finish(callable))
.valueAsNullableElseFail());
}
+ private <T> T finish(Callable<T> callable) throws Exception {
+ return finisher.finish(callable.call());
+ }
+
+ private void finish(ThrowingRunnable runnable) throws Exception {
+ runnable.run();
+ finisher.finish(null);
+ }
+
}
\ No newline at end of file
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncProxyInternal.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncProxyInternal.java
index 502486f42a0..e475eb4dccb 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncProxyInternal.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/AsyncProxyInternal.java
@@ -18,39 +18,48 @@
*/
package org.apache.causeway.core.runtimeservices.wrapper;
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.function.Function;
+
+import org.springframework.util.function.ThrowingConsumer;
+import org.springframework.util.function.ThrowingFunction;
import org.apache.causeway.applib.services.wrapper.WrapperFactory.AsyncProxy;
-//TODO this is just a proof of concept; chaining makes non sense once future
is no longer a proxy
+import lombok.SneakyThrows;
+
+//TODO this is just a proof of concept; chaining makes non sense once future
no longer holds a proxy
record AsyncProxyInternal<T>(
- CompletableFuture<T> future,
- AsyncExecutor executor,
- AsyncExecutionFinisher finisher) implements AsyncProxy<T> {
+ Future<T> future,
+ AsyncExecutor executor) implements AsyncProxy<T> {
- @Override public AsyncProxy<Void> thenAcceptAsync(Consumer<? super T>
action) {
- return map(in->in.thenAcceptAsync(action, executor));
+ @Override public AsyncProxy<Void> thenAcceptAsync(ThrowingConsumer<? super
T> action) {
+ return thenApplyAsync(adapt(action));
}
- @Override public <U> AsyncProxy<U> thenApplyAsync(Function<? super T, ?
extends U> fn) {
- return map(in->in.thenApplyAsync(fn, executor));
+ @Override public <U> AsyncProxy<U> thenApplyAsync(ThrowingFunction<? super
T, ? extends U> fn) {
+ return map(()->fn.apply(future.get()));
}
@Override public AsyncProxy<T> orTimeout(long timeout, TimeUnit unit) {
- return map(in->in.orTimeout(timeout, unit));
+ return map(()->future.get(timeout, unit));
}
+ @SneakyThrows
@Override public T join() {
- var t = future.join();
- return finisher.finish(t);
+ return future.get();
}
// -- HELPER
- private <U> AsyncProxy<U> map(Function<CompletableFuture<T>,
CompletableFuture<U>> fn) {
- return new AsyncProxyInternal<>(fn.apply(future), executor, finisher);
+ /// converts ThrowingConsumer<T> to ThrowingFunction<T, Void>
+ private ThrowingFunction<? super T, Void> adapt(ThrowingConsumer<? super
T> action) {
+ return t->{action.accept(t); return (Void)null; };
}
+
+ private <U> AsyncProxy<U> map(Callable<U> callable) {
+ return new AsyncProxyInternal<>(executor.submit(callable), executor);
+ }
+
}
\ No newline at end of file
diff --git
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
index dcb41fe738d..0fcb27685b0 100644
---
a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
+++
b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/wrapper/WrapperFactoryDefault.java
@@ -74,6 +74,7 @@
import org.apache.causeway.core.metamodel.context.MetaModelContext;
import org.apache.causeway.core.metamodel.object.ManagedObject;
import org.apache.causeway.core.metamodel.object.ManagedObjects;
+import org.apache.causeway.core.metamodel.object.MmEntityUtils;
import org.apache.causeway.core.metamodel.services.command.CommandDtoFactory;
import org.apache.causeway.core.runtime.wrap.WrappingObject;
import
org.apache.causeway.core.runtimeservices.CausewayModuleCoreRuntimeServices;
@@ -159,20 +160,11 @@ public <T> T wrap(
final @NonNull T domainObject,
final @NonNull SyncControl syncControl) {
- var spec =
getSpecificationLoader().specForTypeElseFail(domainObject.getClass());
- if(spec.isMixin()) {
- throw _Exceptions.illegalArgument("cannot wrap a mixin instance
directly, "
- + "use WrapperFactory.wrapMixin(...) instead");
- }
-
if (isWrapper(domainObject)) {
var wrapperObject = (WrappingObject) domainObject;
var origin = wrapperObject.__causeway_origin();
- if(origin.syncControl().isEquivalent(syncControl)) {
- return domainObject;
- }
- var underlyingDomainObject =
wrapperObject.__causeway_origin().pojo();
- return _Casts.uncheckedCast(createProxy(underlyingDomainObject,
syncControl));
+ if(origin.syncControl().isEquivalent(syncControl)) return
domainObject;
+ return _Casts.uncheckedCast(createProxy(origin.pojo(),
syncControl));
}
return createProxy(domainObject, syncControl);
}
@@ -191,34 +183,29 @@ public <T> T wrapMixin(
final @NonNull SyncControl syncControl) {
T mixin = factoryService.mixin(mixinClass, mixee);
- // no need to inject services into the mixin, factoryService does it
for us.
if (isWrapper(mixee)) {
var wrappingObject = (WrappingObject) mixee;
var origin = wrappingObject.__causeway_origin();
- var underlyingMixee = origin.pojo();
-
- getServiceInjector().injectServicesInto(underlyingMixee);
-
- if(origin.syncControl().isEquivalent(syncControl)) {
- return mixin;
- }
- return _Casts.uncheckedCast(createMixinProxy(underlyingMixee,
mixin, syncControl));
+ if(origin.syncControl().isEquivalent(syncControl)) return mixin;
+ return _Casts.uncheckedCast(createMixinProxy(origin.pojo(), mixin,
syncControl));
}
- getServiceInjector().injectServicesInto(mixee);
-
return createMixinProxy(mixee, mixin, syncControl);
}
protected <T> T createProxy(final T domainObject, final SyncControl
syncControl) {
var objAdapter =
adaptAndGuardAgainstWrappingNotSupported(domainObject);
+ guardAgainstMixin(objAdapter);
+ MmEntityUtils.requiresAttached(objAdapter);
return proxyGenerator.objectProxy(domainObject, objAdapter.objSpec(),
syncControl);
}
protected <T> T createMixinProxy(final Object mixee, final T mixin, final
SyncControl syncControl) {
var mixeeAdapter = adaptAndGuardAgainstWrappingNotSupported(mixee);
var mixinAdapter = adaptAndGuardAgainstWrappingNotSupported(mixin);
+ guardAgainstMixin(mixeeAdapter);
+ MmEntityUtils.requiresAttached(mixeeAdapter);
return proxyGenerator.mixinProxy(mixin, mixeeAdapter,
mixinAdapter.objSpec(), syncControl);
}
@@ -242,21 +229,18 @@ AsyncExecutor asyncExecutor(AsyncControl asyncControl) {
transactionServiceProvider.get(),
asyncControl.override(InteractionContext.builder().build()),
Optional.of(Propagation.REQUIRES_NEW),
+ executionFinisher(),
Optional.ofNullable(asyncControl.executorService())
.orElse(commonExecutorService));
}
- AsyncExecutionFinisher finisher() {
- return null;
- }
-
@Override
public <T> AsyncProxy<T> asyncWrap(T domainObject, AsyncControl
asyncControl) {
- var proxy = wrap(domainObject, asyncControl.syncControl());
+ var pojo = unwrap(domainObject);
+ var proxy = wrap(pojo, asyncControl.syncControl());
return new AsyncProxyInternal<>(
CompletableFuture.completedFuture(proxy),
- asyncExecutor(asyncControl),
- executionFinisher());
+ asyncExecutor(asyncControl));
}
@Override
@@ -267,8 +251,7 @@ public <T> AsyncProxy<T> asyncWrapMixin(
var proxy = wrapMixin(mixinClass, mixee, asyncControl.syncControl());
return new AsyncProxyInternal<>(
CompletableFuture.completedFuture(proxy),
- asyncExecutor(asyncControl),
- executionFinisher());
+ asyncExecutor(asyncControl));
}
// -- LISTENERS
@@ -314,6 +297,14 @@ private ManagedObject
adaptAndGuardAgainstWrappingNotSupported(
return adapter;
}
+ private void guardAgainstMixin(ManagedObject mo) {
+ if(mo.objSpec().isMixin()) {
+ throw _Exceptions.illegalArgument("cannot wrap a mixin instance
directly, "
+ + "use WrapperFactory.wrapMixin(...) instead");
+ }
+ }
+
+
// -- HELPER - SETUP
private <T extends InteractionEvent> void putDispatcher(
diff --git
a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
index fa1081002c6..7fbbf988dde 100644
---
a/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
+++
b/extensions/core/commandlog/applib/src/test/java/org/apache/causeway/extensions/commandlog/applib/integtest/BackgroundService_IntegTestAbstract.java
@@ -20,6 +20,7 @@
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import jakarta.inject.Inject;
@@ -38,6 +39,7 @@
import org.apache.causeway.applib.services.bookmark.BookmarkService;
import org.apache.causeway.applib.services.iactnlayer.InteractionService;
import org.apache.causeway.applib.services.wrapper.WrapperFactory;
+import org.apache.causeway.applib.services.wrapper.WrapperFactory.AsyncProxy;
import org.apache.causeway.applib.services.wrapper.control.AsyncControl;
import org.apache.causeway.applib.services.xactn.TransactionService;
import org.apache.causeway.core.config.environment.CausewaySystemEnvironment;
@@ -106,39 +108,49 @@ void setup_counter() {
@Test
void async_using_default_executor_service() {
+ final AtomicReference<AsyncProxy<Counter>> asyncProxyUnderTest1 = new
AtomicReference<>();
+ final AtomicReference<AsyncProxy<Counter_bumpUsingMixin>>
asyncProxyUnderTest2 = new AtomicReference<>();
+
// when
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
var control = AsyncControl.defaults();
- wrapperFactory.asyncWrap(counter, control)
+
+ asyncProxyUnderTest1.set(wrapperFactory.asyncWrap(counter,
control));
+
+ }).ifFailureFail();
+
+ // execute async and wait till done
+ {
+ asyncProxyUnderTest1.get()
.thenApplyAsync(Counter::bumpUsingDeclaredAction)
.orTimeout(5, TimeUnit.SECONDS)
.join(); // wait till done
-
- }).ifFailureFail();
+ }
// then
- transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
- var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
- assertThat(counter.getNum()).isEqualTo(1L);
- }).ifFailureFail();
-
- // when
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
assertThat(counter.getNum()).isEqualTo(1L);
var control = AsyncControl.defaults();
- // when ...
- // returns the detached counter entity, so we can immediately
check whether the action was executed
- counter =
wrapperFactory.asyncWrapMixin(Counter_bumpUsingMixin.class, counter, control)
- .thenApplyAsync(Counter_bumpUsingMixin::act)
- .join(); // wait till done
+ // store the async proxy for later use below
+
asyncProxyUnderTest2.set(wrapperFactory.asyncWrapMixin(Counter_bumpUsingMixin.class,
counter, control));
+
+ }).ifFailureFail();
+ // execute async and wait till done
+ {
+ // returns the detached counter entity, so we can immediately
check whether the action was executed
+ var counter = asyncProxyUnderTest2.get()
+ .thenApplyAsync(Counter_bumpUsingMixin::act)
+ // let's wait max 5 sec to allow executor to complete
before continuing
+ .orTimeout(5, TimeUnit.SECONDS)
+ .join(); // wait till done
assertThat(counter.getNum()).isEqualTo(2L);
+ }
- }).ifFailureFail();
// then
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
@@ -151,6 +163,8 @@ void async_using_default_executor_service() {
@Test
void using_background_service() {
+ final AtomicReference<AsyncProxy<Counter>> asyncProxyUnderTest = new
AtomicReference<>();
+
// given
removeAllCommandLogEntriesAndCounters();
@@ -160,13 +174,19 @@ void using_background_service() {
assertThat(counter.getNum()).isNull();
// when
- backgroundService.execute(counter)
- .thenAcceptAsync(Counter::bumpUsingDeclaredAction)
- .orTimeout(1, TimeUnit.SECONDS)
- .join(); // wait for completion
+ asyncProxyUnderTest.set(backgroundService.execute(counter));
}).ifFailureFail();
+ // execute async and wait till done
+ {
+ asyncProxyUnderTest.get()
+ .thenAcceptAsync(Counter::bumpUsingDeclaredAction)
+ // let's wait max 5 sec to allow executor to complete before
continuing
+ .orTimeout(5, TimeUnit.SECONDS)
+ .join(); // wait till done
+ }
+
// then no change to the counter
transactionService.runTransactional(Propagation.REQUIRES_NEW, () -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
diff --git
a/regressiontests/core-wrapperfactory/src/test/java/org/apache/causeway/regressiontests/core/wrapperfactory/integtests/WrapperFactory_async_IntegTest.java
b/regressiontests/core-wrapperfactory/src/test/java/org/apache/causeway/regressiontests/core/wrapperfactory/integtests/WrapperFactory_async_IntegTest.java
index 192de03e3b2..76df8059a4a 100644
---
a/regressiontests/core-wrapperfactory/src/test/java/org/apache/causeway/regressiontests/core/wrapperfactory/integtests/WrapperFactory_async_IntegTest.java
+++
b/regressiontests/core-wrapperfactory/src/test/java/org/apache/causeway/regressiontests/core/wrapperfactory/integtests/WrapperFactory_async_IntegTest.java
@@ -23,11 +23,11 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import jakarta.inject.Inject;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -37,6 +37,7 @@
import org.apache.causeway.applib.services.bookmark.Bookmark;
import org.apache.causeway.applib.services.bookmark.BookmarkService;
import org.apache.causeway.applib.services.wrapper.WrapperFactory;
+import org.apache.causeway.applib.services.wrapper.WrapperFactory.AsyncProxy;
import org.apache.causeway.applib.services.wrapper.control.AsyncControl;
import org.apache.causeway.applib.services.xactn.TransactionService;
import org.apache.causeway.testdomain.wrapperfactory.Counter;
@@ -55,7 +56,6 @@ class WrapperFactory_async_IntegTest extends
CoreWrapperFactory_IntegTestAbstrac
Bookmark bookmark;
- @BeforeEach
void setup_counter() {
runWithNewTransaction(() -> {
@@ -73,11 +73,17 @@ void setup_counter() {
assertThat(counter.getNum()).isNull();
}
+
@SneakyThrows
@ParameterizedTest(name = "executorService[{index}]: {0}")
@MethodSource("executorServices")
void async_using_default_executor_service(final String displayName, final
ExecutorService executorService) {
+ setup_counter();
+
+ final AtomicReference<AsyncProxy<Counter>> asyncProxyUnderTest1 = new
AtomicReference<>();
+ final AtomicReference<AsyncProxy<Counter_bumpUsingMixin>>
asyncProxyUnderTest2 = new AtomicReference<>();
+
// when - executing regular action
runWithNewTransaction(() -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();
@@ -85,11 +91,17 @@ void async_using_default_executor_service(final String
displayName, final Execut
var asyncControl = AsyncControl.defaults()
.with(executorService);
- wrapperFactory.asyncWrap(counter, asyncControl)
+ // store the async proxy for later use below
+ asyncProxyUnderTest1.set(wrapperFactory.asyncWrap(counter,
asyncControl));
+ });
+
+ // execute async and wait till done
+ {
+ asyncProxyUnderTest1.get()
.thenApplyAsync(Counter::increment)
.orTimeout(5_000, TimeUnit.MILLISECONDS)
.join(); // let's wait max 5 sec to allow executor to complete
before continuing
- });
+ }
// then
runWithNewTransaction(() -> {
@@ -105,17 +117,21 @@ void async_using_default_executor_service(final String
displayName, final Execut
var asyncControl = AsyncControl.defaults()
.with(executorService);
- // when ...
- // returns the detached counter entity, so we can immediately
check whether the action was executed
- counter =
wrapperFactory.asyncWrapMixin(Counter_bumpUsingMixin.class, counter,
asyncControl)
- .thenApplyAsync(Counter_bumpUsingMixin::act)
- // let's wait max 5 sec to allow executor to complete before
continuing
- .orTimeout(5_000, TimeUnit.MILLISECONDS)
- .join(); // wait till done
-
- assertThat(counter.getNum()).isEqualTo(2L); // verify execution
succeeded
+ // store the async proxy for later use below
+
asyncProxyUnderTest2.set(wrapperFactory.asyncWrapMixin(Counter_bumpUsingMixin.class,
counter, asyncControl));
});
+ // execute async and wait till done
+ {
+ // returns the detached counter entity, so we can immediately
check whether the action was executed
+ var counter = asyncProxyUnderTest2.get()
+ .thenApplyAsync(Counter_bumpUsingMixin::act)
+ // let's wait max 5 sec to allow executor to complete
before continuing
+ .orTimeout(5, TimeUnit.SECONDS)
+ .join(); // wait till done
+ assertThat(counter.getNum()).isEqualTo(2L);
+ }
+
// then
runWithNewTransaction(() -> {
var counter = bookmarkService.lookup(bookmark,
Counter.class).orElseThrow();