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 2016/04/22 11:47:48 UTC

[13/14] isis git commit: ISIS-1374: moved setupXxx functionality out of ActionInvocationFacet, and into ObjectActionDefault. Removed duplication with ObjectActionMixedIn and ObjectActionContributee.

ISIS-1374: moved setupXxx functionality out of ActionInvocationFacet, and into ObjectActionDefault.  Removed duplication with ObjectActionMixedIn and ObjectActionContributee.

Also:
- removed unnecessary guards for CommandContext or Command being null (they never will)
- removed (after all) the idea of CommandMento


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/46089a20
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/46089a20
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/46089a20

Branch: refs/heads/ISIS-1291
Commit: 46089a2037e829f1b387a78b17f2e3a1d73cc923
Parents: 6da1e31
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Fri Apr 22 09:34:20 2016 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Fri Apr 22 09:34:20 2016 +0100

----------------------------------------------------------------------
 ...onInvocationFacetForDomainEventAbstract.java | 525 +++++++++----------
 .../PropertySetterFacetViaModifyMethod.java     |  36 +-
 .../services/command/CommandMementoService.java |   4 +-
 .../metamodel/spec/feature/ObjectAction.java    |  18 +-
 .../specimpl/ObjectActionContributee.java       |  55 +-
 .../specimpl/ObjectActionDefault.java           | 190 ++++++-
 .../specimpl/ObjectActionMixedIn.java           | 109 +---
 .../background/BackgroundServiceDefault.java    |  18 +-
 8 files changed, 504 insertions(+), 451 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index 99f0ca4..bac5458 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -34,18 +34,18 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.isis.applib.NonRecoverableException;
 import org.apache.isis.applib.RecoverableException;
-import org.apache.isis.applib.annotation.Bulk;
-import org.apache.isis.applib.annotation.InvokedOn;
 import org.apache.isis.applib.clock.Clock;
-import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
-import org.apache.isis.applib.services.background.ActionInvocationMemento;
 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.spi.CommandService;
 import org.apache.isis.applib.services.eventbus.AbstractDomainEvent;
 import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
+import org.apache.isis.applib.services.metamodel.MetaModelService2;
 import org.apache.isis.applib.services.queryresultscache.QueryResultsCache;
+import org.apache.isis.applib.services.repository.RepositoryService;
+import org.apache.isis.applib.services.xactn.TransactionService;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
 import org.apache.isis.core.commons.config.IsisConfiguration;
@@ -62,21 +62,16 @@ import org.apache.isis.core.metamodel.facets.DomainEventHelper;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.ElementSpecificationProviderFromTypeOfFacet;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
-import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
-import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.publish.PublishedActionFacet;
 import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFacet;
 import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.viewmodel.ViewModelFacet;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
-import org.apache.isis.core.metamodel.services.command.CommandMementoService;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.specloader.ReflectiveActionException;
 import org.apache.isis.core.metamodel.transactions.TransactionState;
 import org.apache.isis.core.metamodel.transactions.TransactionStateProvider;
-import org.apache.isis.schema.cmd.v1.CommandMementoDto;
-import org.apache.isis.schema.utils.CommandMementoDtoUtils;
 
 public abstract class ActionInvocationFacetForDomainEventAbstract
         extends ActionInvocationFacetAbstract
@@ -196,7 +191,7 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             final InteractionInitiatedBy interactionInitiatedBy) {
 
         final CommandContext commandContext = getCommandContext();
-        final Command command = commandContext != null ? commandContext.getCommand() : null;
+        final Command command = commandContext.getCommand();
 
         // ... post the executing event
         final ActionDomainEvent<?> event =
@@ -207,214 +202,55 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
                         command,
                         null);
 
-        // ... invoke the action
-        InvocationResult result1;
-
-        try {
-
-            final Object[] executionParameters = new Object[arguments.length];
-            for (int i = 0; i < arguments.length; i++) {
-                executionParameters[i] = unwrap(arguments[i]);
-            }
-
-            final Object targetPojo = unwrap(targetAdapter);
-
-            final BulkFacet bulkFacet = getFacetHolder().getFacet(BulkFacet.class);
-            if (bulkFacet != null) {
-                final ActionInvocationContext actionInvocationContext = getActionInvocationContext();
-                if (actionInvocationContext != null &&
-                    actionInvocationContext.getInvokedOn() == null) {
-
-                    actionInvocationContext.setInvokedOn(InvokedOn.OBJECT);
-                    actionInvocationContext.setDomainObjects(Collections.singletonList(targetPojo));
-                }
-
-                final Bulk.InteractionContext bulkInteractionContext = getBulkInteractionContext();
-
-                if (bulkInteractionContext != null &&
-                        bulkInteractionContext.getInvokedAs() == null) {
-
-                    bulkInteractionContext.setInvokedAs(Bulk.InteractionContext.InvokedAs.REGULAR);
-                    actionInvocationContext.setDomainObjects(Collections.singletonList(targetPojo));
-                }
-            }
-
-            if(command != null && command.getExecutor() == Command.Executor.USER && owningAction != null) {
-
-                if(command.getTarget() != null) {
-                    // already set up by a ObjectActionContributee or edit form;
-                    // don't overwrite
-                } else {
-                    command.setTargetClass(CommandUtil.targetClassNameFor(targetAdapter));
-                    command.setTargetAction(CommandUtil.targetActionNameFor(owningAction));
-                    command.setArguments(CommandUtil.argDescriptionFor(owningAction, arguments));
-
-                    final Bookmark targetBookmark = CommandUtil.bookmarkFor(targetAdapter);
-                    command.setTarget(targetBookmark);
-                }
-
-                if(Command.ACTION_IDENTIFIER_FOR_EDIT.equals(command.getMemberIdentifier())) {
-                    // special case for edit properties
-                } else {
-
-                    if(command.getMemberIdentifier() == null) {
-                        // any contributed/mixin actions will fire after the main action
-                        // the guard here prevents them from trashing the command's memberIdentifier
-                        command.setMemberIdentifier(CommandUtil.actionIdentifierFor(owningAction));
-                    }
-
-                    if(command.getMemento() == null) {
-                        // similarly, guard here to deal with subsequent or prior contributed/mixin actions.
-
-                        final CommandMementoService commandMementoService = getCommandMementoService();
-
-                        final Object targetObject = unwrap(targetAdapter);
-                        final Object[] args = CommandUtil.objectsFor(arguments);
-
-                        final CommandMementoDto dto = commandMementoService.asCommandMemento(
-                                Collections.singletonList(targetAdapter),
-                                owningAction, arguments);
-
-                        if(dto != null) {
-                            // the default implementation will always return a dto.  The guard is to allow
-                            // for the default implementation to be replaced with a version that returns null
-                            // allowing a fallback to the original API (using ActionInvocationMemento rather than
-                            // CommandMementoDto).
-                            final String mementoXml = CommandMementoDtoUtils.toXml(dto);
-                            command.setMemento(mementoXml);
-                        } else {
-
-                            // fallback to old behaviour
-                            ActionInvocationMemento aim = commandMementoService
-                                    .asActionInvocationMemento(method, targetObject, args);
-                            if(aim != null) {
-                                command.setMemento(aim.asMementoString());
-
-                            } else {
-                                String actionIdentifier = owningAction.getIdentifier().toClassAndNameIdentityString();
-                                throw new IsisException(
-                                        "Unable to build memento for action " + actionIdentifier);
-                            }
-                        }
-                    }
-                }
-
-                // copy over the command execution 'context' (if available)
-                final CommandFacet commandFacet = getFacetHolder().getFacet(CommandFacet.class);
-                if(commandFacet != null && !commandFacet.isDisabled()) {
-                    command.setExecuteIn(commandFacet.executeIn());
-                    command.setPersistence(commandFacet.persistence());
-                } else {
-                    // if no facet, assume do want to execute right now, but only persist (eventually) if hinted.
-                    command.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.FOREGROUND);
-                    command.setPersistence(org.apache.isis.applib.annotation.Command.Persistence.IF_HINTED);
-                }
-            }
+        final InvocationResult invocationResult = invoke(owningAction, targetAdapter, arguments);
 
+        final ObjectAdapter invocationResultAdapter = invocationResult.getAdapter();
 
-            ObjectAdapter resultAdapter;
+        // ... post the executed event
+        if (invocationResult.getWhetherInvoked()) {
+            // perhaps the Action was not properly invoked (i.e. an exception was raised).
+            // If invoked ok, then post to the event bus
+            domainEventHelper.postEventForAction(
+                    AbstractDomainEvent.Phase.EXECUTED,
+                    eventType, verify(event),
+                    owningAction, targetAdapter, arguments,
+                    command,
+                    invocationResultAdapter);
+        }
 
-            if( command != null &&
-                command.getExecutor() == Command.Executor.USER &&
-                command.getExecuteIn() == org.apache.isis.applib.annotation.Command.ExecuteIn.BACKGROUND) {
+        if (invocationResultAdapter == null) {
+            return null;
+        }
 
-                // deal with background commands
+        return filteredIfRequired(invocationResultAdapter, interactionInitiatedBy);
 
-                // persist command so can be this command can be in the 'background'
-                final CommandService commandService = getCommandService();
-                if (!commandService.persistIfPossible(command)) {
-                    throw new IsisException(
-                            "Unable to schedule action '"
-                                    + owningAction.getIdentifier().toClassAndNameIdentityString() + "' to run in background: "
-                                    + "CommandService does not support persistent commands " );
-                }
-                resultAdapter = getAdapterManager().adapterFor(command);
+    }
 
-            } else {
+    protected InvocationResult invoke(
+            final ObjectAction owningAction,
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments) {
 
-                // otherwise, go ahead and execute action in the 'foreground'
-                if(command != null) {
-                    command.setStartedAt(Clock.getTimeAsJavaSqlTimestamp());
-                }
+        final CommandContext commandContext = getCommandContext();
+        final Command command = commandContext.getCommand();
 
-                final ActionSemanticsFacet semanticsFacet = getFacetHolder().getFacet(ActionSemanticsFacet.class);
-                final boolean cacheable = semanticsFacet != null && semanticsFacet.value().isSafeAndRequestCacheable();
-
-                Object result11;
-                if(cacheable) {
-                    final QueryResultsCache queryResultsCache = getQueryResultsCache();
-                    final Object[] targetPojoPlusExecutionParameters = ArrayExtensions.appendT(executionParameters, targetPojo);
-                    result11 = queryResultsCache.execute(new Callable<Object>() {
-                        @Override
-                        public Object call() throws Exception {
-                            return method.invoke(targetPojo, executionParameters);
-                        }
-                    }, targetPojo.getClass(), method.getName(), targetPojoPlusExecutionParameters);
-                } else {
-                    result11 = method.invoke(targetPojo, executionParameters);
-                }
+        try {
+            setupActionInvocationContext(owningAction, targetAdapter);
 
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug(" action result " + result11);
-                }
-                if (result11 == null) {
-
-                    if(targetAdapter.getSpecification().isViewModelCloneable(targetAdapter)) {
-                        // if this was a void method on cloneable view model, then (to save boilerplate in the domain)
-                        // automatically do the clone and return the clone instead.
-                        final ViewModelFacet facet1 = targetAdapter.getSpecification().getFacet(ViewModelFacet.class);
-                        final Object clone = facet1.clone(targetAdapter.getObject());
-                        final ObjectAdapter clonedAdapter = getAdapterManager().adapterFor(clone);
-
-                        resultAdapter = clonedAdapter;
-
-                    } else {
-                        resultAdapter = null;
-                    }
-
-                } else {
-
-                    resultAdapter = getAdapterManager().adapterFor(result11);
-
-                    if(resultAdapter.getSpecification().isViewModelCloneable(resultAdapter)) {
-                        // if the object returned is cloneable, then
-                        // (to save boilerplate in the domain) automatically do the clone.
-                        final ViewModelFacet facet1 = resultAdapter.getSpecification().getFacet(ViewModelFacet.class);
-                        result11 = facet1.clone(result11);
-                        resultAdapter = getAdapterManager().adapterFor(result11);
-                    }
-
-
-                    // copy over TypeOfFacet if required
-                    final TypeOfFacet typeOfFacet = getFacetHolder().getFacet(TypeOfFacet.class);
-                    resultAdapter.setElementSpecificationProvider(ElementSpecificationProviderFromTypeOfFacet.createFrom(typeOfFacet));
-
-                    if(command != null) {
-                        if(!resultAdapter.getSpecification().containsDoOpFacet(ViewModelFacet.class)) {
-                            final Bookmark bookmark = CommandUtil.bookmarkFor(resultAdapter);
-                            command.setResult(bookmark);
-                        }
-                    }
-
-                    if(currentInvocation.get() == null) {
-                        final PublishedActionFacet publishedActionFacet = getIdentified().getFacet(PublishedActionFacet.class);
-                        if(publishedActionFacet != null) {
-                            currentInvocation.set(new CurrentInvocation(targetAdapter, owningAction, getIdentified(),
-                                    arguments, resultAdapter, command));
-                        }
-                    }
-                }
+            owningAction.setupCommand(targetAdapter, arguments);
 
-            }
+            ObjectAdapter resultAdapter = invokeThruCommand(owningAction, targetAdapter, arguments, command);
 
-            result1 = InvocationResult.forActionThatReturned(resultAdapter);
+            return InvocationResult.forActionThatReturned(resultAdapter);
 
         } catch (final IllegalArgumentException e) {
             throw e;
         } catch (final InvocationTargetException e) {
             final Throwable targetException = e.getTargetException();
             if (targetException instanceof IllegalStateException) {
-                throw new ReflectiveActionException("IllegalStateException thrown while executing " + method + " " + targetException.getMessage(), targetException);
+                throw new ReflectiveActionException( String.format(
+                        "IllegalStateException thrown while executing %s %s",
+                        method, targetException.getMessage()), targetException);
             }
             if(targetException instanceof RecoverableException) {
                 if (!getTransactionState().canCommit()) {
@@ -424,13 +260,7 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
                     Throwable nonRecoverableCause = targetExceptionCause != null? targetExceptionCause: targetException;
 
                     // trim to first 300 chars
-                    String message = nonRecoverableCause.getMessage();
-                    if(!Strings.isNullOrEmpty(message)) {
-                        message = message.substring(0, Math.min(message.length(), 300));
-                        if(message.length() == 300) {
-                            message += " ...";
-                        }
-                    }
+                    final String message = trim(nonRecoverableCause.getMessage(), 300);
 
                     throw new NonRecoverableException(message, nonRecoverableCause);
                 }
@@ -439,63 +269,236 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
             ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method);
 
             // Action was not invoked (an Exception was thrown)
-            result1 = InvocationResult.forActionNotInvoked();
+            return InvocationResult.forActionNotInvoked();
+
         } catch (final IllegalAccessException e) {
             throw new ReflectiveActionException("Illegal access of " + method, e);
         }
-        final InvocationResult invocationResult = result1;
-        final ObjectAdapter invocationResultAdapter = invocationResult.getAdapter();
+    }
 
-        // ... post the executed event
-        if (invocationResult.getWhetherInvoked()) {
-            // perhaps the Action was not properly invoked (i.e. an exception was raised).
-            // If invoked ok, then post to the event bus
-            domainEventHelper.postEventForAction(
-                    AbstractDomainEvent.Phase.EXECUTED,
-                    eventType, verify(event),
-                    owningAction, targetAdapter, arguments,
-                    command,
-                    invocationResultAdapter);
+    private static String trim(String message, final int maxLen) {
+        if(!Strings.isNullOrEmpty(message)) {
+            message = message.substring(0, Math.min(message.length(), maxLen));
+            if(message.length() == maxLen) {
+                message += " ...";
+            }
         }
+        return message;
+    }
 
-        if (invocationResultAdapter == null) {
-            return null;
+    protected void setupActionInvocationContext(
+            final ObjectAction owningAction,
+            final ObjectAdapter targetAdapter) {
+
+        owningAction.setupActionInvocationContext(targetAdapter);
+    }
+
+    protected ObjectAdapter invokeThruCommand(
+            final ObjectAction owningAction,
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments, final Command command)
+            throws IllegalAccessException, InvocationTargetException {
+        final ObjectAdapter resultAdapter;
+        if( command.getExecutor() == Command.Executor.USER &&
+                command.getExecuteIn() == org.apache.isis.applib.annotation.Command.ExecuteIn.BACKGROUND) {
+
+            // deal with background commands
+
+            // persist command so can be this command can be in the 'background'
+            final CommandService commandService = getCommandService();
+            if (!commandService.persistIfPossible(command)) {
+                throw new IsisException(
+                        "Unable to schedule action '"
+                                + owningAction.getIdentifier().toClassAndNameIdentityString() + "' to run in background: "
+                                + "CommandService does not support persistent commands " );
+            }
+            resultAdapter = getAdapterManager().adapterFor(command);
+
+        } else {
+
+            // otherwise, go ahead and execute action in the 'foreground'
+            command.setStartedAt(Clock.getTimeAsJavaSqlTimestamp());
+
+            final Object resultPojo = invokeMethodElseFromCache(targetAdapter, arguments);
+
+            if (LOG.isDebugEnabled()) {
+                LOG.debug(" action result " + resultPojo);
+            }
+
+            resultAdapter = cloneIfViewModelCloneable(resultPojo, targetAdapter);
+
+            setCommandResultIfEntity(command, resultAdapter);
+            captureCurrentInvocationForPublishing(owningAction, targetAdapter, arguments, command, resultAdapter);
         }
+        return resultAdapter;
+    }
 
-        boolean filterForVisibility = getConfiguration().getBoolean("isis.reflector.facet.filterVisibility", true);
-        if(filterForVisibility) {
-            final Object result = invocationResultAdapter.getObject();
-            if(result instanceof Collection || result.getClass().isArray()) {
-                final CollectionFacet facet = CollectionFacet.Utils.getCollectionFacetFromSpec(invocationResultAdapter);
-
-                final Iterable<ObjectAdapter> adapterList = facet.iterable(invocationResultAdapter);
-                final List<ObjectAdapter> visibleAdapters =
-                        ObjectAdapter.Util.visibleAdapters(
-                                adapterList,
-                                interactionInitiatedBy);
-                final Object visibleObjects =
-                        CollectionUtils.copyOf(
-                                Lists.transform(visibleAdapters, ObjectAdapter.Functions.getObject()),
-                                method.getReturnType());
-                if (visibleObjects != null) {
-                    return getAdapterManager().adapterFor(visibleObjects);
-                }
-                // would be null if unable to take a copy (unrecognized return type)
-                // fallback to returning the original adapter, without filtering for visibility
-
-            } else {
-                boolean visible =
-                        ObjectAdapter.Util.isVisible(
-                                invocationResultAdapter,
-                                interactionInitiatedBy);
-                if(!visible) {
-                    return null;
+
+    protected Object invokeMethodElseFromCache(
+            final ObjectAdapter targetAdapter, final ObjectAdapter[] arguments)
+            throws IllegalAccessException, InvocationTargetException {
+
+        final Object[] executionParameters = new Object[arguments.length];
+        for (int i = 0; i < arguments.length; i++) {
+            executionParameters[i] = unwrap(arguments[i]);
+        }
+
+        final Object targetPojo = unwrap(targetAdapter);
+
+        final ActionSemanticsFacet semanticsFacet = getFacetHolder().getFacet(ActionSemanticsFacet.class);
+        final boolean cacheable = semanticsFacet != null && semanticsFacet.value().isSafeAndRequestCacheable();
+        if(cacheable) {
+            final QueryResultsCache queryResultsCache = getQueryResultsCache();
+            final Object[] targetPojoPlusExecutionParameters = ArrayExtensions.appendT(executionParameters, targetPojo);
+            return queryResultsCache.execute(new Callable<Object>() {
+                @Override
+                public Object call() throws Exception {
+                    return method.invoke(targetPojo, executionParameters);
                 }
+            }, targetPojo.getClass(), method.getName(), targetPojoPlusExecutionParameters);
+
+        } else {
+            return method.invoke(targetPojo, executionParameters);
+        }
+    }
+
+    protected ObjectAdapter cloneIfViewModelCloneable(
+            final Object resultPojo,
+            final ObjectAdapter targetAdapter) {
+
+        // to remove boilerplate from the domain, we automatically clone the returned object if it is a view model.
+
+        if (resultPojo != null) {
+            final ObjectAdapter resultAdapter = getAdapterManager().adapterFor(resultPojo);
+            return cloneIfViewModelElse(resultAdapter, resultAdapter);
+        } else {
+            // if void or null, attempt to clone the original target, else return null.
+            return cloneIfViewModelElse(targetAdapter, null);
+        }
+    }
+
+    private ObjectAdapter cloneIfViewModelElse(final ObjectAdapter adapter, final ObjectAdapter dfltAdapter) {
+
+        if (!adapter.getSpecification().isViewModelCloneable(adapter)) {
+            return  dfltAdapter;
+        }
+
+        final ViewModelFacet viewModelFacet = adapter.getSpecification().getFacet(ViewModelFacet.class);
+        final Object clone = viewModelFacet.clone(adapter.getObject());
+
+        final ObjectAdapter clonedAdapter = getAdapterManager().adapterFor(clone);
+
+        // copy over TypeOfFacet if required
+        final TypeOfFacet typeOfFacet = getFacetHolder().getFacet(TypeOfFacet.class);
+        clonedAdapter.setElementSpecificationProvider(ElementSpecificationProviderFromTypeOfFacet.createFrom(typeOfFacet));
+
+        return clonedAdapter;
+    }
+
+
+    protected void setCommandResultIfEntity(final Command command, final ObjectAdapter resultAdapter) {
+        if(command.getResult() != null) {
+            // don't trample over any existing result, eg subsequent mixins.
+            return;
+        }
+        if (resultAdapter == null) {
+            return;
+        }
+
+        final Class<?> domainType = resultAdapter.getSpecification().getCorrespondingClass();
+        final MetaModelService2.Sort sort = getMetaModelService().sortOf(domainType);
+        switch (sort) {
+        case JDO_ENTITY:
+            final Object domainObject = resultAdapter.getObject();
+            // ensure that any still-to-be-persisted adapters get persisted to DB.
+            if(!getRepositoryService().isPersistent(domainObject)) {
+                getTransactionService().flushTransaction();
             }
+            if(getRepositoryService().isPersistent(domainObject)) {
+                BookmarkService bookmarkService = getBookmarkService();
+                Bookmark bookmark = bookmarkService.bookmarkFor(domainObject);
+               command.setResult(bookmark);
+            }
+            break;
+        default:
+            // ignore all other sorts of objects
+            break;
+        }
+    }
+
+    private MetaModelService2 getMetaModelService() {
+        return lookupService(MetaModelService2.class);
+    }
+
+    private TransactionService getTransactionService() {
+        return lookupService(TransactionService.class);
+    }
+
+    private BookmarkService getBookmarkService() {
+        return lookupService(BookmarkService.class);
+    }
+
+    private RepositoryService getRepositoryService() {
+        return lookupService(RepositoryService.class);
+    }
+
+    protected void captureCurrentInvocationForPublishing(
+            final ObjectAction owningAction,
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments,
+            final Command command,
+            final ObjectAdapter resultAdapter) {
+
+        // TODO: should instead be using the top-level ActionInvocationMemento associated with command.
+
+        final PublishedActionFacet publishedActionFacet = getIdentified().getFacet(PublishedActionFacet.class);
+        if (publishedActionFacet != null && currentInvocation.get() == null) {
+            final CurrentInvocation currentInvocation = new CurrentInvocation(
+                    targetAdapter, owningAction, getIdentified(),
+                    arguments, resultAdapter, command);
+            ActionInvocationFacet.currentInvocation.set(currentInvocation);
+        }
+    }
+
+    protected ObjectAdapter filteredIfRequired(
+            final ObjectAdapter resultAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+
+        final boolean filterForVisibility = getConfiguration().getBoolean("isis.reflector.facet.filterVisibility", true);
+        if (!filterForVisibility) {
+            return resultAdapter;
+        }
+
+        final Object result = resultAdapter.getObject();
+
+        if(result instanceof Collection || result.getClass().isArray()) {
+            final CollectionFacet facet = CollectionFacet.Utils.getCollectionFacetFromSpec(resultAdapter);
+
+            final Iterable<ObjectAdapter> adapterList = facet.iterable(resultAdapter);
+            final List<ObjectAdapter> visibleAdapters =
+                    ObjectAdapter.Util.visibleAdapters(
+                            adapterList,
+                            interactionInitiatedBy);
+            final Object visibleObjects =
+                    CollectionUtils.copyOf(
+                            Lists.transform(visibleAdapters, ObjectAdapter.Functions.getObject()),
+                            method.getReturnType());
+            if (visibleObjects != null) {
+                return getAdapterManager().adapterFor(visibleObjects);
+            }
+
+            // would be null if unable to take a copy (unrecognized return type)
+            // fallback to returning the original adapter, without filtering for visibility
+
+            return resultAdapter;
+
+        } else {
+            boolean visible = ObjectAdapter.Util.isVisible(resultAdapter, interactionInitiatedBy);
+            return visible ? resultAdapter : null;
         }
-        return invocationResultAdapter;
     }
 
+
     /**
      * Optional hook to allow the facet implementation for the deprecated {@link org.apache.isis.applib.annotation.PostsActionInvokedEvent} annotation
      * to discard the event if the domain event is of a different type (specifically if was installed by virtue of a no
@@ -528,7 +531,11 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
     // /////////////////////////////////////////////////////////
 
     private CommandContext getCommandContext() {
-        return lookupService(CommandContext.class);
+        CommandContext commandContext = lookupService(CommandContext.class);
+        if (commandContext == null) {
+            throw new IllegalStateException("The CommandContext service is not registered!");
+        }
+        return commandContext;
     }
 
     private QueryResultsCache getQueryResultsCache() {
@@ -539,18 +546,6 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
         return lookupService(CommandService.class);
     }
 
-    private CommandMementoService getCommandMementoService() {
-        return lookupService(CommandMementoService.class);
-    }
-
-    private Bulk.InteractionContext getBulkInteractionContext() {
-        return lookupService(Bulk.InteractionContext.class);
-    }
-
-    private ActionInvocationContext getActionInvocationContext() {
-        return lookupService(ActionInvocationContext.class);
-    }
-
     private <T> T lookupService(final Class<T> serviceClass) {
         return getServicesInjector().lookupService(serviceClass);
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/update/modify/PropertySetterFacetViaModifyMethod.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/update/modify/PropertySetterFacetViaModifyMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/update/modify/PropertySetterFacetViaModifyMethod.java
index 9339274..32865c5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/update/modify/PropertySetterFacetViaModifyMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/update/modify/PropertySetterFacetViaModifyMethod.java
@@ -64,24 +64,20 @@ public class PropertySetterFacetViaModifyMethod extends PropertySetterFacetAbstr
             final InteractionInitiatedBy interactionInitiatedBy) {
 
         final CommandContext commandContext = getServicesInjector().lookupService(CommandContext.class);
-        final Command command;
+        final Command command = commandContext.getCommand();
 
-        if (commandContext != null) {
-            command = commandContext.getCommand();
+        // cf similar code in ActionInvocationFacetForDomainEventFacet
+        command.setExecutor(Command.Executor.USER);
 
-            // cf similar code in ActionInvocationFacetForDomainEventFacet
-            command.setExecutor(Command.Executor.USER);
+        command.setTarget(CommandUtil.bookmarkFor(targetAdapter));
+        command.setTargetClass(CommandUtil.targetClassNameFor(targetAdapter));
+        command.setTargetAction(Command.ACTION_IDENTIFIER_FOR_EDIT);
+        command.setMemberIdentifier(Command.ACTION_IDENTIFIER_FOR_EDIT);
 
-            command.setTarget(CommandUtil.bookmarkFor(targetAdapter));
-            command.setTargetClass(CommandUtil.targetClassNameFor(targetAdapter));
-            command.setTargetAction(Command.ACTION_IDENTIFIER_FOR_EDIT);
-            command.setMemberIdentifier(Command.ACTION_IDENTIFIER_FOR_EDIT);
+        command.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.FOREGROUND);
+        command.setPersistence(org.apache.isis.applib.annotation.Command.Persistence.IF_HINTED);
 
-            command.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.FOREGROUND);
-            command.setPersistence(org.apache.isis.applib.annotation.Command.Persistence.IF_HINTED);
-
-            command.setStartedAt(Clock.getTimeAsJavaSqlTimestamp());
-        }
+        command.setStartedAt(Clock.getTimeAsJavaSqlTimestamp());
 
         ObjectAdapter.InvokeUtils.invoke(method, targetAdapter, valueAdapter);
     }
@@ -96,4 +92,16 @@ public class PropertySetterFacetViaModifyMethod extends PropertySetterFacetAbstr
         return servicesInjector;
     }
 
+    private <T> T lookupService(final Class<T> serviceClass) {
+        return getServicesInjector().lookupService(serviceClass);
+    }
+
+    protected CommandContext getCommandContext() {
+        CommandContext commandContext = lookupService(CommandContext.class);
+        if (commandContext == null) {
+            throw new IllegalStateException("The CommandContext service is not registered!");
+        }
+        return commandContext;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/command/CommandMementoService.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/command/CommandMementoService.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/command/CommandMementoService.java
index acbec29..713d2de 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/command/CommandMementoService.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/command/CommandMementoService.java
@@ -41,8 +41,8 @@ public interface CommandMementoService {
     ActionInvocationMemento asActionInvocationMemento(Method m, Object domainObject, Object[] args);
 
     /**
-     * The default implementation returns a non-null value.  If a custom implementation returns <tt>null</tt>, then
-     * the framework will fall back to using the deprecated {@link #asActionInvocationMemento(Method, Object, Object[])}.
+     * Returns a JAXB DTO (hence convertible to XML) that represents the intention to invoke an action on an action
+     * (or possible many, for bulk actions).  The action can be a mixin action or a contributed action.
      */
     @Programmatic
     CommandMementoDto asCommandMemento(

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
index 659c5dc..cf50c4b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
@@ -17,7 +17,6 @@
 
 package org.apache.isis.core.metamodel.spec.feature;
 
-import java.lang.reflect.Method;
 import java.util.List;
 
 import com.google.common.base.Predicate;
@@ -52,7 +51,8 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 public interface ObjectAction extends ObjectMember {
 
-    // //////////////////////////////////////////////////////
+
+        // //////////////////////////////////////////////////////
     // semantics, realTarget, getOnType
     // //////////////////////////////////////////////////////
 
@@ -186,6 +186,20 @@ public interface ObjectAction extends ObjectMember {
             final InteractionInitiatedBy interactionInitiatedBy);
 
 
+
+    /**
+     * internal API
+     */
+    void setupActionInvocationContext(
+            final ObjectAdapter targetAdapter);
+
+    /**
+     * internal API
+     */
+    void setupCommand(
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments);
+
     // //////////////////////////////////////////////////////
     // Utils
     // //////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
index 4d9d782..59a0048 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
@@ -17,21 +17,13 @@
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 
 import com.google.common.collect.Lists;
 
 import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.annotation.Bulk;
-import org.apache.isis.applib.annotation.InvokedOn;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.filter.Filter;
-import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
-import org.apache.isis.applib.services.bookmark.Bookmark;
-import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.Command.Executor;
-import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.core.commons.lang.ObjectExtensions;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.consent.Consent;
@@ -41,8 +33,6 @@ import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
-import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.core.metamodel.interactions.InteractionUtils;
 import org.apache.isis.core.metamodel.interactions.UsabilityContext;
 import org.apache.isis.core.metamodel.interactions.VisibilityContext;
@@ -199,53 +189,14 @@ public class ObjectActionContributee extends ObjectActionDefault implements Cont
             final ObjectAdapter[] arguments,
             final InteractionInitiatedBy interactionInitiatedBy) {
         
-        // this code also exists in ActionInvocationFacetViaMethod
-        // we need to repeat it here because the target adapter should be the contributee, not the contributing service.
+        setupActionInvocationContext(contributee);
+        setupCommandTarget(contributee, arguments);
 
-        final BulkFacet bulkFacet = getFacet(BulkFacet.class);
-        if (bulkFacet != null) {
-
-            final ActionInvocationContext actionInvocationContext = getServicesInjector().lookupService(ActionInvocationContext.class);
-            if (actionInvocationContext != null &&
-                    actionInvocationContext.getInvokedOn() == null) {
-
-                actionInvocationContext.setInvokedOn(InvokedOn.OBJECT);
-                actionInvocationContext.setDomainObjects(Collections.singletonList(contributee.getObject()));
-            }
-
-            final Bulk.InteractionContext bulkInteractionContext = getServicesInjector().lookupService(Bulk.InteractionContext.class);
-            if (bulkInteractionContext != null &&
-                    bulkInteractionContext.getInvokedAs() == null) {
-
-                bulkInteractionContext.setInvokedAs(Bulk.InteractionContext.InvokedAs.REGULAR);
-                bulkInteractionContext.setDomainObjects(Collections.singletonList(contributee.getObject()));
-            }
-
-
-        }
-
-        final CommandContext commandContext = getServicesInjector().lookupService(CommandContext.class);
-        final Command command = commandContext != null ? commandContext.getCommand() : null;
-
-        if(command != null && command.getExecutor() == Executor.USER) {
-
-            if (command.getTarget() != null) {
-                // already set up by a edit form
-                // don't overwrite
-            } else {
-                command.setTargetClass(CommandUtil.targetClassNameFor(contributee));
-                command.setTargetAction(CommandUtil.targetActionNameFor(this));
-                command.setArguments(CommandUtil.argDescriptionFor(this, arguments));
-
-                final Bookmark targetBookmark = CommandUtil.bookmarkFor(contributee);
-                command.setTarget(targetBookmark);
-            }
-        }
-        
         return serviceAction.execute(getServiceAdapter(), argsPlusContributee(contributee, arguments),
                 interactionInitiatedBy);
     }
 
+
     private ObjectAdapter[] argsPlusContributee(final ObjectAdapter contributee, final ObjectAdapter[] arguments) {
         return addElementToArray(arguments, contributeeParam, contributee, new ObjectAdapter[]{});
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
index 7060cf1..bcc0f03 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
+import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Objects;
@@ -29,8 +30,13 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.annotation.ActionSemantics;
+import org.apache.isis.applib.annotation.Bulk;
+import org.apache.isis.applib.annotation.InvokedOn;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.filter.Filter;
+import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.core.commons.debug.DebugString;
 import org.apache.isis.core.commons.exceptions.UnknownTypeException;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
@@ -44,6 +50,9 @@ import org.apache.isis.core.metamodel.facets.FacetedMethod;
 import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
 import org.apache.isis.core.metamodel.facets.TypedHolder;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.ActionInvocationFacet;
+import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
+import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
+import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.debug.DebugFacet;
 import org.apache.isis.core.metamodel.facets.actions.defaults.ActionDefaultsFacet;
 import org.apache.isis.core.metamodel.facets.actions.exploration.ExplorationFacet;
@@ -59,12 +68,15 @@ import org.apache.isis.core.metamodel.interactions.InteractionUtils;
 import org.apache.isis.core.metamodel.interactions.UsabilityContext;
 import org.apache.isis.core.metamodel.interactions.ValidityContext;
 import org.apache.isis.core.metamodel.interactions.VisibilityContext;
+import org.apache.isis.core.metamodel.services.command.CommandMementoService;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.DomainModelException;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMemberDependencies;
+import org.apache.isis.schema.cmd.v1.CommandMementoDto;
+import org.apache.isis.schema.utils.CommandMementoDtoUtils;
 
 public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectAction {
 
@@ -353,15 +365,15 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
 
     @Override
     public ObjectAdapter execute(
-            final ObjectAdapter target,
+            final ObjectAdapter targetAdapter,
             final ObjectAdapter[] arguments,
             final InteractionInitiatedBy interactionInitiatedBy) {
         if(LOG.isDebugEnabled()) {
-            LOG.debug("execute action " + target + "." + getId());
+            LOG.debug("execute action " + targetAdapter + "." + getId());
         }
         final ActionInvocationFacet facet = getFacet(ActionInvocationFacet.class);
-        return facet.invoke(this, target, arguments,
-                interactionInitiatedBy);
+
+        return facet.invoke(this, targetAdapter, arguments, interactionInitiatedBy);
     }
 
     protected ActionInvocationFacet getActionInvocationFacet() {
@@ -412,10 +424,8 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
         }
 
         final ObjectAdapter[] parameterDefaultAdapters = new ObjectAdapter[parameterCount];
-        if (parameterDefaultPojos != null) {
-            for (int i = 0; i < parameterCount; i++) {
-                parameterDefaultAdapters[i] = adapterFor(parameterDefaultPojos[i]);
-            }
+        for (int i = 0; i < parameterCount; i++) {
+            parameterDefaultAdapters[i] = adapterFor(parameterDefaultPojos[i]);
         }
 
         return parameterDefaultAdapters;
@@ -494,6 +504,141 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
         return parameterChoicesAdapters;
     }
 
+
+    /**
+     * Internal API
+     */
+    @Override
+    public void setupActionInvocationContext(final ObjectAdapter targetAdapter) {
+
+        final Object targetPojo = unwrap(targetAdapter);
+
+        final BulkFacet bulkFacet = getFacetHolder().getFacet(BulkFacet.class);
+        if (bulkFacet != null) {
+            final org.apache.isis.applib.services.actinvoc.ActionInvocationContext actionInvocationContext = getActionInvocationContext();
+            if (actionInvocationContext != null && actionInvocationContext.getInvokedOn() == null) {
+
+                actionInvocationContext.setInvokedOn(InvokedOn.OBJECT);
+                actionInvocationContext.setDomainObjects(Collections.singletonList(targetPojo));
+            }
+
+            final Bulk.InteractionContext bulkInteractionContext = getBulkInteractionContext();
+
+            if (bulkInteractionContext != null && bulkInteractionContext.getInvokedAs() == null) {
+
+                bulkInteractionContext.setInvokedAs(Bulk.InteractionContext.InvokedAs.REGULAR);
+                bulkInteractionContext.setDomainObjects(Collections.singletonList(targetPojo));
+            }
+        }
+    }
+
+    /**
+     * Internal API
+     */
+    @Override
+    public void setupCommand(
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments) {
+
+        setupCommandTarget(targetAdapter, arguments);
+        setupCommandMemberIdentifier();
+        setupCommandMementoAndExecutionContext(targetAdapter, arguments);
+    }
+
+
+    protected void setupCommandTarget(
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments) {
+
+        final CommandContext commandContext = getCommandContext();
+        final Command command = commandContext.getCommand();
+
+        if (command.getExecutor() != Command.Executor.USER) {
+            return;
+        }
+
+        if(command.getTarget() != null) {
+            // already set up by a ObjectActionContributee or edit form;
+            // don't overwrite
+            return;
+        }
+
+        command.setTargetClass(CommandUtil.targetClassNameFor(targetAdapter));
+        command.setTargetAction(CommandUtil.targetActionNameFor(this));
+        command.setArguments(CommandUtil.argDescriptionFor(this, arguments));
+
+        final Bookmark targetBookmark = CommandUtil.bookmarkFor(targetAdapter);
+        command.setTarget(targetBookmark);
+    }
+
+    protected void setupCommandMemberIdentifier() {
+
+        final CommandContext commandContext = getCommandContext();
+        final Command command = commandContext.getCommand();
+
+        if (command.getExecutor() != Command.Executor.USER) {
+            return;
+        }
+
+        if(Command.ACTION_IDENTIFIER_FOR_EDIT.equals(command.getMemberIdentifier())) {
+            // special case for edit properties, don't overwrite
+            return;
+        }
+
+        if (command.getMemberIdentifier() != null) {
+            // any contributed/mixin actions will fire after the main action
+            // the guard here prevents them from trashing the command's memberIdentifier
+            return;
+        }
+
+        command.setMemberIdentifier(CommandUtil.actionIdentifierFor(this));
+    }
+
+    protected void setupCommandMementoAndExecutionContext(
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter[] arguments) {
+
+        final CommandContext commandContext = getCommandContext();
+        final Command command = commandContext.getCommand();
+
+
+        if (command.getExecutor() != Command.Executor.USER) {
+            return;
+        }
+
+        if(Command.ACTION_IDENTIFIER_FOR_EDIT.equals(command.getMemberIdentifier())) {
+            // special case for edit properties, don't overwrite
+            return;
+        }
+
+        if (command.getMemento() != null) {
+            // guard here to prevent subsequent contributed/mixin actions from
+            // trampling over the command's memento and execution context
+            return;
+        }
+
+        // memento
+        final CommandMementoService commandMementoService = getCommandMementoService();
+        final CommandMementoDto dto = commandMementoService.asCommandMemento(
+                Collections.singletonList(targetAdapter),
+                this, arguments);
+
+        final String mementoXml = CommandMementoDtoUtils.toXml(dto);
+        command.setMemento(mementoXml);
+
+        // copy over the command execution 'context' (if available)
+        final CommandFacet commandFacet = getFacetHolder().getFacet(CommandFacet.class);
+        if(commandFacet != null && !commandFacet.isDisabled()) {
+            command.setExecuteIn(commandFacet.executeIn());
+            command.setPersistence(commandFacet.persistence());
+        } else {
+            // if no facet, assume do want to execute right now, but only persist (eventually) if hinted.
+            command.setExecuteIn(org.apache.isis.applib.annotation.Command.ExecuteIn.FOREGROUND);
+            command.setPersistence(org.apache.isis.applib.annotation.Command.Persistence.IF_HINTED);
+        }
+
+    }
+
     //endregion
 
     //region > debug, toString
@@ -528,4 +673,33 @@ public class ObjectActionDefault extends ObjectMemberAbstract implements ObjectA
     //endregion
 
 
+    private static Object unwrap(final ObjectAdapter adapter) {
+        return adapter == null ? null : adapter.getObject();
+    }
+
+    private <T> T lookupService(final Class<T> serviceClass) {
+        return getServicesInjector().lookupService(serviceClass);
+    }
+
+    protected CommandContext getCommandContext() {
+        CommandContext commandContext = lookupService(CommandContext.class);
+        if (commandContext == null) {
+            throw new IllegalStateException("The CommandContext service is not registered!");
+        }
+        return commandContext;
+    }
+
+    protected CommandMementoService getCommandMementoService() {
+        return lookupService(CommandMementoService.class);
+    }
+
+    protected Bulk.InteractionContext getBulkInteractionContext() {
+        return lookupService(Bulk.InteractionContext.class);
+    }
+
+    protected org.apache.isis.applib.services.actinvoc.ActionInvocationContext getActionInvocationContext() {
+        return lookupService(org.apache.isis.applib.services.actinvoc.ActionInvocationContext.class);
+    }
+
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
index 9bdb98e..3490efd 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
@@ -16,7 +16,6 @@
  */
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
-import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Objects;
@@ -24,13 +23,8 @@ import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 
 import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.annotation.Bulk;
-import org.apache.isis.applib.annotation.InvokedOn;
 import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
-import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
-import org.apache.isis.applib.services.command.Command.Executor;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.core.commons.lang.ObjectExtensions;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
@@ -39,18 +33,13 @@ import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
-import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
-import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred;
 import org.apache.isis.core.metamodel.interactions.InteractionUtils;
 import org.apache.isis.core.metamodel.interactions.UsabilityContext;
 import org.apache.isis.core.metamodel.interactions.VisibilityContext;
-import org.apache.isis.core.metamodel.services.command.CommandMementoService;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMemberDependencies;
-import org.apache.isis.schema.cmd.v1.CommandMementoDto;
-import org.apache.isis.schema.utils.CommandMementoDtoUtils;
 
 public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInMember2 {
 
@@ -154,7 +143,7 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
             final Where where) {
         final VisibilityContext<?> ic =
                 mixinAction.createVisibleInteractionContext(
-                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+                        mixinAdapterFor(mixedInAdapter), interactionInitiatedBy, where);
         ic.putContributee(0, mixedInAdapter);
         return InteractionUtils.isVisibleResult(this, ic).createConsent();
     }
@@ -165,28 +154,32 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
             final InteractionInitiatedBy interactionInitiatedBy, final Where where) {
         final UsabilityContext<?> ic =
                 mixinAction.createUsableInteractionContext(
-                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+                        mixinAdapterFor(mixedInAdapter), interactionInitiatedBy, where);
         ic.putContributee(0, mixedInAdapter);
         return InteractionUtils.isUsableResult(this, ic).createConsent();
     }
 
     @Override
     public ObjectAdapter[] getDefaults(final ObjectAdapter mixedInAdapter) {
-        return mixinAction.getDefaults(mixinAdapterFor(mixinType, mixedInAdapter));
+        return mixinAction.getDefaults(mixinAdapterFor(mixedInAdapter));
     }
 
     @Override
     public ObjectAdapter[][] getChoices(
             final ObjectAdapter mixedInAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        return mixinAction.getChoices(mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy);
+        return mixinAction.getChoices(mixinAdapterFor(mixedInAdapter), interactionInitiatedBy);
+    }
+
+    protected ObjectAdapter mixinAdapterFor(final ObjectAdapter mixedInAdapter) {
+        return mixinAdapterFor(mixinType, mixedInAdapter);
     }
 
     public Consent isProposedArgumentSetValid(
             final ObjectAdapter mixedInAdapter,
             final ObjectAdapter[] proposedArguments,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        return mixinAction.isProposedArgumentSetValid(mixinAdapterFor(mixinType, mixedInAdapter), proposedArguments, interactionInitiatedBy);
+        return mixinAction.isProposedArgumentSetValid(mixinAdapterFor(mixedInAdapter), proposedArguments, interactionInitiatedBy);
     }
 
     @Override
@@ -198,73 +191,17 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
         // this code also exists in ActionInvocationFacetViaMethod
         // we need to repeat it here because the target adapter should be the mixedInAdapter, not the mixin
 
-        final BulkFacet bulkFacet = getFacet(BulkFacet.class);
-        if (bulkFacet != null) {
-
-            final ActionInvocationContext actionInvocationContext =
-                    getServicesInjector().lookupService(ActionInvocationContext.class);
-            if (actionInvocationContext != null &&
-                    actionInvocationContext.getInvokedOn() == null) {
+        setupActionInvocationContext(mixedInAdapter);
 
-                actionInvocationContext.setInvokedOn(InvokedOn.OBJECT);
-                actionInvocationContext.setDomainObjects(Collections.singletonList(mixedInAdapter.getObject()));
-            }
-
-            final Bulk.InteractionContext bulkInteractionContext = getServicesInjector().lookupService(Bulk.InteractionContext.class);
-            if (bulkInteractionContext != null &&
-                    bulkInteractionContext.getInvokedAs() == null) {
+        final CommandContext commandContext = getCommandContext();
+        final Command command = commandContext.getCommand();
 
-                bulkInteractionContext.setInvokedAs(Bulk.InteractionContext.InvokedAs.REGULAR);
-                actionInvocationContext.setDomainObjects(Collections.singletonList(mixedInAdapter.getObject()));
-            }
-        }
+        setupCommandTarget(mixedInAdapter, arguments);
+        setupCommandMementoAndExecutionContext(mixedInAdapter, arguments);
 
-        final CommandContext commandContext = getServicesInjector().lookupService(CommandContext.class);
-        final Command command = commandContext != null ? commandContext.getCommand() : null;
-
-        if(command != null && command.getExecutor() == Executor.USER) {
-
-            if (command.getTarget() != null) {
-                // already set up by a edit form
-                // don't overwrite
-            } else {
-                command.setTargetClass(CommandUtil.targetClassNameFor(mixedInAdapter));
-                command.setTargetAction(CommandUtil.targetActionNameFor(this));
-                command.setArguments(CommandUtil.argDescriptionFor(this, arguments));
-
-                final Bookmark targetBookmark = CommandUtil.bookmarkFor(mixedInAdapter);
-                command.setTarget(targetBookmark);
-
-            }
-
-            if(command.getMemento() == null) {
-                // similarly, guard here to deal with subsequent or prior contributed/mixin actions.
-
-                final CommandMementoService commandMementoService = getCommandMementoService();
-
-                final CommandMementoDto dto = commandMementoService.asCommandMemento(
-                        Collections.singletonList(mixedInAdapter),
-                        this, arguments);
-
-                if(dto != null) {
-                    // the default implementation will always return a dto.  The guard is to allow
-                    // for the default implementation to be replaced with a version that returns null
-                    // allowing a fallback to the original API (using ActionInvocationMemento rather than
-                    // CommandMementoDto).
-                    final String mementoXml = CommandMementoDtoUtils.toXml(dto);
-                    command.setMemento(mementoXml);
-                } else {
-                    // no fallback to old behaviour is required; mixin actions were simply not correctly supported.
-                }
-            }
-
-
-        }
-
-        return mixinAction.execute(mixinAdapterFor(mixinType, mixedInAdapter), arguments, interactionInitiatedBy);
+        return mixinAction.execute(mixinAdapterFor(mixedInAdapter), arguments, interactionInitiatedBy);
     }
 
-
     //region > facetHolder
     @Override
     protected FacetHolder getFacetHolder() {
@@ -272,15 +209,6 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
     }
     //endregion
 
-
-    ObjectAdapter mixinAdapterFor(final ObjectAdapter mixedInAdapter) {
-        return mixinAdapterFor(mixinType, mixedInAdapter);
-    }
-
-    private static Object unwrap(final ObjectAdapter adapter) {
-        return adapter == null ? null : adapter.getObject();
-    }
-
     /* (non-Javadoc)
      * @see org.apache.isis.core.metamodel.specloader.specimpl.ObjectMemberAbstract#getIdentifier()
      */
@@ -299,12 +227,5 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
 
     // //////////////////////////////////////
 
-    private CommandMementoService getCommandMementoService() {
-        return lookupService(CommandMementoService.class);
-    }
-
-    private <T> T lookupService(final Class<T> serviceClass) {
-        return getServicesInjector().lookupService(serviceClass);
-    }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/46089a20/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
index 845d7ff..b758e65 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/background/BackgroundServiceDefault.java
@@ -129,9 +129,7 @@ public class BackgroundServiceDefault implements BackgroundService {
             proxyObject.setHandler(methodHandler);
 
             return newInstance;
-        } catch (final InstantiationException e) {
-            throw new IsisException(e);
-        } catch (final IllegalAccessException e) {
+        } catch (final InstantiationException | IllegalAccessException e) {
             throw new IsisException(e);
         }
     }
@@ -170,20 +168,13 @@ public class BackgroundServiceDefault implements BackgroundService {
                 final Command command = commandContext.getCommand();
 
                 if(backgroundCommandService instanceof BackgroundCommandService2) {
-                    final BackgroundCommandService2 backgroundCommandService2 =
-                            (BackgroundCommandService2) backgroundCommandService;
+                    final BackgroundCommandService2 bcs2 = (BackgroundCommandService2) backgroundCommandService;
 
                     final CommandMementoDto dto = commandMementoService
                             .asCommandMemento(Collections.singletonList(targetAdapter), action, argAdapters);
 
-                    if(dto != null) {
-                        // the default implementation returns a non-null value.  This guard is to allow a different
-                        // implementation to be configured that returns null, thereby falling through to the
-                        // original behaviour (of using ActionInvocationMemento instead of CommandMementoDto).
-                        backgroundCommandService2.schedule(dto, command, targetClassName, targetActionName, targetArgs);
-
-                        return null;
-                    }
+                    bcs2.schedule(dto, command, targetClassName, targetActionName, targetArgs);
+                    return null;
                 }
 
                 // fallback
@@ -191,7 +182,6 @@ public class BackgroundServiceDefault implements BackgroundService {
                         .asActionInvocationMemento(proxyMethod, domainObject, args);
 
                 backgroundCommandService.schedule(aim, command, targetClassName, targetActionName, targetArgs);
-
                 return null;
             }