You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/12/09 14:08:12 UTC

[isis] branch master updated: ISIS-2158: some cleanup around domain event action handling

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

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new ad4a6a1  ISIS-2158: some cleanup around domain event action handling
ad4a6a1 is described below

commit ad4a6a1f251eea18765c812b0fcf5f6422489ec8
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Dec 9 15:08:03 2019 +0100

    ISIS-2158: some cleanup around domain event action handling
    
    - move to and contain with, the complicated type inference stuff with
    DomainEventHelper
---
 .../isis/metamodel/facets/DomainEventHelper.java   |  56 ++++-
 .../invocation/ActionDomainEventFacetAbstract.java |  85 +++----
 ...ctionInvocationFacetForDomainEventAbstract.java | 276 ++++++++++-----------
 ...ionFacetForDomainEventFromActionAnnotation.java |   8 +-
 ...onInvocationFacetForDomainEventFromDefault.java |   9 +-
 .../org/apache/isis/metamodel/util/EventUtil.java  |   1 +
 .../java/demoapp/dom/events/EventSubscriber.java   |   5 +-
 7 files changed, 221 insertions(+), 219 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
index b01260b..8630c74 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/DomainEventHelper.java
@@ -35,7 +35,7 @@ import org.apache.isis.applib.events.domain.PropertyDomainEvent;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
 import org.apache.isis.commons.internal.assertions._Assert;
-import org.apache.isis.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.metamodel.facetapi.IdentifiedHolder;
 import org.apache.isis.metamodel.services.events.MetamodelEventService;
 import org.apache.isis.metamodel.spec.ManagedObject;
@@ -44,6 +44,7 @@ import org.apache.isis.metamodel.spec.feature.ObjectActionParameter;
 
 import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
 
+import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import lombok.val;
 
@@ -57,19 +58,52 @@ public class DomainEventHelper {
 
     private final MetamodelEventService metamodelEventService;
 
-    // -- postEventForAction, newActionDomainEvent
+    // -- postEventForAction
+    
+    // variant using eventType and no existing event
+    public ActionDomainEvent<?> postEventForAction(
+            final AbstractDomainEvent.Phase phase,
+            @NonNull final Class<? extends ActionDomainEvent<?>> eventType,
+            final ObjectAction objectAction,
+            final IdentifiedHolder identified,
+            final ManagedObject targetAdapter,
+            final ManagedObject mixedInAdapter,
+            final ManagedObject[] argumentAdapters,
+            final Command command,
+            final ManagedObject resultAdapter) {
+        
+        return postEventForAction(phase, uncheckedCast(eventType), /*existingEvent*/null, objectAction, identified, 
+                targetAdapter, mixedInAdapter, argumentAdapters, command, resultAdapter);
+    }
+    
+    // variant using existing event and not eventType (is derived from event)
+    public ActionDomainEvent<?> postEventForAction(
+            final AbstractDomainEvent.Phase phase,
+            @NonNull final ActionDomainEvent<?> existingEvent,
+            final ObjectAction objectAction,
+            final IdentifiedHolder identified,
+            final ManagedObject targetAdapter,
+            final ManagedObject mixedInAdapter,
+            final ManagedObject[] argumentAdapters,
+            final Command command,
+            final ManagedObject resultAdapter) {
+        
+        return postEventForAction(phase, 
+                uncheckedCast(existingEvent.getClass()), existingEvent, objectAction, identified, 
+                targetAdapter, mixedInAdapter, argumentAdapters, command, resultAdapter);
+    }
 
-    public <S> ActionDomainEvent<S> postEventForAction(
+    private <S> ActionDomainEvent<S> postEventForAction(
             final AbstractDomainEvent.Phase phase,
             final Class<? extends ActionDomainEvent<S>> eventType,
-                    final ActionDomainEvent<S> existingEvent,
-                    final ObjectAction objectAction,
-                    final IdentifiedHolder identified,
-                    final ManagedObject targetAdapter,
-                    final ManagedObject mixedInAdapter,
-                    final ManagedObject[] argumentAdapters,
-                    final Command command,
-                    final ManagedObject resultAdapter) {
+            final ActionDomainEvent<S> existingEvent,
+            final ObjectAction objectAction,
+            final IdentifiedHolder identified,
+            final ManagedObject targetAdapter,
+            final ManagedObject mixedInAdapter,
+            final ManagedObject[] argumentAdapters,
+            final Command command,
+            final ManagedObject resultAdapter) {
         
         _Assert.assertTypeIsInstanceOf(eventType, ActionDomainEvent.class);
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
index 47c61fa..a07552e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionDomainEventFacetAbstract.java
@@ -27,10 +27,7 @@ import org.apache.isis.applib.services.wrapper.events.InteractionEvent;
 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.commons.internal.assertions._Assert;
-import org.apache.isis.commons.internal.base._Casts;
 import org.apache.isis.commons.internal.base._Tuples.Tuple2;
-import org.apache.isis.metamodel.facetapi.Facet;
 import org.apache.isis.metamodel.facetapi.FacetHolder;
 import org.apache.isis.metamodel.facetapi.IdentifiedHolder;
 import org.apache.isis.metamodel.facets.DomainEventHelper;
@@ -44,23 +41,23 @@ import org.apache.isis.metamodel.interactions.VisibilityContext;
 import org.apache.isis.metamodel.spec.ManagedObject;
 import org.apache.isis.metamodel.spec.feature.ObjectAction;
 
+import lombok.Getter;
+import lombok.Setter;
+
 public abstract class ActionDomainEventFacetAbstract
-extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
+extends SingleClassValueFacetAbstract 
+implements ActionDomainEventFacet {
 
-    private Class<? extends ActionDomainEvent<?>> eventType;
+    @Getter @Setter private Class<? extends ActionDomainEvent<?>> eventType;
     private final TranslationService translationService;
     private final String translationContext;
-
-    static Class<? extends Facet> type() {
-        return ActionDomainEventFacet.class;
-    }
-
     private final DomainEventHelper domainEventHelper;
 
     public ActionDomainEventFacetAbstract(
             final Class<? extends ActionDomainEvent<?>> eventType,
-                    final FacetHolder holder) {
-        super(type(), holder, eventType);
+            final FacetHolder holder) {
+        
+        super(ActionDomainEventFacet.class, holder, eventType);
         setEventType(eventType);
 
         this.translationService = getTranslationService();
@@ -74,28 +71,14 @@ extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
     public Class<?> value() {
         return eventType;
     }
-
-    protected Class<?> eventType() {
-        return eventType;
-    }
-
-    public <S> Class<? extends ActionDomainEvent<S>> getEventType() {
-        return _Casts.uncheckedCast(eventType);
-    }
-    public void setEventType(final Class<? extends ActionDomainEvent<?>> eventType) {
-        _Assert.assertTypeIsInstanceOf(eventType, ActionDomainEvent.class);
-        this.eventType = eventType;
-    }
-
-
-
+    
     @Override
     public String hides(final VisibilityContext<? extends VisibilityEvent> ic) {
 
         final ActionDomainEvent<?> event =
                 domainEventHelper.postEventForAction(
                         AbstractDomainEvent.Phase.HIDE,
-                        getEventType(), null,
+                        getEventType(),
                         actionFrom(ic), getIdentified(),
                         ic.getTarget(), ic.getMixedIn(), argumentAdaptersFrom(ic),
                         null,
@@ -112,7 +95,7 @@ extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
         final ActionDomainEvent<?> event =
                 domainEventHelper.postEventForAction(
                         AbstractDomainEvent.Phase.DISABLE,
-                        getEventType(), null,
+                        getEventType(),
                         actionFrom(ic), getIdentified(),
                         ic.getTarget(), ic.getMixedIn(), argumentAdaptersFrom(ic),
                         null,
@@ -128,26 +111,6 @@ extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
         return null;
     }
 
-    private static ObjectAction actionFrom(final InteractionContext<?> ic) {
-        if(!(ic instanceof ActionInteractionContext)) {
-            throw new IllegalStateException(
-                    "Expecting ic to be of type ActionInteractionContext, instead was: " + ic);
-        }
-        return ((ActionInteractionContext) ic).getObjectAction();
-    }
-
-    private static ManagedObject[] argumentAdaptersFrom(final InteractionContext<? extends InteractionEvent> ic) {
-        final Tuple2<Integer, ManagedObject> contributee = ic.getContributeeWithParamIndex();
-
-        if(contributee!=null) {
-            int paramIndex = contributee.get_1(); 
-            ManagedObject adapter = contributee.get_2();
-            return new ManagedObject[]{paramIndex==0 ? adapter : null};
-        }
-
-        return null;
-    }
-
     @Override
     public String invalidates(final ValidityContext<? extends ValidityEvent> ic) {
 
@@ -155,7 +118,7 @@ extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
         final ActionDomainEvent<?> event =
                 domainEventHelper.postEventForAction(
                         AbstractDomainEvent.Phase.VALIDATE,
-                        getEventType(), null,
+                        getEventType(),
                         actionFrom(ic), getIdentified(),
                         ic.getTarget(), ic.getMixedIn(), aic.getArgs(),
                         null,
@@ -170,5 +133,27 @@ extends SingleClassValueFacetAbstract implements ActionDomainEventFacet {
 
         return null;
     }
+    
+    // -- HELPER
+    
+    private static ObjectAction actionFrom(final InteractionContext<?> ic) {
+        if(!(ic instanceof ActionInteractionContext)) {
+            throw new IllegalStateException(
+                    "Expecting ic to be of type ActionInteractionContext, instead was: " + ic);
+        }
+        return ((ActionInteractionContext) ic).getObjectAction();
+    }
+
+    private static ManagedObject[] argumentAdaptersFrom(final InteractionContext<? extends InteractionEvent> ic) {
+        final Tuple2<Integer, ManagedObject> contributee = ic.getContributeeWithParamIndex();
+
+        if(contributee!=null) {
+            int paramIndex = contributee.get_1(); 
+            ManagedObject adapter = contributee.get_2();
+            return new ManagedObject[]{paramIndex==0 ? adapter : null};
+        }
+
+        return null;
+    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index 86d5e28..04b3901 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -21,7 +21,6 @@ package org.apache.isis.metamodel.facets.actions.action.invocation;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -42,6 +41,7 @@ import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.applib.services.command.spi.CommandService;
 import org.apache.isis.applib.services.iactn.Interaction;
+import org.apache.isis.applib.services.iactn.Interaction.ActionInvocation;
 import org.apache.isis.applib.services.iactn.InteractionContext;
 import org.apache.isis.applib.services.metamodel.MetaModelService;
 import org.apache.isis.applib.services.metamodel.MetaModelService.Mode;
@@ -70,29 +70,28 @@ import org.apache.isis.metamodel.spec.ObjectSpecification;
 import org.apache.isis.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.schema.ixn.v1.ActionInvocationDto;
 
-import static org.apache.isis.commons.internal.base._Casts.uncheckedCast;
-
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 
 public abstract class ActionInvocationFacetForDomainEventAbstract
 extends ActionInvocationFacetAbstract
 implements ImperativeFacet {
 
+    @Getter private final Class<? extends ActionDomainEvent<?>> eventType;
     private final Method method;
-    private final ObjectSpecification onType;
-    private final ObjectSpecification returnType;
-
+    @Getter(onMethod = @__(@Override)) private final ObjectSpecification onType;
+    @Getter(onMethod = @__(@Override)) private final ObjectSpecification returnType;
     private final ServiceRegistry serviceRegistry;
-
-    private final Class<? extends ActionDomainEvent<?>> eventType;
     private final DomainEventHelper domainEventHelper;
 
     public ActionInvocationFacetForDomainEventAbstract(
             final Class<? extends ActionDomainEvent<?>> eventType,
-                    final Method method,
-                    final ObjectSpecification onType,
-                    final ObjectSpecification returnType,
-                    final FacetHolder holder) {
+            final Method method,
+            final ObjectSpecification onType,
+            final ObjectSpecification returnType,
+            final FacetHolder holder) {
+        
         super(holder);
         this.eventType = eventType;
         this.method = method;
@@ -117,17 +116,6 @@ implements ImperativeFacet {
     }
 
     @Override
-    public ObjectSpecification getReturnType() {
-        return returnType;
-    }
-
-    @Override
-    public ObjectSpecification getOnType() {
-        return onType;
-    }
-
-
-    @Override
     public ManagedObject invoke(
             final ObjectAction owningAction,
             final ManagedObject targetAdapter,
@@ -137,14 +125,31 @@ implements ImperativeFacet {
 
         final ManagedObject executionResult = 
                 getTransactionService().executeWithinTransaction(()->
-                doInvoke(owningAction, targetAdapter, mixedInAdapter, argumentAdapters, interactionInitiatedBy));
+                    doInvoke(owningAction, targetAdapter, mixedInAdapter, argumentAdapters, 
+                            interactionInitiatedBy));
 
         //PersistableTypeGuard.instate(executionResult);
 
         return executionResult;
     }
 
-    ManagedObject doInvoke(
+    @Override 
+    public void appendAttributesTo(final Map<String, Object> attributeMap) {
+        super.appendAttributesTo(attributeMap);
+        ImperativeFacet.Util.appendAttributesTo(this, attributeMap);
+        attributeMap.put("onType", onType);
+        attributeMap.put("returnType", returnType);
+        attributeMap.put("eventType", eventType);
+    }
+    
+    @Override
+    protected String toStringValues() {
+        return "method=" + method;
+    }
+    
+    // -- HELPER
+    
+    private ManagedObject doInvoke(
             final ObjectAction owningAction,
             final ManagedObject targetAdapter,
             final ManagedObject mixedInAdapter,
@@ -192,92 +197,9 @@ implements ImperativeFacet {
                     new Interaction.ActionInvocation(interaction, actionId, mixinElseRegularPojo, argumentPojos, targetMember,
                             targetClass);
             final Interaction.MemberExecutor<Interaction.ActionInvocation> callable =
-                    new Interaction.MemberExecutor<Interaction.ActionInvocation>() {
-
-
-                @Override
-                public Object execute(final Interaction.ActionInvocation currentExecution) {
-
-
-                    try {
-
-                        // update the current execution with the DTO (memento)
-                        final ActionInvocationDto invocationDto =
-                                getInteractionDtoServiceInternal().asActionInvocationDto(
-                                        owningAction, mixinElseRegularAdapter, argumentAdapterList);
-                        currentExecution.setDto(invocationDto);
-
-
-                        // set the startedAt (and update command if this is the top-most member execution)
-                        // (this isn't done within Interaction#execute(...) because it requires the DTO
-                        // to have been set on the current execution).
-                        final Timestamp startedAt = getClockService().nowAsJavaSqlTimestamp();
-                        execution.setStartedAt(startedAt);
-                        if(command.getStartedAt() == null) {
-                            command.internal().setStartedAt(startedAt);
-                        }
-
-
-                        // ... post the executing event
-
-                        final ActionDomainEvent<?> event =
-                                domainEventHelper.postEventForAction(
-                                        AbstractDomainEvent.Phase.EXECUTING,
-                                        getEventType(), null,
-                                        owningAction, owningAction,
-                                        targetAdapter, mixedInAdapter, argumentAdapters,
-                                        command,
-                                        null);
-
-                        // set event onto the execution
-                        currentExecution.setEvent(event);
-
-                        // invoke method
-                        val resultPojo = invokeMethodElseFromCache(targetAdapter, argumentAdapters);
-                        ManagedObject resultAdapterPossiblyCloned = cloneIfViewModelCloneable(resultPojo, mixinElseRegularAdapter);
-
-                        // ... post the executed event
-
-                        //[ahuber] javac (jdk-8) won't compile this without the cast '(ActionDomainEvent)event', 
-                        // while the eclipse compiler does ... 
-                        domainEventHelper.postEventForAction(
-                                AbstractDomainEvent.Phase.EXECUTED,
-                                getEventType(), (ActionDomainEvent)event,
-                                owningAction, owningAction, targetAdapter, mixedInAdapter, argumentAdapters,
-                                command,
-                                resultAdapterPossiblyCloned);
-
-                        final Object returnValue = event.getReturnValue();
-                        if(returnValue != resultPojo) {
-                            resultAdapterPossiblyCloned = cloneIfViewModelCloneable(returnValue, mixinElseRegularAdapter);
-                        }
-                        return ManagedObject.unwrapPojo(resultAdapterPossiblyCloned);
-
-                    } catch (Exception e) {
-
-                        final Consumer<RecoverableException> recovery = recoverableException->{
-
-                            if (!getTransactionState().canCommit()) {
-                                // something severe has happened to the underlying transaction;
-                                // so escalate this exception to be non-recoverable
-                                final Throwable recoverableExceptionCause = recoverableException.getCause();
-                                Throwable nonRecoverableCause = recoverableExceptionCause != null
-                                        ? recoverableExceptionCause
-                                                : recoverableException;
-
-                                // trim to first 300 chars
-                                final String message = trim(nonRecoverableCause.getMessage(), 300);
-
-                                throw new NonRecoverableException(message, nonRecoverableCause);
-                            }
-                        };
-
-                        return ThrowableExtensions.handleInvocationException(e, method.getName(), recovery);
-                    }
-
-
-                }
-            };
+                    new DomainEventMemberExecutor(
+                            argumentAdapters, targetAdapter, argumentAdapterList, command, owningAction,
+                            mixinElseRegularAdapter, mixedInAdapter, execution);
 
             // sets up startedAt and completedAt on the execution, also manages the execution call graph
             interaction.execute(callable, execution);
@@ -296,7 +218,6 @@ implements ImperativeFacet {
                         : new RuntimeException(executionExceptionIfAny);
             }
 
-
             final Object returnedPojo = priorExecution.getReturned();
             returnedAdapter = getObjectManager().adapt(returnedPojo);
 
@@ -304,17 +225,13 @@ implements ImperativeFacet {
             getInteractionDtoServiceInternal()
             .updateResult(priorExecution.getDto(), owningAction, returnedPojo);
 
-
             // update Command (if required)
             setCommandResultIfEntity(command, returnedAdapter);
 
             // publish (if not a contributed association, query-only mixin)
             final PublishedActionFacet publishedActionFacet = getIdentified().getFacet(PublishedActionFacet.class);
             if (publishedActionFacet != null) {
-
-                getPublishingServiceInternal().publishAction(
-                        priorExecution
-                        );
+                getPublishingServiceInternal().publishAction(priorExecution);
             }
         }
 
@@ -337,7 +254,7 @@ implements ImperativeFacet {
         return message;
     }
 
-    protected Object invokeMethodElseFromCache(
+    private Object invokeMethodElseFromCache(
             final ManagedObject targetAdapter, final ManagedObject[] arguments)
                     throws IllegalAccessException, InvocationTargetException {
 
@@ -362,7 +279,7 @@ implements ImperativeFacet {
         }
     }
 
-    protected ManagedObject cloneIfViewModelCloneable(
+    private ManagedObject cloneIfViewModelCloneable(
             final Object resultPojo,
             final ManagedObject targetAdapter) {
 
@@ -391,7 +308,7 @@ implements ImperativeFacet {
     }
 
 
-    protected void setCommandResultIfEntity(final Command command, final ManagedObject resultAdapter) {
+    private void setCommandResultIfEntity(final Command command, final ManagedObject resultAdapter) {
         if(command.getResult() != null) {
             // don't trample over any existing result, eg subsequent mixins.
             return;
@@ -429,7 +346,7 @@ implements ImperativeFacet {
         return serviceRegistry.lookupServiceElseFail(BookmarkService.class);
     }
 
-    protected ManagedObject filteredIfRequired(
+    private ManagedObject filteredIfRequired(
             final ManagedObject resultAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
 
@@ -471,28 +388,14 @@ implements ImperativeFacet {
         }
     }
 
-    public <S> Class<? extends ActionDomainEvent<S>> getEventType() {
-        return uncheckedCast(eventType);
-    }
-
     private static Object unwrap(final ManagedObject adapter) {
         return adapter == null ? null : adapter.getPojo();
     }
-
-    @Override
-    protected String toStringValues() {
-        return "method=" + method;
-    }
-
-
-
-    // /////////////////////////////////////////////////////////
-    // Dependencies (looked up)
-    // /////////////////////////////////////////////////////////
-
+    
     private CommandContext getCommandContext() {
         return serviceRegistry.lookupServiceElseFail(CommandContext.class);
     }
+    
     private InteractionContext getInteractionContext() {
         return serviceRegistry.lookupServiceElseFail(InteractionContext.class);
     }
@@ -516,18 +419,99 @@ implements ImperativeFacet {
     private InteractionDtoServiceInternal getInteractionDtoServiceInternal() {
         return serviceRegistry.lookupServiceElseFail(InteractionDtoServiceInternal.class);
     }
+    
+    @RequiredArgsConstructor
+    private final class DomainEventMemberExecutor 
+    implements Interaction.MemberExecutor<Interaction.ActionInvocation> {
+        
+        private final ManagedObject[] argumentAdapters;
+        private final ManagedObject targetAdapter;
+        private final List<ManagedObject> argumentAdapterList;
+        private final Command command;
+        private final ObjectAction owningAction;
+        private final ManagedObject mixinElseRegularAdapter;
+        private final ManagedObject mixedInAdapter;
+        private final ActionInvocation execution;
+
+        @Override
+        public Object execute(final Interaction.ActionInvocation currentExecution) {
+
+            try {
+
+                // update the current execution with the DTO (memento)
+                val invocationDto = getInteractionDtoServiceInternal()
+                .asActionInvocationDto(owningAction, mixinElseRegularAdapter, argumentAdapterList);
+                
+                currentExecution.setDto(invocationDto);
+
+
+                // set the startedAt (and update command if this is the top-most member execution)
+                // (this isn't done within Interaction#execute(...) because it requires the DTO
+                // to have been set on the current execution).
+                val startedAt = getClockService().nowAsJavaSqlTimestamp();
+                execution.setStartedAt(startedAt);
+                if(command.getStartedAt() == null) {
+                    command.internal().setStartedAt(startedAt);
+                }
 
-    // /////////////////////////////////////////////////////////
-    // Dependencies (from constructor)
-    // /////////////////////////////////////////////////////////
+                // ... post the executing event
+                //compiler: cannot use val here, because initializer expression does not have a representable type
+                final ActionDomainEvent<?> actionDomainEvent = domainEventHelper.postEventForAction(
+                        AbstractDomainEvent.Phase.EXECUTING,
+                        getEventType(),
+                        owningAction, owningAction,
+                        targetAdapter, mixedInAdapter, argumentAdapters,
+                        command,
+                        null);
+
+                // set event onto the execution
+                currentExecution.setEvent(actionDomainEvent);
+
+                // invoke method
+                val resultPojo = invokeMethodElseFromCache(targetAdapter, argumentAdapters);
+                ManagedObject resultAdapterPossiblyCloned = 
+                        cloneIfViewModelCloneable(resultPojo, mixinElseRegularAdapter);
+
+                // ... post the executed event
+
+                domainEventHelper.postEventForAction(
+                        AbstractDomainEvent.Phase.EXECUTED,
+                        actionDomainEvent,
+                        owningAction, owningAction, targetAdapter, mixedInAdapter, argumentAdapters,
+                        command,
+                        resultAdapterPossiblyCloned);
+
+                final Object returnValue = actionDomainEvent.getReturnValue();
+                if(returnValue != resultPojo) {
+                    resultAdapterPossiblyCloned = 
+                            cloneIfViewModelCloneable(returnValue, mixinElseRegularAdapter);
+                }
+                return ManagedObject.unwrapPojo(resultAdapterPossiblyCloned);
 
+            } catch (Exception e) {
 
-    @Override public void appendAttributesTo(final Map<String, Object> attributeMap) {
-        super.appendAttributesTo(attributeMap);
-        ImperativeFacet.Util.appendAttributesTo(this, attributeMap);
-        attributeMap.put("onType", onType);
-        attributeMap.put("returnType", returnType);
-        attributeMap.put("eventType", eventType);
+                final Consumer<RecoverableException> recovery = recoverableException->{
+
+                    if (!getTransactionState().canCommit()) {
+                        // something severe has happened to the underlying transaction;
+                        // so escalate this exception to be non-recoverable
+                        final Throwable recoverableExceptionCause = recoverableException.getCause();
+                        Throwable nonRecoverableCause = recoverableExceptionCause != null
+                                ? recoverableExceptionCause
+                                        : recoverableException;
+
+                        // trim to first 300 chars
+                        final String message = trim(nonRecoverableCause.getMessage(), 300);
+
+                        throw new NonRecoverableException(message, nonRecoverableCause);
+                    }
+                };
+
+                return ThrowableExtensions.handleInvocationException(e, method.getName(), recovery);
+            }
+
+
+        }
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromActionAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromActionAnnotation.java
index a689868..b03b081 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromActionAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromActionAnnotation.java
@@ -30,10 +30,10 @@ extends ActionInvocationFacetForDomainEventAbstract {
 
     public ActionInvocationFacetForDomainEventFromActionAnnotation(
             final Class<? extends ActionDomainEvent<?>> eventType,
-                    final Method method,
-                    final ObjectSpecification onType,
-                    final ObjectSpecification returnType,
-                    final FacetHolder holder) {
+            final Method method,
+            final ObjectSpecification onType,
+            final ObjectSpecification returnType,
+            final FacetHolder holder) {
 
         super(eventType, method, onType, returnType, holder);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromDefault.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromDefault.java
index 3e505e7..f4b67d6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventFromDefault.java
@@ -30,10 +30,11 @@ extends ActionInvocationFacetForDomainEventAbstract {
 
     public ActionInvocationFacetForDomainEventFromDefault(
             final Class<? extends ActionDomainEvent<?>> eventType,
-                    final Method method,
-                    final ObjectSpecification onType,
-                    final ObjectSpecification returnType,
-                    final FacetHolder holder) {
+            final Method method,
+            final ObjectSpecification onType,
+            final ObjectSpecification returnType,
+            final FacetHolder holder) {
+        
         super(eventType, method, onType, returnType, holder);
     }
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/util/EventUtil.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/util/EventUtil.java
index f777034..83c4e73 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/util/EventUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/util/EventUtil.java
@@ -29,6 +29,7 @@ public final class EventUtil {
             final Class<? extends T> noopClass,
             final Class<? extends T> defaultClass,
             final boolean configurationPropertyValue) {
+        
         if (noopClass.isAssignableFrom(eventType)) {
             return false;
         }
diff --git a/examples/demo/src/main/java/demoapp/dom/events/EventSubscriber.java b/examples/demo/src/main/java/demoapp/dom/events/EventSubscriber.java
index a3abb62..e7f684e 100644
--- a/examples/demo/src/main/java/demoapp/dom/events/EventSubscriber.java
+++ b/examples/demo/src/main/java/demoapp/dom/events/EventSubscriber.java
@@ -24,14 +24,11 @@ import javax.inject.Named;
 
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.context.event.EventListener;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
 
-import org.apache.isis.applib.annotation.DomainService;
-import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.events.domain.AbstractDomainEvent;
 import org.apache.isis.applib.services.eventbus.EventBusService;
 import org.apache.isis.applib.services.wrapper.WrapperFactory;
-import org.springframework.stereotype.Service;
 
 import static demoapp.utils.DemoUtils.emphasize;