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/15 17:28:24 UTC

[isis] 31/31: ISIS-2278: adds some more AsyncControl flesh

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 ed4a39d4b85853caf94ffe831f705055490c8fb0
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Sun Mar 15 17:26:11 2020 +0000

    ISIS-2278: adds some more AsyncControl flesh
---
 .../applib/services/wrapper/WrapperFactory.java    | 145 ++++++----------
 .../applib/services/wrapper/WrappingObject.java    |   8 +-
 .../services/wrapper/control/AsyncControl.java     | 105 +++++++-----
 .../wrapper/control/AsyncControlService.java       |   7 +-
 .../services/wrapper/control/ControlAbstract.java  | 102 +++++++++++
 .../services/wrapper/control/ExecutionMode.java    |  15 +-
 .../services/wrapper/control/ExecutionModes.java   |  53 ------
 .../wrapper/control/RuleCheckingPolicy.java        |   8 -
 .../{AsyncControlService.java => SyncControl.java} |  29 ++--
 .../command/CommandExecutorServiceDefault.java     |   3 +
 .../wrapper/WrapperFactoryDefault.java             | 187 +++++++++------------
 .../AbstractCollectionInvocationHandler.java       |   2 +-
 .../DelegatingInvocationHandlerDefault.java        |   9 +-
 .../handlers/DomainObjectInvocationHandler.java    |  90 ++++------
 .../wrapper/handlers/ProxyContextHandler.java      |   7 +-
 .../wrapper/WrapperFactoryDefaultTest.java         |  30 ++--
 .../testdomain/auditing/AuditerServiceTest.java    |   1 -
 .../commandexecution/WrapperAsyncTest.java         |   6 +-
 .../publishing/PublisherServiceTest.java           |   1 -
 .../applib/fixturescripts/FixtureScript.java       |   7 +-
 20 files changed, 387 insertions(+), 428 deletions(-)

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 15a1179..db42c48 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
@@ -20,14 +20,10 @@
 package org.apache.isis.applib.services.wrapper;
 
 import java.util.List;
-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.control.SyncControl;
 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;
@@ -35,7 +31,14 @@ import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 /**
  * 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
- * the Isis programming model.
+ * the Apache Isis programming model.
+ *
+ * <p>
+ *     The wrapper can alternatively also be used to execute the action
+ *     asynchronously, through an {@link java.util.concurrent.ExecutorService}.
+ *     Any business rules will be invoked synchronously beforehand, however.
+ *     hand
+ * </p>
  *
  * <p>
  * The 'wrap' is a runtime-code-generated proxy that wraps the underlying domain
@@ -43,14 +46,13 @@ import org.apache.isis.core.commons.collections.ImmutableEnumSet;
  * <ul>
  * <li>a <tt>get</tt> method for properties or collections</li>
  * <li>a <tt>set</tt> method for properties</li>
- * <li>an <tt>addTo</tt> or <tt>removeFrom</tt> method for collections</li>
  * <li>any action</li>
  * </ul>
  *
  * <p>
  * Calling any of the above methods may result in a (subclass of)
  * {@link InteractionException} if the object disallows it. For example, if a
- * property is annotated with {@link Hidden} then a {@link HiddenException} will
+ * property is annotated as hidden then a {@link HiddenException} will
  * be thrown. Similarly if an action has a <tt>validate</tt> method and the
  * supplied arguments are invalid then a {@link InvalidException} will be
  * thrown.
@@ -63,12 +65,8 @@ import org.apache.isis.core.commons.collections.ImmutableEnumSet;
  * </ul>
  *
  * <p>
- * An exception will be thrown if any other methods are thrown.
- *
- * <p>
- * An implementation of this service (<tt>WrapperFactoryDefault</tt>) can be registered by including
- * <tt>o.a.i.core:isis-core-wrapper</tt> on the classpath; no further configuration is required.
- * </p>
+ * If the object has (see {@link #isWrapper(Object)} already been wrapped),
+ * then should just return the object back unchanged.
  */
 // tag::refguide[]
 // tag::refguide-async[]
@@ -80,8 +78,20 @@ public interface WrapperFactory {
     // end::refguide[]
 
     /**
-     * Same as {@link #wrap(Object)}, except the actual execution occurs only if
-     * the <tt>execute</tt> parameter indicates.
+     * Provides the &quot;wrapper&quot; of a domain object against which to invoke the action.
+     *
+     * <p>
+     *     The provided {@link SyncControl} determines whether business rules are checked first, and conversely
+     *     whether the action is executed.  There are therefore three typical cases:
+     *     <ul>
+     *         <li>check rules, execute action</li>
+     *         <li>skip rules, execute action</li>
+     *         <li>check rules, skip action</li>
+     *     </ul>
+     *     <p>
+     *         The last logical option (skip rules, skip action) is valid but doesn't make sense, as it's basically a no-op.
+     *     </p>
+     * </p>
      *
      * <p>
      * Otherwise, will do all the validations (raise exceptions as required
@@ -89,46 +99,35 @@ public interface WrapperFactory {
      */
     // tag::refguide[]
     <T> T wrap(T domainObject,                                      // <.>
-               ImmutableEnumSet<ExecutionMode> modes);
+               SyncControl syncControl);
 
     // end::refguide[]
     /**
-     * Provides the &quot;wrapper&quot; of the underlying domain object.
-     *
-     * <p>
-     * If the object has (see {@link #isWrapper(Object)} already been wrapped),
-     * then should just return the object back unchanged.
+     * A convenience overload for {@link #wrap(Object, SyncControl)},
+     * returning a wrapper to invoke the action synchronously, enforcing business rules.
+     * Any exceptions will be propagated, not swallowed.
      */
     // tag::refguide[]
     <T> T wrap(T domainObject);                                     // <.>
 
     // end::refguide[]
     /**
-     * Convenience method for {@link #wrap(Object, ImmutableEnumSet)} with {@link ExecutionModes#TRY},
-     * to make this feature more discoverable.
-     */
-    // tag::refguide[]
-    <T> T wrapTry(T domainObject);                                  // <.>
-
-    // end::refguide[]
-    /**
-     * Convenience method for {@link #wrap(Object, ImmutableEnumSet)} with {@link ExecutionModes#NO_EXECUTE},
-     * to make this feature more discoverable.
-     */
-    // tag::refguide[]
-    <T> T wrapNoExecute(T domainObject);                            // <.>
-
-    // end::refguide[]
-    /**
-     * Convenience method for {@link #wrap(Object, ImmutableEnumSet)} with {@link ExecutionModes#SKIP_RULES},
-     * to make this feature more discoverable.
+     * Provides the wrapper for a {@link FactoryService#mixin(Class, Object) mixin}, against which to invoke the action.
+     *
+     * <p>
+     *     The provided {@link SyncControl} determines whether business rules are checked first, and conversely
+     *     whether the action is executed.  See {@link #wrap(Object, SyncControl)} for more details on this.
+     * </p>
      */
     // tag::refguide[]
-    <T> T wrapSkipRules(T domainObject);                            // <.>
+    <T> T wrapMixin(Class<T> mixinClass, Object mixedIn,           // <.>
+                    SyncControl syncControl);
 
     // end::refguide[]
     /**
-     * {@link #wrap(Object) wraps} a {@link FactoryService#mixin(Class, Object) mixin}.
+     * A convenience overload for {@link #wrapMixin(Class, Object, SyncControl)},
+     * returning a wrapper to invoke the action synchronously, enforcing business rules.
+     * Any exceptions will be propagated, not swallowed.
      */
     // tag::refguide[]
     <T> T wrapMixin(Class<T> mixinClass, Object mixedIn);           // <.>
@@ -168,11 +167,11 @@ public interface WrapperFactory {
      * Returns a proxy object for the provided {@code domainObject},
      * through which can execute the action asynchronously.
      *
-     * @param <T>
+     * @param <T> - the type of the domain object
+     * @param <R> - the type of the return of the action
      * @param domainObject
-     * @param mode
+     * @param asyncControl
      *
-     * @param executorService
      * @since 2.0
      */
     // tag::refguide-async[]
@@ -187,63 +186,15 @@ public interface WrapperFactory {
      * @param <T>
      * @param mixinClass
      * @param mixedIn
-     * @param modes
+     * @param asyncControl
      *
      * @since 2.0
      */
     // tag::refguide-async[]
-    <T> T asyncMixin(                                    // <.>
-                Class<T> mixinClass, Object mixedIn,
-                ImmutableEnumSet<ExecutionMode> modes,
-                RuleCheckingPolicy ruleCheckingPolicy,
-                ExecutorService executorService);
-
-    // end::refguide-async[]
-    /**
-     * Returns a {@link AsyncControl} bound to the provided {@code mixinClass},
-     * to prepare for type-safe asynchronous action execution.
-     *
-     * @param <T>
-     * @param mixinClass
-     * @param mixedIn
-     * @param modes
-     *
-     * @since 2.0
-     */
-    // tag::refguide-async[]
-    default <T> T asyncMixin(                                    // <.>
-                Class<T> mixinClass, Object mixedIn,
-                ImmutableEnumSet<ExecutionMode> modes) {
-        // end::refguide-async[]
-
-        return asyncMixin(
-                mixinClass, mixedIn, modes,
-                RuleCheckingPolicy.ASYNC, ForkJoinPool.commonPool());
-
-        // tag::refguide-async[]
-        // ...
-    }
-
-    // end::refguide-async[]
-    /**
-     * Shortcut for {@link #asyncMixin(Class, Object, ImmutableEnumSet)} using execution mode
-     * {@link ExecutionModes#EXECUTE}.
-     * @param <T>
-     * @param mixinClass
-     * @param mixedIn
-     *
-     * @since 2.0
-     */
-    // tag::refguide-async[]
-    default <T> T asyncMixin(                            // <.>
-                        Class<T> mixinClass, Object mixedIn) {
-        // end::refguide-async[]
-
-        return asyncMixin(mixinClass, mixedIn, ExecutionModes.EXECUTE);
+    <T,R> T asyncMixin(                                    // <.>
+             Class<T> mixinClass, Object mixedIn,
+             AsyncControl<R> asyncControl);
 
-        // tag::refguide-async[]
-        // ...
-    }
     // end::refguide-async[]
 
 
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 efcec9b..62369ab 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
@@ -66,9 +66,9 @@ public interface WrappingObject {
 
     // end::refguide[]
     /**
-     * The {@link EnumSet< ExecutionMode > execution mode}
-     * with which this wrapper was
-     * {@link WrapperFactory#wrap(Object, ImmutableEnumSet) created}.
+     * The {@link EnumSet<ExecutionMode > execution modes} inferred from the
+     * {@link org.apache.isis.applib.services.wrapper.control.SyncControl} with which this wrapper was
+     * {@link WrapperFactory#wrap(Object, org.apache.isis.applib.services.wrapper.control.SyncControl) created}.
      *
      * <p>
      * NOTE: domain classes may not have a method with this name.  The <tt>__isis_</tt> prefix is
@@ -76,7 +76,7 @@ public interface WrappingObject {
      * </p>
      */
     // tag::refguide[]
-    ImmutableEnumSet<ExecutionMode> __isis_executionMode();
+    ImmutableEnumSet<ExecutionMode> __isis_executionModes();
 
 }
 // 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
index 4f5d893..8059930 100644
--- 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
@@ -19,11 +19,21 @@
 package org.apache.isis.applib.services.wrapper.control;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ForkJoinPool;
 import java.util.concurrent.Future;
 import java.util.function.Consumer;
 
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.user.RoleMemento;
+import org.apache.isis.applib.services.user.UserMemento;
+import org.apache.isis.applib.services.user.UserService;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.schema.common.v2.OidDto;
 
@@ -38,43 +48,44 @@ 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 class AsyncControl<R> extends ControlAbstract<AsyncControl<R>> {
 
-    public static AsyncControl<Void> ignoreReturn() {
+    public static AsyncControl<Void> create() {
         return new AsyncControl<>();
     }
 
-    public static AsyncControl<Void> voidReturn() {
+    public static <X> AsyncControl<X> create(final Class<X> clazz) {
         return new AsyncControl<>();
     }
 
-    private AsyncControl() {
+    /**
+     * Alias for {@link #create(Class)}
+     */
+    public static <X> AsyncControl<X> toReturn(final Class<X> clazz) {
+        return create(clazz);
     }
 
     /**
-     * Set by framework.
+     * Alias for {@link #create()}
      */
-    @Setter(AccessLevel.PACKAGE)
-    private Method method;
+    public static AsyncControl<Void> ignoreReturn() {
+        return create();
+    }
 
     /**
-     * Set by framework.
+     * Alias for {@link #create()}
      */
-    @Setter(AccessLevel.PACKAGE)
-    private OidDto oidDto;
-
+    public static AsyncControl<Void> voidReturn() {
+        return create();
+    }
 
-    @Getter @NonNull
-    private ImmutableEnumSet<ExecutionMode> executionModes = ExecutionModes.EXECUTE;
-    public AsyncControl<R> with(ImmutableEnumSet<ExecutionMode> executionModes) {
-        this.executionModes = executionModes;
-        return this;
+    private AsyncControl() {
+        with((exception) -> {
+            log.error(logMessage(), exception);
+        });
     }
 
+
     @Getter @NonNull
     private ExecutorService executorService = ForkJoinPool.commonPool();
     public AsyncControl<R> with(ExecutorService executorService) {
@@ -82,41 +93,49 @@ public class AsyncControl<R> {
         return this;
     }
 
-    @Getter @NonNull
-    private RuleCheckingPolicy ruleCheckingPolicy = RuleCheckingPolicy.ASYNC;
-    public AsyncControl<R> with(RuleCheckingPolicy ruleCheckingPolicy) {
-        this.ruleCheckingPolicy = ruleCheckingPolicy;
+
+    /**
+     * Defaults to user initiating the action, if not overridden
+     */
+    @Getter
+    private String user;
+    public AsyncControl<R> withUser(final String user) {
+        this.user = user;
         return this;
     }
 
-    @Getter @NonNull
-    private Consumer<Exception> exceptionHandler = (exception) -> {
-        log.error(logMessage(), exception);
-    };
-    public AsyncControl<R> with(Consumer<Exception> exceptionHandler) {
-        this.exceptionHandler = exceptionHandler;
+    /**
+     * Defaults to roles of user initiating the action, if not overridden
+     */
+    @Getter
+    private List<String> roles;
+    public AsyncControl<R> withRoles(final List<String> roles) {
+        this.roles = Collections.unmodifiableList(roles);
         return this;
     }
+    public AsyncControl<R> withRoles(String... roles) {
+        return withRoles(Arrays.asList(roles));
+    }
+
+    /**
+     * Set by framework
+     */
+    @Getter @Setter(AccessLevel.PACKAGE)
+    private Future<R> future;
 
     private String logMessage() {
         StringBuilder buf = new StringBuilder("Failed to execute ");
-        if(method != null) {
-            buf.append(" ").append(method.getName()).append(" ");
-            if(oidDto != null) {
+        if(getMethod() != null) {
+            buf.append(" ").append(getMethod().getName()).append(" ");
+            if(getBookmark() != null) {
                 buf.append(" on '")
-                    .append(oidDto.getType())
-                    .append(":")
-                    .append(oidDto.getId())
-                    .append("'");
+                        .append(getBookmark().getObjectType())
+                        .append(":")
+                        .append(getBookmark().getIdentifier())
+                        .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
index 44621c5..853c94c 100644
--- 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
@@ -26,6 +26,7 @@ import java.util.function.Consumer;
 
 import org.springframework.stereotype.Component;
 
+import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.schema.common.v2.OidDto;
 
@@ -39,11 +40,11 @@ import lombok.extern.log4j.Log4j2;
  * For framework internal use.
  */
 @Component
-public class AsyncControlService<R> {
+public class AsyncControlService {
 
-    public <R> AsyncControl<R> init(AsyncControl<R> asyncControl, Method method, OidDto oidDto) {
+    public <R> AsyncControl<R> init(AsyncControl<R> asyncControl, Method method, Bookmark bookmark) {
         asyncControl.setMethod(method);
-        asyncControl.setOidDto(oidDto);
+        asyncControl.setBookmark(bookmark);
         return asyncControl;
     }
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ControlAbstract.java b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ControlAbstract.java
new file mode 100644
index 0000000..0ed6a1e
--- /dev/null
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ControlAbstract.java
@@ -0,0 +1,102 @@
+/*
+ *  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.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.function.Consumer;
+
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+public class ControlAbstract<T extends ControlAbstract<T>> {
+
+    protected ControlAbstract() {
+    }
+
+    /**
+     * Set by framework.
+     */
+    @Getter(AccessLevel.PACKAGE)
+    @Setter(AccessLevel.PACKAGE)
+    private Method method;
+
+    /**
+     * Set by framework.
+     */
+    @Getter(AccessLevel.PACKAGE)
+    @Setter(AccessLevel.PACKAGE)
+    private Bookmark bookmark;
+
+    private boolean checkRules = true;
+    public T withCheckRules() {
+        checkRules = true;
+        return (T)this;
+    }
+    public T withSkipRules() {
+        checkRules = false;
+        return (T)this;
+    }
+
+    private boolean execute = true;
+    public T withExecute() {
+        execute = true;
+        return (T)this;
+    }
+    public T withNoExecute() {
+        execute = false;
+        return (T)this;
+    }
+
+    public ImmutableEnumSet<ExecutionMode> getExecutionModes() {
+        EnumSet<ExecutionMode> modes = EnumSet.noneOf(ExecutionMode.class);
+        if(!checkRules) {
+            modes.add(ExecutionMode.SKIP_RULE_VALIDATION);
+        }
+        if(!execute) {
+            modes.add(ExecutionMode.SKIP_EXECUTION);
+        }
+        return ImmutableEnumSet.from(modes);
+    }
+
+    /**
+     * Initialized in constructor.
+     */
+    @Getter @NonNull
+    private Consumer<Exception> exceptionHandler;
+    public T with(Consumer<Exception> exceptionHandler) {
+        this.exceptionHandler = exceptionHandler;
+        return (T)this;
+    }
+
+
+
+}
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
index 3226e41..694a1c3 100644
--- 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
@@ -1,6 +1,9 @@
 package org.apache.isis.applib.services.wrapper.control;
 
+import java.util.function.Consumer;
+
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
+import org.apache.isis.applib.services.wrapper.control.AsyncControl;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 
 import lombok.AccessLevel;
@@ -10,7 +13,7 @@ import lombok.RequiredArgsConstructor;
  * Whether interactions with the wrapper are actually passed onto the
  * underlying domain object.
  *
- * @see WrapperFactory#wrap(Object, ImmutableEnumSet)
+ * @see WrapperFactory#wrap(Object, org.apache.isis.applib.services.wrapper.control.SyncControl)
  */
 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
 // tag::refguide[]
@@ -23,15 +26,13 @@ public enum ExecutionMode {
     SKIP_RULE_VALIDATION,
     // end::refguide[]
     /**
-     * Skip execution.
+     * Skip actual execution.
+     *
+     * <p>
+     * This is not supported for {@link WrapperFactory#async(Object, AsyncControl)}; instead just invoke {@link WrapperFactory#wrap(Object, ImmutableEnumSet)}.
      */
     // 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
deleted file mode 100644
index d910eed..0000000
--- a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/ExecutionModes.java
+++ /dev/null
@@ -1,53 +0,0 @@
-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
deleted file mode 100644
index bc3dc06..0000000
--- a/api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/RuleCheckingPolicy.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.apache.isis.applib.services.wrapper.control;
-
-// tag::refguide[]
-public enum RuleCheckingPolicy {
-    IMMEDIATE,   // <.>
-    ASYNC   // <.>
-}
-// end::refguide[]
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/SyncControl.java
similarity index 73%
copy from api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/AsyncControlService.java
copy to api/applib/src/main/java/org/apache/isis/applib/services/wrapper/control/SyncControl.java
index 44621c5..3bc403b 100644
--- 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/SyncControl.java
@@ -19,13 +19,14 @@
 package org.apache.isis.applib.services.wrapper.control;
 
 import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 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;
 
@@ -33,23 +34,27 @@ import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
 import lombok.Setter;
+import lombok.SneakyThrows;
 import lombok.extern.log4j.Log4j2;
 
 /**
- * For framework internal use.
  */
-@Component
-public class AsyncControlService<R> {
+@Log4j2
+public class SyncControl extends ControlAbstract<SyncControl> {
 
-    public <R> AsyncControl<R> init(AsyncControl<R> asyncControl, Method method, OidDto oidDto) {
-        asyncControl.setMethod(method);
-        asyncControl.setOidDto(oidDto);
-        return asyncControl;
+    public static SyncControl create() {
+        return new SyncControl();
     }
 
-    public <R> AsyncControl<R> update(AsyncControl<R> asyncControl, Future<R> future) {
-        asyncControl.setFuture(future);
-        return asyncControl;
+    private SyncControl() {
+
+        with(new Consumer<Exception>() {
+            @SneakyThrows
+            @Override
+            public void accept(Exception e) {
+                throw e;
+            }
+        });
     }
 
 }
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandExecutorServiceDefault.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandExecutorServiceDefault.java
index d729583..7103ccb 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandExecutorServiceDefault.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/command/CommandExecutorServiceDefault.java
@@ -53,6 +53,8 @@ import org.apache.isis.core.metamodel.adapter.oid.Oid;
 import org.apache.isis.core.metamodel.adapter.oid.RootOid;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
+import org.apache.isis.core.metamodel.interactions.InteractionUtils;
+import org.apache.isis.core.metamodel.interactions.VisibilityContext;
 import org.apache.isis.core.metamodel.objectmanager.load.ObjectLoader;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecId;
@@ -175,6 +177,7 @@ public class CommandExecutorServiceDefault implements CommandExecutorService {
                 // we pass 'null' for the mixedInAdapter; if this action _is_ a mixin then
                 // it will switch the targetAdapter to be the mixedInAdapter transparently
                 val argAdapters = argAdaptersFor(actionDto);
+
                 val resultAdapter = objectAction.execute(
                         targetAdapter, null, argAdapters, InteractionInitiatedBy.FRAMEWORK);
 
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 074686e..1d45c36 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,6 +18,7 @@
  */
 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;
@@ -40,18 +41,17 @@ import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.OrderPrecedence;
+import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.BookmarkService;
-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.control.SyncControl;
 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;
@@ -92,6 +92,7 @@ import org.apache.isis.core.runtimeservices.wrapper.dispatchers.InteractionEvent
 import org.apache.isis.core.runtimeservices.wrapper.handlers.ProxyContextHandler;
 import org.apache.isis.core.runtimeservices.wrapper.proxy.ProxyCreator;
 import org.apache.isis.schema.cmd.v2.CommandDto;
+import org.apache.isis.schema.common.v2.OidDto;
 
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
@@ -154,48 +155,40 @@ public class WrapperFactoryDefault implements WrapperFactory {
     // -- WRAPPING
     
     @Override
-    public <T> T wrapMixin(Class<T> mixinClass, Object mixedIn) {
-        return wrap(factoryService.mixin(mixinClass, mixedIn));
-    }
-
-    @Override
     public <T> T wrap(T domainObject) {
-        return wrap(domainObject, ExecutionModes.EXECUTE);
-    }
-
-    @Override
-    public <T> T wrapTry(T domainObject) {
-        return wrap(domainObject, ExecutionModes.TRY);
-    }
-
-    @Override
-    public <T> T wrapNoExecute(T domainObject) {
-        return wrap(domainObject, ExecutionModes.NO_EXECUTE);
-    }
-
-    @Override
-    public <T> T wrapSkipRules(T domainObject) {
-        return wrap(domainObject, ExecutionModes.SKIP_RULES);
+        return wrap(domainObject, SyncControl.create());
     }
 
     @Override
     public <T> T wrap(
             final T domainObject,
-            final ImmutableEnumSet<ExecutionMode> modes) {
+            final SyncControl syncControl) {
+        final ImmutableEnumSet<ExecutionMode> modes = syncControl.getExecutionModes();
         if (domainObject instanceof WrappingObject) {
             val wrapperObject = (WrappingObject) domainObject;
-            val executionMode = wrapperObject.__isis_executionMode();
+            val executionMode = wrapperObject.__isis_executionModes();
             if(executionMode != modes) {
                 val underlyingDomainObject = wrapperObject.__isis_wrapped();
-                return _Casts.uncheckedCast(createProxy(underlyingDomainObject, modes));
+                return _Casts.uncheckedCast(createProxy(underlyingDomainObject, syncControl));
             }
             return domainObject;
         }
-        return createProxy(domainObject, modes);
+        return createProxy(domainObject, syncControl);
     }
 
-    protected <T> T createProxy(T domainObject, ImmutableEnumSet<ExecutionMode> modes) {
-        return proxyContextHandler.proxy(metaModelContext, domainObject, modes);
+    @Override
+    public <T> T wrapMixin(Class<T> mixinClass, Object mixedIn) {
+        return wrapMixin(mixinClass, mixedIn, SyncControl.create());
+    }
+
+    @Override
+    public <T> T wrapMixin(Class<T> mixinClass, Object mixedIn, SyncControl syncControl) {
+        T mixin = factoryService.mixin(mixinClass, mixedIn);
+        return wrap(mixin, syncControl);
+    }
+
+    protected <T> T createProxy(T domainObject, SyncControl syncControl) {
+        return proxyContextHandler.proxy(metaModelContext, domainObject, syncControl);
     }
 
     @Override
@@ -216,7 +209,7 @@ public class WrapperFactoryDefault implements WrapperFactory {
     // -- ASYNC WRAPPING
 
     @Inject
-    AsyncControlService asyncControlService;
+    private AsyncControlService asyncControlService;
 
     @Override
     public <T,R> T async(
@@ -224,92 +217,82 @@ public class WrapperFactoryDefault implements WrapperFactory {
             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);
 
-        return factory.createInstance((Object proxy, Method method, Object[] args) -> {
-
-            val executionModesAsync = determineExecutionModesAsync(executionModes, ruleCheckingPolicy);
-            if (!shouldValidateAsync(executionModesAsync) && !shouldExecuteAsync(executionModesAsync)) {
-                // nothing to be done.
-                return null;
-            }
+        return factory.createInstance(new InvocationHandler() {
+            @Override
+            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+
+                //
+                // executed in the same thread as caller..
+                //
+                final boolean inheritedFromObject = method.getDeclaringClass().equals(Object.class);
+                if (inheritedFromObject) {
+                    return method.invoke(domainObject, args);
+                }
 
-            //
-            // executed in the same thread as caller..
-            //
-            final boolean inheritedFromObject = method.getDeclaringClass().equals(Object.class);
-            if(inheritedFromObject) {
-                return method.invoke(domainObject, args);
-            }
+                val skipRules = executionModes.contains(ExecutionMode.SKIP_RULE_VALIDATION);
+                if(!skipRules) {
+                    // normal wrapped object...
+                    SyncControl syncControl = SyncControl.create().withNoExecute();
+                    T syncProxy = WrapperFactoryDefault.this.createProxy(domainObject, syncControl);
+                    method.invoke(syncProxy, args);
+                }
 
-            val executionModesSync = determineExecutionModesSync(executionModes, ruleCheckingPolicy);
-            if(shouldValidateSync(executionModesSync)) {
-                // normal wrapped object...
-                T syncProxy = createProxy(domainObject, executionModesSync);
-                method.invoke(syncProxy, args);
-            }
 
-            //
-            // submit async stuff
-            //
-            final ObjectSpecificationDefault targetObjSpec = (ObjectSpecificationDefault) specificationLoader.loadSpecification(method.getDeclaringClass());
-            final ObjectMember member = targetObjSpec.getMember(method);
+                //
+                // submit async stuff
+                //
+                final ObjectSpecificationDefault targetObjSpec = (ObjectSpecificationDefault) specificationLoader.loadSpecification(method.getDeclaringClass());
+                final ObjectMember member = targetObjSpec.getMember(method);
 
-            if(member == null) {
-                return method.invoke(domainObject, args);
-            }
+                if (member == null) {
+                    return method.invoke(domainObject, args);
+                }
 
-            if(!(member instanceof ObjectAction)) {
-                throw new UnsupportedOperationException(
-                        "Only actions can be executed in the background "
-                                + "(method " + method.getName() + " represents a " + member.getFeatureType().name() + "')");
-            }
+                if (!(member instanceof ObjectAction)) {
+                    throw new UnsupportedOperationException(
+                            "Only actions can be executed in the background "
+                                    + "(method " + method.getName() + " represents a " + member.getFeatureType().name() + "')");
+                }
 
-            ObjectAction action = (ObjectAction) member;
+                ObjectAction action = (ObjectAction) member;
 
-            val isisSession = isisSessionTracker.currentSession().orElseThrow(() -> new RuntimeException("No IsisSession is open"));
-            val authSession = isisSession.getAuthenticationSession();
+                val isisSession = isisSessionTracker.currentSession().orElseThrow(() -> new RuntimeException("No IsisSession is open"));
+                val authSession = isisSession.getAuthenticationSession();
 
-            final ManagedObject domainObjectAdapter = isisSession.getObjectManager().adapt(domainObject);
-            final List<ManagedObject> argAdapters = Arrays.asList(adaptersFor(args));
+                final ManagedObject domainObjectAdapter = isisSession.getObjectManager().adapt(domainObject);
+                final List<ManagedObject> argAdapters = Arrays.asList(WrapperFactoryDefault.this.adaptersFor(args));
 
-            final List<ManagedObject> targetList = Collections.singletonList(domainObjectAdapter);
-            final CommandDto dto =
-                    commandDtoServiceInternal.asCommandDto(targetList, action, argAdapters);
-            asyncControlService.init(asyncControl, method, dto.getTargets().getOid().get(0));
+                final List<ManagedObject> targetList = Collections.singletonList(domainObjectAdapter);
+                final CommandDto dto =
+                        commandDtoServiceInternal.asCommandDto(targetList, action, argAdapters);
+                final OidDto oidDto = dto.getTargets().getOid().get(0);
 
-            Future<?> submit = executorService.submit(() -> {
+                asyncControlService.init(asyncControl, method, Bookmark.from(oidDto));
 
-                isisSessionFactory.runAuthenticated(authSession, () -> {
+                Future future = executorService.submit(new Runnable() {
+                    @Override
+                    public void run() {
 
-                    if (shouldValidateAsync(executionModesAsync)) {
-                        // TODO...
-                    }
-                    if (shouldExecuteAsync(executionModesAsync)) {
-                        commandExecutorService.executeCommand(dto);
+                        isisSessionFactory.runAuthenticated(authSession, () -> {
+                            commandExecutorService.executeCommand(dto);
+                        });
                     }
-
                 });
-            });
-            asyncControlService.update(asyncControl, submit);
-            return null;
-
+                asyncControlService.update(asyncControl, future);
+                return null;
 
+            }
         }, false);
     }
 
     @Override
-    public <T> T asyncMixin(
-            Class<T> mixinClass, Object mixedIn,
-            ImmutableEnumSet<ExecutionMode> modes,
-            RuleCheckingPolicy ruleCheckingPolicy,
-            ExecutorService executorService) {
-
-        throw new RuntimeException("TODO");
+    public <T, R> T asyncMixin(Class<T> mixinClass, Object mixedIn, AsyncControl<R> asyncControl) {
+        throw new RuntimeException("TODO!!!");
     }
 
     private ManagedObject[] adaptersFor(final Object[] args) {
@@ -318,24 +301,6 @@ public class WrapperFactoryDefault implements WrapperFactory {
         return CommandUtil.adaptersFor(args, objectManager);
     }
 
-    private static ImmutableEnumSet<ExecutionMode> determineExecutionModesSync(
-            ImmutableEnumSet<ExecutionMode> executionModes,
-            RuleCheckingPolicy ruleCheckingPolicy) {
-        return executionModes.contains(ExecutionMode.SKIP_RULE_VALIDATION) ||
-                ruleCheckingPolicy != RuleCheckingPolicy.IMMEDIATE
-                ? ExecutionModes.NOOP
-                : ExecutionModes.NO_EXECUTE;
-    }
-
-    private static ImmutableEnumSet<ExecutionMode> determineExecutionModesAsync(
-            ImmutableEnumSet<ExecutionMode> executionModes,
-            RuleCheckingPolicy ruleCheckingPolicy) {
-        return executionModes.contains(ExecutionMode.SKIP_RULE_VALIDATION) ||
-                RuleCheckingPolicy.IMMEDIATE == ruleCheckingPolicy
-                ? join(executionModes, ExecutionMode.SKIP_RULE_VALIDATION)
-                : executionModes;
-    }
-
     private static ImmutableEnumSet<ExecutionMode> join(
             ImmutableEnumSet<ExecutionMode> executionModes,
             ExecutionMode skipRuleValidation) {
diff --git a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/AbstractCollectionInvocationHandler.java b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/AbstractCollectionInvocationHandler.java
index 1f5ebbb..1eda159 100644
--- a/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/AbstractCollectionInvocationHandler.java
+++ b/core/runtimeservices/src/main/java/org/apache/isis/core/runtimeservices/wrapper/handlers/AbstractCollectionInvocationHandler.java
@@ -44,7 +44,7 @@ abstract class AbstractCollectionInvocationHandler<T, C> extends DelegatingInvoc
         
         super(otma.getMetaModelContext().getServiceRegistry(), 
                 collectionOrMapToProxy, 
-                handler.getExecutionMode());
+                handler.getSyncControl());
         
         this.collectionName = collectionName;
         this.oneToManyAssociation = otma;
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 272c10c..7ad4e7d 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,9 +24,8 @@ 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.control.ExecutionMode;
+import org.apache.isis.applib.services.wrapper.control.SyncControl;
 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;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.objectmanager.load.ObjectLoader;
@@ -43,7 +42,7 @@ public class DelegatingInvocationHandlerDefault<T> implements DelegatingInvocati
     // getter is API
     @Getter(onMethod = @__(@Override)) private final T delegate;
     @Getter protected final WrapperFactory wrapperFactory;
-    @Getter private final ImmutableEnumSet<ExecutionMode> executionMode;
+    @Getter private final SyncControl syncControl;
 
     protected final Method equalsMethod;
     protected final Method hashCodeMethod;
@@ -56,7 +55,7 @@ public class DelegatingInvocationHandlerDefault<T> implements DelegatingInvocati
     public DelegatingInvocationHandlerDefault(
             final ServiceRegistry serviceRegistry,
             final T delegate,
-            final ImmutableEnumSet<ExecutionMode> executionMode) {
+            final SyncControl syncControl) {
 
         if (delegate == null) {
             throw new IllegalArgumentException("delegate must not be null");
@@ -64,7 +63,7 @@ public class DelegatingInvocationHandlerDefault<T> implements DelegatingInvocati
         this.delegate = delegate;
         this.wrapperFactory = serviceRegistry.lookupServiceElseFail(WrapperFactory.class);
         this.objectManager = serviceRegistry.lookupServiceElseFail(ObjectManager.class);
-        this.executionMode = executionMode;
+        this.syncControl = syncControl;
 
         try {
             equalsMethod = delegate.getClass().getMethod("equals", new Class[] { Object.class });
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 9eb21b0..9523fe7 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
@@ -35,13 +35,13 @@ import org.apache.isis.applib.services.wrapper.InteractionException;
 import org.apache.isis.applib.services.wrapper.InvalidException;
 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.SyncControl;
 import org.apache.isis.applib.services.wrapper.events.CollectionAccessEvent;
 import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 import org.apache.isis.applib.services.wrapper.events.PropertyAccessEvent;
 import org.apache.isis.applib.services.wrapper.events.UsabilityEvent;
 import org.apache.isis.applib.services.wrapper.events.ValidityEvent;
 import org.apache.isis.applib.services.wrapper.events.VisibilityEvent;
-import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.commons.internal.base._NullSafe;
 import org.apache.isis.core.commons.internal.collections._Arrays;
 import org.apache.isis.core.commons.internal.collections._Lists;
@@ -73,7 +73,6 @@ import lombok.val;
 public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandlerDefault<T> {
 
     private final ProxyContextHandler proxy;
-    private final ImmutableEnumSet<ExecutionMode> executionMode;
     private final MetaModelContext mmContext;
 
     /**
@@ -92,23 +91,20 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
     protected Method __isis_wrappedMethod;
 
     /**
-     * The <tt>__isis_executionMode()</tt> method from {@link WrappingObject#__isis_executionMode()}.
+     * The <tt>__isis_executionModes()</tt> method from {@link WrappingObject#__isis_executionModes()}.
      */
-    protected Method __isis_executionMode;
+    protected Method __isis_executionModes;
 
     private EntityFacet entityFacet; 
 
     public DomainObjectInvocationHandler(
             final MetaModelContext metaModelContext,
             final T domainObject,
-            final ImmutableEnumSet<ExecutionMode> mode,
-            final ProxyContextHandler proxy) {
-        
-        super(metaModelContext.getServiceRegistry(), domainObject, mode);
+            final SyncControl syncControl, final ProxyContextHandler proxy) {
+        super(metaModelContext.getServiceRegistry(), domainObject, syncControl);
 
         this.mmContext = metaModelContext;
         this.proxy = proxy;
-        this.executionMode = mode;
 
         try {
             titleMethod = domainObject.getClass().getMethod("title", new Class[]{});
@@ -118,16 +114,17 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         try {
             __isis_saveMethod = WrappingObject.class.getMethod("__isis_save", new Class[]{});
             __isis_wrappedMethod = WrappingObject.class.getMethod("__isis_wrapped", new Class[]{});
-            __isis_executionMode = WrappingObject.class.getMethod("__isis_executionMode", new Class[]{});
+            __isis_executionModes = WrappingObject.class.getMethod("__isis_executionModes", new Class[]{});
             
-            entityFacet = metaModelContext.getSpecification(domainObject.getClass())
-                    .getFacet(EntityFacet.class);
 
         } catch (final NoSuchMethodException nsme) {
             throw new IllegalStateException(
-                    "Could not locate reserved declared methods in the WrappingObject and WrappedObject interfaces",
+                    "Could not locate reserved declared methods in the WrappingObject interfaces",
                     nsme);
         }
+
+        entityFacet = metaModelContext.getSpecification(domainObject.getClass())
+                .getFacet(EntityFacet.class);
     }
 
     @Override
@@ -147,25 +144,24 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
 
         final ManagedObject targetAdapter = getObjectManager().adapt(getDelegate());
 
-        if (isTitleMethod(method)) {
+        if (method.equals(titleMethod)) {
             return handleTitleMethod(targetAdapter);
         }
 
 
         final ObjectSpecification targetSpec = targetAdapter.getSpecification();
-        final ObjectSpecification targetNoSpec = targetSpec;
 
         // save method, through the proxy
-        if (isSaveMethod(method)) {
-            return handleSaveMethod(targetAdapter, targetNoSpec);
+        if (method.equals(__isis_saveMethod)) {
+            return handleSaveMethod(targetAdapter, targetSpec);
         }
 
-        if (isWrappedMethod(method)) {
+        if (method.equals(__isis_wrappedMethod)) {
             return getDelegate();
         }
 
-        if (isExecutionModeMethod(method)) {
-            return executionMode;
+        if (method.equals(__isis_executionModes)) {
+            return getSyncControl().getExecutionModes();
         }
 
         final ObjectMember objectMember = locateAndCheckMember(method);
@@ -772,22 +768,6 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
         return member;
     }
 
-    protected boolean isTitleMethod(final Method method) {
-        return method.equals(titleMethod);
-    }
-
-    protected boolean isSaveMethod(final Method method) {
-        return method.equals(__isis_saveMethod);
-    }
-
-    protected boolean isWrappedMethod(final Method method) {
-        return method.equals(__isis_wrappedMethod);
-    }
-
-    protected boolean isExecutionModeMethod(final Method method) {
-        return method.equals(__isis_executionMode);
-    }
-
     // -- SPECIFICATION LOOKUP
 
     private ObjectSpecificationDefault getJavaSpecificationOfOwningClass(final Method method) {
@@ -809,45 +789,39 @@ public class DomainObjectInvocationHandler<T> extends DelegatingInvocationHandle
     // -- HELPER
     
     private boolean shouldEnforceRules() {
-        return !getExecutionMode().contains(ExecutionMode.SKIP_RULE_VALIDATION);
+        return !getSyncControl().getExecutionModes().contains(ExecutionMode.SKIP_RULE_VALIDATION);
     }
     
     private boolean shouldExecute() {
-        return !getExecutionMode().contains(ExecutionMode.SKIP_EXECUTION);
-    }
-    
-    private boolean shouldFailFast() {
-        return !getExecutionMode().contains(ExecutionMode.SWALLOW_EXCEPTIONS);
+        return !getSyncControl().getExecutionModes().contains(ExecutionMode.SKIP_EXECUTION);
     }
     
+
     private void runValidationTask(Runnable task) {
         if(!shouldEnforceRules()) {
             return;
         }
-        if(shouldFailFast()) {
+        try {
             task.run();
-        } else {
-            try {
-                task.run();
-            } catch(Exception ex) {
-                // swallow
-            }
+        } catch(Exception ex) {
+            handleException(ex);
         }
     }
-    
+
+    private void handleException(Exception ex) {
+        getSyncControl().getExceptionHandler().accept(ex);
+    }
+
     private <X> X runExecutionTask(Supplier<X> task) {
         if(!shouldExecute()) {
             return null;
         }
-        if(shouldFailFast()) {
+        try {
             return task.get();
-        } else {
-            try {
-                return task.get();
-            } catch(Exception ex) {
-                // swallow
-                return null;
-            }
+        } catch(Exception ex) {
+            handleException(ex);
+            // in case the exception handler doesn't throw
+            return null;
         }
     }
 
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 fb24725..f11ab15 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,8 +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.control.ExecutionMode;
-import org.apache.isis.core.commons.collections.ImmutableEnumSet;
+import org.apache.isis.applib.services.wrapper.control.SyncControl;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.runtimeservices.wrapper.proxy.ProxyCreator;
@@ -37,12 +36,12 @@ public class ProxyContextHandler {
 
     @NonNull private final ProxyCreator proxyCreator;
 
-    public <T> T proxy(MetaModelContext metaModelContext, T domainObject, ImmutableEnumSet<ExecutionMode> mode) {
+    public <T> T proxy(MetaModelContext metaModelContext, T domainObject, SyncControl syncControl) {
 
         val invocationHandler = new DomainObjectInvocationHandler<T>(
                 metaModelContext, 
                 domainObject,
-                mode,
+                syncControl,
                 this);
 
         return proxyCreator.instantiateProxy(invocationHandler);
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 bd5dd15..408f6c3 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
@@ -18,6 +18,8 @@
  */
 package org.apache.isis.core.runtimeservices.wrapper;
 
+import org.assertj.core.api.Assertions;
+import org.hamcrest.CoreMatchers;
 import org.jmock.auto.Mock;
 import org.junit.Before;
 import org.junit.Rule;
@@ -30,7 +32,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 
 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.applib.services.wrapper.control.SyncControl;
 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;
@@ -43,10 +45,10 @@ public class WrapperFactoryDefaultTest {
     }
 
     @RequiredArgsConstructor
-    private static class WrappedDomainObject extends DomainObject implements WrappingObject {
+    private static class WrappingDomainObject extends DomainObject implements WrappingObject {
 
         private final DomainObject wrappedObject;
-        private final ImmutableEnumSet<ExecutionMode> mode;
+        private final ImmutableEnumSet<ExecutionMode> modes;
 
         @Override
         public void __isis_save() {
@@ -58,8 +60,8 @@ public class WrapperFactoryDefaultTest {
         }
 
         @Override
-        public ImmutableEnumSet<ExecutionMode> __isis_executionMode() {
-            return mode;
+        public ImmutableEnumSet<ExecutionMode> __isis_executionModes() {
+            return modes;
         }
     }
 
@@ -70,7 +72,7 @@ public class WrapperFactoryDefaultTest {
     private WrapperFactoryDefault wrapperFactory;
 
     private DomainObject createProxyCalledWithDomainObject;
-    private ImmutableEnumSet<ExecutionMode> createProxyCalledWithMode;
+    private SyncControl createProxyCalledWithSyncControl;
 
     @Before
     public void setUp() throws Exception {
@@ -83,8 +85,8 @@ public class WrapperFactoryDefaultTest {
             }
             
             @Override
-            protected <T> T createProxy(T domainObject, ImmutableEnumSet<ExecutionMode> modes) {
-                WrapperFactoryDefaultTest.this.createProxyCalledWithMode = modes;
+            protected <T> T createProxy(T domainObject, SyncControl syncControl) {
+                WrapperFactoryDefaultTest.this.createProxyCalledWithSyncControl = syncControl;
                 WrapperFactoryDefaultTest.this.createProxyCalledWithDomainObject = (DomainObject) domainObject;
                 return domainObject;
             }
@@ -99,7 +101,7 @@ public class WrapperFactoryDefaultTest {
         wrapperFactory.wrap(domainObject);
 
         assertThat(createProxyCalledWithDomainObject, is(domainObject));
-        assertThat(createProxyCalledWithMode, is(ExecutionModes.EXECUTE));
+        assertThat(createProxyCalledWithSyncControl, is(not(nullValue())));
     }
 
 
@@ -107,10 +109,10 @@ public class WrapperFactoryDefaultTest {
     public void wrap_ofWrapped_sameMode_returnsUnchanged() throws Exception {
         // given
         final DomainObject wrappedObject = new DomainObject();
-        final DomainObject domainObject = new WrappedDomainObject(wrappedObject, ExecutionModes.EXECUTE);
+        final DomainObject domainObject = new WrappingDomainObject(wrappedObject, ImmutableEnumSet.noneOf(ExecutionMode.class));
 
         // when
-        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, ExecutionModes.EXECUTE);
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject);
 
         // then
         assertThat(wrappingObject, is(domainObject));
@@ -121,15 +123,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, ExecutionModes.EXECUTE);
+        final DomainObject domainObject = new WrappingDomainObject(wrappedObject, ImmutableEnumSet.noneOf(ExecutionMode.class));
 
         // when
-        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, ExecutionModes.SKIP_RULES);
+        final DomainObject wrappingObject = wrapperFactory.wrap(domainObject, SyncControl.create().withSkipRules());
 
         // then
         assertThat(wrappingObject, is(not(domainObject)));
         assertThat(createProxyCalledWithDomainObject, is(wrappedObject));
-        assertThat(createProxyCalledWithMode, is(ExecutionModes.SKIP_RULES));
+        Assertions.assertThat(createProxyCalledWithSyncControl.getExecutionModes()).contains(ExecutionMode.SKIP_RULE_VALIDATION);
     }
 
 }
\ No newline at end of file
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 57505b3..4feadca 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
@@ -42,7 +42,6 @@ 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;
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 1fd01a8..29e32b5 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
@@ -44,9 +44,7 @@ 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;
@@ -122,7 +120,9 @@ class WrapperAsyncTest {
         actionDomainEventListener.prepareLatch();
 
         // use of custom executor (optional)
-        val control = AsyncControl.toReturn(JdoProduct.class).with(ExecutionModes.EXECUTE).with(RuleCheckingPolicy.ASYNC).with(Executors.newCachedThreadPool());
+        val control = AsyncControl.toReturn(JdoProduct.class)
+                .with(ExecutionModes.EXECUTE)
+                .with(Executors.newCachedThreadPool());
         wrapper.async(inventoryManager, control)
                 .updateProductPrice(product, 123d);
 
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 7a62d58..20e72dc 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
@@ -44,7 +44,6 @@ 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;
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 7c5a69f..d499471 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
@@ -44,6 +44,7 @@ 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.wrapper.control.SyncControl;
 import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.commons.collections.ImmutableEnumSet;
 import org.apache.isis.core.commons.internal.base._Casts;
@@ -779,10 +780,10 @@ public abstract class FixtureScript {
     }
 
     /**
-     * Convenience method, simply delegates to {@link WrapperFactory#wrap(Object, EnumSet)}.
+     * Convenience method, simply delegates to {@link WrapperFactory#wrap(Object, SyncControl)}.
      */
-    protected <T> T wrap(final T domainObject, final ImmutableEnumSet<ExecutionMode> executionMode) {
-        return wrapperFactory.wrap(domainObject, executionMode);
+    protected <T> T wrap(final T domainObject, final SyncControl syncControl) {
+        return wrapperFactory.wrap(domainObject, syncControl);
     }
 
     /**