You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2020/03/16 17:15:03 UTC

[isis] 30/41: ISIS-2278: fleshes out AsyncControl

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch ISIS-2278
in repository https://gitbox.apache.org/repos/asf/isis.git

commit f55e63a7b70d3ea7b16c7a36fde5077308aaa61a
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Sat Mar 14 14:51:20 2020 +0000

    ISIS-2278: fleshes out AsyncControl
---
 .../modules/applib-svc/pages/WrapperFactory.adoc   |   2 +-
 .../org/apache/isis/applib/IsisModuleApplib.java   |   4 +
 .../isis/applib/services/wrapper/AsyncWrap.java    | 119 -------------------
 .../applib/services/wrapper/WrapperFactory.java    | 132 ++-------------------
 .../applib/services/wrapper/WrappingObject.java    |   5 +-
 .../services/wrapper/control/AsyncControl.java     | 122 +++++++++++++++++++
 .../wrapper/control/AsyncControlService.java       |  55 +++++++++
 .../services/wrapper/control/ExecutionMode.java    |  37 ++++++
 .../services/wrapper/control/ExecutionModes.java   |  53 +++++++++
 .../wrapper/control/RuleCheckingPolicy.java        |   8 ++
 .../IsisModuleCoreRuntimeServices.java             |   8 +-
 .../wrapper/WrapperFactoryDefault.java             |  41 +++----
 .../DelegatingInvocationHandlerDefault.java        |   2 +-
 .../handlers/DomainObjectInvocationHandler.java    |   2 +-
 .../wrapper/handlers/ProxyContextHandler.java      |   2 +-
 .../wrapper/WrapperFactoryDefaultTest.java         |  23 ++--
 .../demoapp/dom/events/DemoEventSubscriber.java    |   3 +-
 .../testdomain/auditing/AuditerServiceTest.java    |  13 +-
 .../commandexecution/WrapperAsyncTest.java         |  16 +--
 .../publishing/PublisherServiceTest.java           |  14 ++-
 .../applib/fixturescripts/FixtureScript.java       |   3 +-
 21 files changed, 357 insertions(+), 307 deletions(-)

diff --git a/api/applib/src/main/adoc/modules/applib-svc/pages/WrapperFactory.adoc b/api/applib/src/main/adoc/modules/applib-svc/pages/WrapperFactory.adoc
index fdd6f25..9cb52908 100644
--- a/api/applib/src/main/adoc/modules/applib-svc/pages/WrapperFactory.adoc
+++ b/api/applib/src/main/adoc/modules/applib-svc/pages/WrapperFactory.adoc
@@ -3,7 +3,7 @@
 :Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at. http://www.apache.org/licenses/LICENSE-2.0 . Unless required by applicable law or ag [...]
 :page-partial:
 
-WARNING: Document `WrappingObject`, document `AsyncWrap`
+WARNING: Document `WrappingObject`, document `AsyncControl`
 
 
 The `WrapperFactory` provides the ability to enforce business rules for programmatic interactions between domain objects.
diff --git a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
index 5e0c58d..819b1ea 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/IsisModuleApplib.java
@@ -44,6 +44,7 @@ import org.apache.isis.applib.services.metamodel.MetaModelServiceMenu;
 import org.apache.isis.applib.services.publish.PublisherServiceLogging;
 import org.apache.isis.applib.services.scratchpad.Scratchpad;
 import org.apache.isis.applib.services.session.SessionLoggingServiceLogging;
+import org.apache.isis.applib.services.wrapper.control.AsyncControlService;
 import org.apache.isis.schema.IsisModuleSchema;
 
 @Configuration
@@ -79,6 +80,9 @@ import org.apache.isis.schema.IsisModuleSchema;
         PublisherServiceLogging.class,
         SessionLoggingServiceLogging.class,
 
+        // @Component's
+        AsyncControlService.class,
+
 })
 public class IsisModuleApplib {
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/AsyncWrap.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/AsyncWrap.java
deleted file mode 100644
index 585a8aa..0000000
--- a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/AsyncWrap.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-package org.apache.isis.applib.services.wrapper;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.function.Consumer;
-
-import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
-import org.apache.isis.core.commons.collections.ImmutableEnumSet;
-import org.apache.isis.applib.services.MethodReferences.Call1;
-import org.apache.isis.applib.services.MethodReferences.Call2;
-import org.apache.isis.applib.services.MethodReferences.Call3;
-import org.apache.isis.applib.services.MethodReferences.Call4;
-import org.apache.isis.applib.services.MethodReferences.Call5;
-import org.apache.isis.applib.services.MethodReferences.Run1;
-import org.apache.isis.applib.services.MethodReferences.Run2;
-import org.apache.isis.applib.services.MethodReferences.Run3;
-import org.apache.isis.applib.services.MethodReferences.Run4;
-import org.apache.isis.applib.services.MethodReferences.Run5;
-
-/**
- * Binds to a domain object, to prepare for type-safe asynchronous action invocation.
- * <p> 
- * Get an instance by using {@link WrapperFactory#async(Object)}.
- * 
- * @apiNote INCUBATING
- * @since 2.0
- */
-public interface AsyncWrap<T> {
-
-    // -- CONFIGURATION
-    
-    ExecutorService getExecutorService();
-    AsyncWrap<T> withExecutorService(ExecutorService executor);
-    
-    Consumer<Exception> getExceptionHandler();
-    AsyncWrap<T> withExceptionHandler(Consumer<Exception> handler);
-
-    ImmutableEnumSet<ExecutionMode> getExecutionModes();
-    
-    // -- INVOCATION (WITH RETURN VALUE)
-    
-    <R> Future<R> call(Call1<? extends R, ? super T> action);
-    
-    <R, A1> Future<R> call(Call2<? extends R, ? super T, A1> action, 
-            A1 arg1);
-    
-    <R, A1, A2> Future<R> call(Call3<? extends R, ? super T, A1, A2> action, 
-            A1 arg1, A2 arg2);
-    
-    <R, A1, A2, A3> Future<R> call(Call4<? extends R, ? super T, A1, A2, A3> action, 
-            A1 arg1, A2 arg2, A3 arg3);
-    
-    <R, A1, A2, A3, A4> Future<R> call(Call5<? extends R, ? super T, A1, A2, A3, A4> action, 
-            A1 arg1, A2 arg2, A3 arg3, A4 arg4);
-    
-    // -- INVOCATION (WITHOUT RETURN VALUE)
-    
-    default Future<Void> run(Run1<? super T> action) {
-        return call(obj->{
-            action.run(obj); 
-            return null;
-        });
-    }
-    
-    default <A1> Future<Void> run(Run2<? super T, A1> action, 
-            A1 arg1) {
-        
-        return call(obj->{
-            action.run(obj, arg1); 
-            return null;
-        });
-    }
-    
-    default <A1, A2> Future<Void> run(Run3<? super T, A1, A2> action, 
-            A1 arg1, A2 arg2) {
-        
-        return call(obj->{
-            action.run(obj, arg1, arg2); 
-            return null;
-        });
-    }
-    
-    default <A1, A2, A3> Future<Void> run(Run4<? super T, A1, A2, A3> action, 
-            A1 arg1, A2 arg2, A3 arg3) {
-        
-        return call(obj->{
-            action.run(obj, arg1, arg2, arg3); 
-            return null;
-        });
-    }
-    
-    default <A1, A2, A3, A4> Future<Void> run(Run5<? super T, A1, A2, A3, A4> action, 
-            A1 arg1, A2 arg2, A3 arg3, A4 arg4) {
-        
-        return call(obj->{
-            action.run(obj, arg1, arg2, arg3, arg4); 
-            return null;
-        });
-    }
-    
-}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
index 2a932a8..15a1179 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrapperFactory.java
@@ -24,17 +24,14 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ForkJoinPool;
 
 import org.apache.isis.applib.services.factory.FactoryService;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
+import org.apache.isis.applib.services.wrapper.control.RuleCheckingPolicy;
 import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 import org.apache.isis.applib.services.wrapper.listeners.InteractionListener;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 
-import static org.apache.isis.core.commons.collections.ImmutableEnumSet.*;
-
-import lombok.AccessLevel;
-import lombok.RequiredArgsConstructor;
-import lombok.val;
-import lombok.experimental.UtilityClass;
-
 /**
  * Provides the ability to &quot;wrap&quot; of a domain object such that it can
  * be interacted with while enforcing the hide/disable/validate rules implied by
@@ -81,89 +78,6 @@ public interface WrapperFactory {
     // end::refguide-listeners[]
     // end::refguide-async[]
     // end::refguide[]
-    /**
-     * Whether interactions with the wrapper are actually passed onto the
-     * underlying domain object.
-     *
-     * @see WrapperFactory#wrap(Object, ImmutableEnumSet)
-     */
-    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
-    // tag::refguide-1[]
-    enum ExecutionMode {
-        // end::refguide-1[]
-        /**
-         * Skip all business rules.
-         */
-        // tag::refguide-1[]
-        SKIP_RULE_VALIDATION,
-        // end::refguide-1[]
-        /**
-         * Skip execution.
-         */
-        // tag::refguide-1[]
-        SKIP_EXECUTION,
-        // end::refguide-1[]
-        /**
-         * Don't fail fast, swallow any exception during validation or execution.
-         */
-        // tag::refguide-1[]
-        SWALLOW_EXCEPTIONS,
-    }
-
-    // end::refguide-1[]
-
-    // tag::refguide-2[]
-    @UtilityClass
-    class ExecutionModes {
-        // end::refguide-2[]
-        /**
-         * Validate all business rules and then execute. May throw exceptions in order to fail fast.
-         */
-        // tag::refguide-2[]
-        public static final ImmutableEnumSet<ExecutionMode> EXECUTE =
-                                noneOf(ExecutionMode.class);
-        // end::refguide-2[]
-        /**
-         * Skip all business rules and then execute, does throw an exception if execution fails.
-         */
-        // tag::refguide-2[]
-        public static final ImmutableEnumSet<ExecutionMode> SKIP_RULES =
-                                of(ExecutionMode.SKIP_RULE_VALIDATION);
-        // end::refguide-2[]
-        /**
-         * Validate all business rules but do not execute, throw an exception if validation
-         * fails.
-         */
-        // tag::refguide-2[]
-        public static final ImmutableEnumSet<ExecutionMode> NO_EXECUTE =
-                                of(ExecutionMode.SKIP_EXECUTION);
-        // end::refguide-2[]
-        /**
-         * Validate all business rules and then execute, but don't throw an exception if validation
-         * or execution fails.
-         */
-        // tag::refguide-2[]
-        public static final ImmutableEnumSet<ExecutionMode> TRY =
-                                of(ExecutionMode.SWALLOW_EXCEPTIONS);
-        // end::refguide-2[]
-        /**
-         * Skips all steps.
-         * @since 2.0
-         */
-        // tag::refguide-2[]
-        public static final ImmutableEnumSet<ExecutionMode> NOOP =
-                                of(ExecutionMode.SKIP_RULE_VALIDATION,
-                                   ExecutionMode.SKIP_EXECUTION);
-    }
-    // end::refguide-2[]
-
-
-    // tag::refguide-3[]
-    enum RuleCheckingPolicy {
-        SYNC,   // <.>
-        ASYNC   // <.>
-    }
-    // end::refguide-3[]
 
     /**
      * Same as {@link #wrap(Object)}, except the actual execution occurs only if
@@ -249,6 +163,7 @@ public interface WrapperFactory {
     // -- ASYNC WRAPPING
     //
 
+
     /**
      * Returns a proxy object for the provided {@code domainObject},
      * through which can execute the action asynchronously.
@@ -261,39 +176,8 @@ public interface WrapperFactory {
      * @since 2.0
      */
     // tag::refguide-async[]
-    <T> T async(T domainObject,                          // <.>
-                ImmutableEnumSet<ExecutionMode> mode,
-                RuleCheckingPolicy ruleCheckingPolicy,
-                ExecutorService executorService);
-
-    // end::refguide-async[]
-    // tag::refguide-async[]
-    default <T> T async(T domainObject,                          // <.>
-                ImmutableEnumSet<ExecutionMode> modes) {
-        // end::refguide-async[]
-
-        return async(domainObject, modes, RuleCheckingPolicy.ASYNC, ForkJoinPool.commonPool());
-
-        // tag::refguide-async[]
-        // ...
-    }
-
-    // end::refguide-async[]
-    /**
-     * Shortcut for {@link #async(Object, ImmutableEnumSet)} using execution mode
-     * {@link ExecutionModes#EXECUTE}.
-     *
-     * @since 2.0
-     */
-    // tag::refguide-async[]
-    default <T> T async(T domainObject) {                // <.>
-        // end::refguide-async[]
-
-        return async(domainObject, ExecutionModes.EXECUTE);
-
-        // tag::refguide-async[]
-        // ...
-    }
+    <T,R> T async(T domainObject,                          // <.>
+                  AsyncControl<R> asyncControl);
 
     // end::refguide-async[]
     /**
@@ -316,7 +200,7 @@ public interface WrapperFactory {
 
     // end::refguide-async[]
     /**
-     * Returns a {@link AsyncWrap} bound to the provided {@code mixinClass},
+     * Returns a {@link AsyncControl} bound to the provided {@code mixinClass},
      * to prepare for type-safe asynchronous action execution.
      *
      * @param <T>
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrappingObject.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrappingObject.java
index 9c936cf..efcec9b 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrappingObject.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/WrappingObject.java
@@ -21,6 +21,7 @@ package org.apache.isis.applib.services.wrapper;
 
 import java.util.EnumSet;
 
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 
 /**
@@ -65,7 +66,7 @@ public interface WrappingObject {
 
     // end::refguide[]
     /**
-     * The {@link EnumSet<WrapperFactory.ExecutionMode> execution mode}
+     * The {@link EnumSet< ExecutionMode > execution mode}
      * with which this wrapper was
      * {@link WrapperFactory#wrap(Object, ImmutableEnumSet) created}.
      *
@@ -75,7 +76,7 @@ public interface WrappingObject {
      * </p>
      */
     // tag::refguide[]
-    ImmutableEnumSet<WrapperFactory.ExecutionMode> __isis_executionMode();
+    ImmutableEnumSet<ExecutionMode> __isis_executionMode();
 
 }
 // end::refguide[]
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControl.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControl.java
new file mode 100644
index 0000000..4f5d893
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControl.java
@@ -0,0 +1,122 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.wrapper.control;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+import org.apache.isis.schema.common.v2.OidDto;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ *
+ * @param <R> - return value.
+ */
+@Log4j2
+public class AsyncControl<R> {
+
+    public static <X> AsyncControl<X> toReturn(final Class<X> clazz) {
+        return new AsyncControl<>();
+    }
+
+    public static AsyncControl<Void> ignoreReturn() {
+        return new AsyncControl<>();
+    }
+
+    public static AsyncControl<Void> voidReturn() {
+        return new AsyncControl<>();
+    }
+
+    private AsyncControl() {
+    }
+
+    /**
+     * Set by framework.
+     */
+    @Setter(AccessLevel.PACKAGE)
+    private Method method;
+
+    /**
+     * Set by framework.
+     */
+    @Setter(AccessLevel.PACKAGE)
+    private OidDto oidDto;
+
+
+    @Getter @NonNull
+    private ImmutableEnumSet<ExecutionMode> executionModes = ExecutionModes.EXECUTE;
+    public AsyncControl<R> with(ImmutableEnumSet<ExecutionMode> executionModes) {
+        this.executionModes = executionModes;
+        return this;
+    }
+
+    @Getter @NonNull
+    private ExecutorService executorService = ForkJoinPool.commonPool();
+    public AsyncControl<R> with(ExecutorService executorService) {
+        this.executorService = executorService;
+        return this;
+    }
+
+    @Getter @NonNull
+    private RuleCheckingPolicy ruleCheckingPolicy = RuleCheckingPolicy.ASYNC;
+    public AsyncControl<R> with(RuleCheckingPolicy ruleCheckingPolicy) {
+        this.ruleCheckingPolicy = ruleCheckingPolicy;
+        return this;
+    }
+
+    @Getter @NonNull
+    private Consumer<Exception> exceptionHandler = (exception) -> {
+        log.error(logMessage(), exception);
+    };
+    public AsyncControl<R> with(Consumer<Exception> exceptionHandler) {
+        this.exceptionHandler = exceptionHandler;
+        return this;
+    }
+
+    private String logMessage() {
+        StringBuilder buf = new StringBuilder("Failed to execute ");
+        if(method != null) {
+            buf.append(" ").append(method.getName()).append(" ");
+            if(oidDto != null) {
+                buf.append(" on '")
+                    .append(oidDto.getType())
+                    .append(":")
+                    .append(oidDto.getId())
+                    .append("'");
+            }
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Set by framework
+     */
+    @Getter @Setter(AccessLevel.PACKAGE)
+    private Future<R> future;
+
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControlService.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControlService.java
new file mode 100644
index 0000000..44621c5
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControlService.java
@@ -0,0 +1,55 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.wrapper.control;
+
+import java.lang.reflect.Method;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+import org.springframework.stereotype.Component;
+
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+import org.apache.isis.schema.common.v2.OidDto;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+/**
+ * For framework internal use.
+ */
+@Component
+public class AsyncControlService<R> {
+
+    public <R> AsyncControl<R> init(AsyncControl<R> asyncControl, Method method, OidDto oidDto) {
+        asyncControl.setMethod(method);
+        asyncControl.setOidDto(oidDto);
+        return asyncControl;
+    }
+
+    public <R> AsyncControl<R> update(AsyncControl<R> asyncControl, Future<R> future) {
+        asyncControl.setFuture(future);
+        return asyncControl;
+    }
+
+}
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionMode.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionMode.java
new file mode 100644
index 0000000..3226e41
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionMode.java
@@ -0,0 +1,37 @@
+package org.apache.isis.applib.services.wrapper.control;
+
+import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Whether interactions with the wrapper are actually passed onto the
+ * underlying domain object.
+ *
+ * @see WrapperFactory#wrap(Object, ImmutableEnumSet)
+ */
+@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+// tag::refguide[]
+public enum ExecutionMode {
+    // end::refguide[]
+    /**
+     * Skip all business rules.
+     */
+    // tag::refguide[]
+    SKIP_RULE_VALIDATION,
+    // end::refguide[]
+    /**
+     * Skip execution.
+     */
+    // tag::refguide[]
+    SKIP_EXECUTION,
+    // end::refguide[]
+    /**
+     * Don't fail fast, swallow any exception during validation or execution.
+     */
+    // tag::refguide[]
+    SWALLOW_EXCEPTIONS,
+}
+// end::refguide[]
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionModes.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionModes.java
new file mode 100644
index 0000000..d910eed
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionModes.java
@@ -0,0 +1,53 @@
+package org.apache.isis.applib.services.wrapper.control;
+
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+
+import static org.apache.isis.core.commons.collections.ImmutableEnumSet.noneOf;
+import static org.apache.isis.core.commons.collections.ImmutableEnumSet.of;
+
+import lombok.experimental.UtilityClass;
+
+// tag::refguide[]
+@UtilityClass
+public class ExecutionModes {
+    // end::refguide[]
+    /**
+     * Validate all business rules and then execute. May throw exceptions in order to fail fast.
+     */
+    // tag::refguide[]
+    public static final ImmutableEnumSet<ExecutionMode> EXECUTE =
+                            noneOf(ExecutionMode.class);
+    // end::refguide[]
+    /**
+     * Skip all business rules and then execute, does throw an exception if execution fails.
+     */
+    // tag::refguide[]
+    public static final ImmutableEnumSet<ExecutionMode> SKIP_RULES =
+                            of(ExecutionMode.SKIP_RULE_VALIDATION);
+    // end::refguide[]
+    /**
+     * Validate all business rules but do not execute, throw an exception if validation
+     * fails.
+     */
+    // tag::refguide[]
+    public static final ImmutableEnumSet<ExecutionMode> NO_EXECUTE =
+                            of(ExecutionMode.SKIP_EXECUTION);
+    // end::refguide[]
+    /**
+     * Validate all business rules and then execute, but don't throw an exception if validation
+     * or execution fails.
+     */
+    // tag::refguide[]
+    public static final ImmutableEnumSet<ExecutionMode> TRY =
+                            of(ExecutionMode.SWALLOW_EXCEPTIONS);
+    // end::refguide[]
+    /**
+     * Skips all steps.
+     * @since 2.0
+     */
+    // tag::refguide[]
+    public static final ImmutableEnumSet<ExecutionMode> NOOP =
+                            of(ExecutionMode.SKIP_RULE_VALIDATION,
+                               ExecutionMode.SKIP_EXECUTION);
+}
+// end::refguide[]
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/RuleCheckingPolicy.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/RuleCheckingPolicy.java
new file mode 100644
index 0000000..bc3dc06
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/RuleCheckingPolicy.java
@@ -0,0 +1,8 @@
+package org.apache.isis.applib.services.wrapper.control;
+
+// tag::refguide[]
+public enum RuleCheckingPolicy {
+    IMMEDIATE,   // <.>
+    ASYNC   // <.>
+}
+// end::refguide[]
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
index 85bea18..03a6ca0 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/IsisModuleCoreRuntimeServices.java
@@ -26,6 +26,7 @@ import org.springframework.context.annotation.Import;
 import org.springframework.core.OrderComparator;
 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
 
+import org.apache.isis.applib.services.wrapper.control.AsyncControlService;
 import org.apache.isis.core.codegen.bytebuddy.IsisModuleCoreCodegenByteBuddy;
 import org.apache.isis.core.runtime.IsisModuleCoreRuntime;
 import org.apache.isis.core.runtimeservices.bookmarks.BookmarkServiceDefault;
@@ -107,12 +108,7 @@ import org.apache.isis.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefaul
         TranslationServicePoMenu.class,
 
 })
-public class IsisModuleCoreRuntimeServices /*implements ApplicationContextAware*/ {
-
-//    @Override
-//    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-//        // just make sure we wait for the context; (not sure if need anymore)
-//    }
+public class IsisModuleCoreRuntimeServices {
 
     @Bean @Singleton // also used by _Spring utility
     public OrderComparator orderComparator() {
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefault.java
index 7267b25..074686e 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefault.java
@@ -18,7 +18,6 @@
  */
 package org.apache.isis.core.runtimeservices.wrapper;
 
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -26,7 +25,6 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.function.BiConsumer;
@@ -47,8 +45,13 @@ import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.command.CommandExecutorService;
 import org.apache.isis.applib.services.factory.FactoryService;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
+import org.apache.isis.applib.services.wrapper.control.AsyncControlService;
+import org.apache.isis.applib.services.wrapper.control.RuleCheckingPolicy;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.applib.services.wrapper.WrappingObject;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
 import org.apache.isis.applib.services.wrapper.events.ActionArgumentEvent;
 import org.apache.isis.applib.services.wrapper.events.ActionInvocationEvent;
 import org.apache.isis.applib.services.wrapper.events.ActionUsabilityEvent;
@@ -73,27 +76,21 @@ import org.apache.isis.core.commons.internal.base._Casts;
 import org.apache.isis.core.commons.internal.exceptions._Exceptions;
 import org.apache.isis.core.commons.internal.plugins.codegen.ProxyFactory;
 import org.apache.isis.core.commons.internal.plugins.codegen.ProxyFactoryService;
-import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
-import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.specimpl.dflt.ObjectSpecificationDefault;
-import org.apache.isis.core.runtime.session.IsisSession;
 import org.apache.isis.core.runtime.session.IsisSessionFactory;
 import org.apache.isis.core.runtime.session.IsisSessionTracker;
-import org.apache.isis.core.runtime.session.init.InitialisationSession;
 import org.apache.isis.core.runtimeservices.wrapper.dispatchers.InteractionEventDispatcher;
 import org.apache.isis.core.runtimeservices.wrapper.dispatchers.InteractionEventDispatcherTypeSafe;
 import org.apache.isis.core.runtimeservices.wrapper.handlers.ProxyContextHandler;
 import org.apache.isis.core.runtimeservices.wrapper.proxy.ProxyCreator;
-import org.apache.isis.core.security.authentication.AuthenticationSession;
-import org.apache.isis.core.security.authentication.AuthenticationSessionTracker;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
 import lombok.val;
@@ -218,12 +215,17 @@ public class WrapperFactoryDefault implements WrapperFactory {
 
     // -- ASYNC WRAPPING
 
+    @Inject
+    AsyncControlService asyncControlService;
+
     @Override
-    public <T> T async(
+    public <T,R> T async(
             final T domainObject,
-            final ImmutableEnumSet<ExecutionMode> executionModes,
-            final RuleCheckingPolicy ruleCheckingPolicy,
-            final ExecutorService executorService) {
+            final AsyncControl<R> asyncControl) {
+
+        final ImmutableEnumSet<ExecutionMode> executionModes = asyncControl.getExecutionModes();
+        final RuleCheckingPolicy ruleCheckingPolicy = asyncControl.getRuleCheckingPolicy();
+        final ExecutorService executorService = asyncControl.getExecutorService();
 
         Class<T> domainClass = (Class<T>) domainObject.getClass();
         ProxyFactory<T> factory = proxyFactoryService.factory(domainClass, WrappingObject.class);
@@ -273,20 +275,14 @@ public class WrapperFactoryDefault implements WrapperFactory {
             val authSession = isisSession.getAuthenticationSession();
 
             final ManagedObject domainObjectAdapter = isisSession.getObjectManager().adapt(domainObject);
-            final String domainObjectClassName = CommandUtil.targetClassNameFor(domainObjectAdapter);
-
-            final String targetActionName = CommandUtil.targetMemberNameFor(action);
-
             final List<ManagedObject> argAdapters = Arrays.asList(adaptersFor(args));
-            final String targetArgs = CommandUtil.argDescriptionFor(action, argAdapters);
-
-            final Command command = commandContextProvider.get().getCommand();
 
             final List<ManagedObject> targetList = Collections.singletonList(domainObjectAdapter);
             final CommandDto dto =
                     commandDtoServiceInternal.asCommandDto(targetList, action, argAdapters);
+            asyncControlService.init(asyncControl, method, dto.getTargets().getOid().get(0));
 
-            executorService.submit(() -> {
+            Future<?> submit = executorService.submit(() -> {
 
                 isisSessionFactory.runAuthenticated(authSession, () -> {
 
@@ -299,6 +295,7 @@ public class WrapperFactoryDefault implements WrapperFactory {
 
                 });
             });
+            asyncControlService.update(asyncControl, submit);
             return null;
 
 
@@ -325,7 +322,7 @@ public class WrapperFactoryDefault implements WrapperFactory {
             ImmutableEnumSet<ExecutionMode> executionModes,
             RuleCheckingPolicy ruleCheckingPolicy) {
         return executionModes.contains(ExecutionMode.SKIP_RULE_VALIDATION) ||
-                ruleCheckingPolicy != RuleCheckingPolicy.SYNC
+                ruleCheckingPolicy != RuleCheckingPolicy.IMMEDIATE
                 ? ExecutionModes.NOOP
                 : ExecutionModes.NO_EXECUTE;
     }
@@ -334,7 +331,7 @@ public class WrapperFactoryDefault implements WrapperFactory {
             ImmutableEnumSet<ExecutionMode> executionModes,
             RuleCheckingPolicy ruleCheckingPolicy) {
         return executionModes.contains(ExecutionMode.SKIP_RULE_VALIDATION) ||
-                RuleCheckingPolicy.SYNC == ruleCheckingPolicy
+                RuleCheckingPolicy.IMMEDIATE == ruleCheckingPolicy
                 ? join(executionModes, ExecutionMode.SKIP_RULE_VALIDATION)
                 : executionModes;
     }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DelegatingInvocationHandlerDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DelegatingInvocationHandlerDefault.java
index afd86c3..272c10c 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DelegatingInvocationHandlerDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DelegatingInvocationHandlerDefault.java
@@ -24,7 +24,7 @@ import java.lang.reflect.Method;
 
 import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.commons.internal._Constants;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
index 70aad5d..9eb21b0 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/DomainObjectInvocationHandler.java
@@ -33,7 +33,7 @@ import org.apache.isis.applib.services.wrapper.DisabledException;
 import org.apache.isis.applib.services.wrapper.HiddenException;
 import org.apache.isis.applib.services.wrapper.InteractionException;
 import org.apache.isis.applib.services.wrapper.InvalidException;
-import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.applib.services.wrapper.WrappingObject;
 import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
 import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/ProxyContextHandler.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/ProxyContextHandler.java
index bbfe32c..fb24725 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/ProxyContextHandler.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/ProxyContextHandler.java
@@ -22,7 +22,7 @@ package org.apache.isis.core.runtimeservices.wrapper.handlers;
 import java.util.Collection;
 import java.util.Map;
 
-import org.apache.isis.applib.services.wrapper.WrapperFactory.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
diff --git a/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java b/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
index 9434440..bd5dd15 100644
--- a/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
+++ b/core/runtimeservices/src/test/java/org/apache/isis/core/runtimeservices/wrapper/WrapperFactoryDefaultTest.java
@@ -28,8 +28,9 @@ import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
-import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.applib.services.wrapper.WrappingObject;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.commons.internal.plugins.codegen.ProxyFactoryService;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
@@ -45,7 +46,7 @@ public class WrapperFactoryDefaultTest {
     private static class WrappedDomainObject extends DomainObject implements WrappingObject {
 
         private final DomainObject wrappedObject;
-        private final ImmutableEnumSet<WrapperFactory.ExecutionMode> mode;
+        private final ImmutableEnumSet<ExecutionMode> mode;
 
         @Override
         public void __isis_save() {
@@ -57,7 +58,7 @@ public class WrapperFactoryDefaultTest {
         }
 
         @Override
-        public ImmutableEnumSet<WrapperFactory.ExecutionMode> __isis_executionMode() {
+        public ImmutableEnumSet<ExecutionMode> __isis_executionMode() {
             return mode;
         }
     }
@@ -69,7 +70,7 @@ public class WrapperFactoryDefaultTest {
     private WrapperFactoryDefault wrapperFactory;
 
     private DomainObject createProxyCalledWithDomainObject;
-    private ImmutableEnumSet<WrapperFactory.ExecutionMode> createProxyCalledWithMode;
+    private ImmutableEnumSet<ExecutionMode> createProxyCalledWithMode;
 
     @Before
     public void setUp() throws Exception {
@@ -82,7 +83,7 @@ public class WrapperFactoryDefaultTest {
             }
             
             @Override
-            protected <T> T createProxy(T domainObject, ImmutableEnumSet<WrapperFactory.ExecutionMode> modes) {
+            protected <T> T createProxy(T domainObject, ImmutableEnumSet<ExecutionMode> modes) {
                 WrapperFactoryDefaultTest.this.createProxyCalledWithMode = modes;
                 WrapperFactoryDefaultTest.this.createProxyCalledWithDomainObject = (DomainObject) domainObject;
                 return domainObject;
@@ -98,7 +99,7 @@ public class WrapperFactoryDefaultTest {
         wrapperFactory.wrap(domainObject);
 
         assertThat(createProxyCalledWithDomainObject, is(domainObject));
-        assertThat(createProxyCalledWithMode, is(WrapperFactory.ExecutionModes.EXECUTE));
+        assertThat(createProxyCalledWithMode, is(ExecutionModes.EXECUTE));
     }
 
 
@@ -106,10 +107,10 @@ public class WrapperFactoryDefaultTest {
     public void wrap_ofWrapped_sameMode_returnsUnchanged() throws Exception {
         // given
         final DomainObject wrappedObject = new DomainObject();
-        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, WrapperFactory.ExecutionModes.EXECUTE);
+        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, ExecutionModes.EXECUTE);
 
         // when
-        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, WrapperFactory.ExecutionModes.EXECUTE);
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, ExecutionModes.EXECUTE);
 
         // then
         assertThat(wrappingObject, is(domainObject));
@@ -120,15 +121,15 @@ public class WrapperFactoryDefaultTest {
     public void wrap_ofWrapped_differentMode_delegates_to_createProxy() throws Exception {
         // given
         final DomainObject wrappedObject = new DomainObject();
-        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, WrapperFactory.ExecutionModes.EXECUTE);
+        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, ExecutionModes.EXECUTE);
 
         // when
-        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, WrapperFactory.ExecutionModes.SKIP_RULES);
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, ExecutionModes.SKIP_RULES);
 
         // then
         assertThat(wrappingObject, is(not(domainObject)));
         assertThat(createProxyCalledWithDomainObject, is(wrappedObject));
-        assertThat(createProxyCalledWithMode, is(WrapperFactory.ExecutionModes.SKIP_RULES));
+        assertThat(createProxyCalledWithMode, is(ExecutionModes.SKIP_RULES));
     }
 
 }
\ No newline at end of file
diff --git a/examples/demo/src/main/java/demoapp/dom/events/DemoEventSubscriber.java b/examples/demo/src/main/java/demoapp/dom/events/DemoEventSubscriber.java
index 2856a5f..33d1253 100644
--- a/examples/demo/src/main/java/demoapp/dom/events/DemoEventSubscriber.java
+++ b/examples/demo/src/main/java/demoapp/dom/events/DemoEventSubscriber.java
@@ -30,6 +30,7 @@ import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.annotation.Nature;
 import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -54,7 +55,7 @@ public class DemoEventSubscriber {
         
         val eventLogWriter = factoryService.get(EventLogWriter.class); // <-- get a new writer
         
-        wrapper.async(eventLogWriter).storeEvent(event);
+        wrapper.async(eventLogWriter, AsyncControl.ignoreReturn()).storeEvent(event);
 
     }
 
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java
index 5f1e204..57505b3 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/auditing/AuditerServiceTest.java
@@ -41,6 +41,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.wrapper.DisabledException;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.testdomain.Incubating;
 import org.apache.isis.testdomain.Smoketest;
@@ -131,7 +133,9 @@ class AuditerServiceTest extends IsisIntegrationTestAbstract {
         kvStore.clear(AuditerServiceForTesting.class);
 
         // when - running within its own background task
-        wrapper.async(book, WrapperFactory.ExecutionModes.SKIP_RULES).setName("Book #2"); // don't enforce rules for this test
+        val control = AsyncControl.voidReturn().with(ExecutionModes.SKIP_RULES);
+        // don't enforce rules for this test
+        wrapper.async(book, control).setName("Book #2");
 
         // then - after the commit
         assertEquals("targetClassName=Jdo Book,propertyName=name,preValue=Sample Book,postValue=Book #2;",
@@ -150,10 +154,11 @@ class AuditerServiceTest extends IsisIntegrationTestAbstract {
 
         // when - running within its own background task
         assertThrows(DisabledException.class, ()->{
-        
-            wrapper.async(book, WrapperFactory.ExecutionModes.EXECUTE).setName("Book #2");
 
-            //future.get(1000, TimeUnit.SECONDS);
+            val control = AsyncControl.voidReturn().with(ExecutionModes.EXECUTE);
+            wrapper.async(book, control).setName("Book #2");
+
+            control.getFuture().get(1000, TimeUnit.SECONDS);
             
         });
         
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/commandexecution/WrapperAsyncTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/commandexecution/WrapperAsyncTest.java
index 12c416c..1fd01a8 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/commandexecution/WrapperAsyncTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/commandexecution/WrapperAsyncTest.java
@@ -43,7 +43,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import org.apache.isis.applib.events.domain.AbstractDomainEvent.Phase;
 import org.apache.isis.applib.services.factory.FactoryService;
 import org.apache.isis.applib.services.repository.RepositoryService;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
+import org.apache.isis.applib.services.wrapper.control.RuleCheckingPolicy;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.testdomain.Incubating;
 import org.apache.isis.testdomain.Smoketest;
@@ -99,8 +102,7 @@ class WrapperAsyncTest {
 
         actionDomainEventListener.prepareLatch();
 
-        wrapper.wrap(inventoryManager)
-        .updateProductPrice(product, 123);
+        wrapper.wrap(inventoryManager).updateProductPrice(product, 123);
 
         assertTrue(
                 actionDomainEventListener.getCountDownLatch()
@@ -120,14 +122,12 @@ class WrapperAsyncTest {
         actionDomainEventListener.prepareLatch();
 
         // use of custom executor (optional)
-        wrapper.async(inventoryManager, WrapperFactory.ExecutionModes.EXECUTE, WrapperFactory.RuleCheckingPolicy.ASYNC, Executors.newCachedThreadPool())
+        val control = AsyncControl.toReturn(JdoProduct.class).with(ExecutionModes.EXECUTE).with(RuleCheckingPolicy.ASYNC).with(Executors.newCachedThreadPool());
+        wrapper.async(inventoryManager, control)
                 .updateProductPrice(product, 123d);
 
-//XXX type-safety should prevent this snippet from being compiled!        
-//        Future<String> invocationResult2 = wrapper.async(inventoryManager)
-//                .invoke(Product::toString);
-        
-        // assertNotNull(invocationResult);
+        Future<JdoProduct> future = control.getFuture();
+        assertNotNull(future);
         
 
         assertTrue(
diff --git a/examples/smoketests/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java b/examples/smoketests/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java
index 6210a85..7a62d58 100644
--- a/examples/smoketests/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java
+++ b/examples/smoketests/src/test/java/org/apache/isis/testdomain/publishing/PublisherServiceTest.java
@@ -43,6 +43,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.wrapper.DisabledException;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
+import org.apache.isis.applib.services.wrapper.control.ExecutionModes;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.testdomain.Incubating;
 import org.apache.isis.testdomain.Smoketest;
@@ -143,9 +145,10 @@ class PublisherServiceTest extends IsisIntegrationTestAbstract {
         val latch = kvStore.latch(PublisherServiceForTesting.class);
 
         // when - running within its own background task
-        wrapper.async(book, WrapperFactory.ExecutionModes.SKIP_RULES).setName("Book #2"); // don't enforce rules for this test
+        AsyncControl<Void> control = AsyncControl.voidReturn().with(ExecutionModes.SKIP_RULES);
+        wrapper.async(book, control).setName("Book #2"); // don't enforce rules for this test
 
-        //future.get(10, TimeUnit.SECONDS);
+        control.getFuture().get(10, TimeUnit.SECONDS);
 
         latch.await(2, TimeUnit.SECONDS);
         
@@ -171,10 +174,11 @@ class PublisherServiceTest extends IsisIntegrationTestAbstract {
 
         // when - running within its own background task
         assertThrows(DisabledException.class, ()->{
+
+            val control = AsyncControl.voidReturn().with(ExecutionModes.EXECUTE);
+            wrapper.async(book, control).setName("Book #2");
             
-            wrapper.async(book, WrapperFactory.ExecutionModes.EXECUTE).setName("Book #2");
-            
-            //future.get(10, TimeUnit.SECONDS);
+            control.getFuture().get(10, TimeUnit.SECONDS);
             
         });
 
diff --git a/testing/fixtures/applib/src/main/java/org/apache/isis/testing/fixtures/applib/fixturescripts/FixtureScript.java b/testing/fixtures/applib/src/main/java/org/apache/isis/testing/fixtures/applib/fixturescripts/FixtureScript.java
index d680a53..7c5a69f 100644
--- a/testing/fixtures/applib/src/main/java/org/apache/isis/testing/fixtures/applib/fixturescripts/FixtureScript.java
+++ b/testing/fixtures/applib/src/main/java/org/apache/isis/testing/fixtures/applib/fixturescripts/FixtureScript.java
@@ -42,6 +42,7 @@ import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.applib.services.repository.RepositoryService;
 import org.apache.isis.applib.services.user.UserService;
+import org.apache.isis.applib.services.wrapper.control.ExecutionMode;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
@@ -780,7 +781,7 @@ public abstract class FixtureScript {
     /**
      * Convenience method, simply delegates to {@link WrapperFactory#wrap(Object, EnumSet)}.
      */
-    protected <T> T wrap(final T domainObject, final ImmutableEnumSet<WrapperFactory.ExecutionMode> executionMode) {
+    protected <T> T wrap(final T domainObject, final ImmutableEnumSet<ExecutionMode> executionMode) {
         return wrapperFactory.wrap(domainObject, executionMode);
     }