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 2018/01/29 09:21:55 UTC

[isis] 04/05: ISIS-1569: adds support for @Action(commandWithDtoProcessor=...) and @Property(commandWithDtoProcessor=....)

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

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

commit 48ea4812dc1b343e13efb20f004d0837eabd8184
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Fri Jan 26 13:46:20 2018 +0000

    ISIS-1569: adds support for @Action(commandWithDtoProcessor=...) and @Property(commandWithDtoProcessor=....)
    
    In support, MetaModelService5 introduced, to allow the associated commandWithDtoProcessor to be obtained for a given action or property (if any).
    
    ContentMappingServiceForCommand(s)Dto now delegate to the processor if present.
    
    Also, extended common.xsd to support blobs and clobs.
---
 .../org/apache/isis/applib/annotation/Action.java  | 22 ++++++++
 .../apache/isis/applib/annotation/Property.java    | 21 ++++++++
 .../conmap/ContentMappingServiceForCommandDto.java | 35 ++++++++++---
 .../ContentMappingServiceForCommandsDto.java       | 19 ++++---
 .../services/command/CommandWithDtoProcessor.java  | 58 ++++++++++++++++++++++
 .../services/metamodel/MetaModelService5.java}     | 19 +++----
 .../apache/isis/schema/utils/CommonDtoUtils.java   | 36 +++++++++++++-
 .../action/ActionAnnotationFacetFactory.java       |  5 +-
 .../command/CommandFacetForActionAnnotation.java   | 29 ++++++++---
 ...ommandFacetForActionAnnotationAsConfigured.java |  7 ++-
 .../command/CommandFacetForCommandAnnotation.java  | 14 ++++--
 .../command/CommandFacetFromConfiguration.java     | 16 ++++--
 .../facets/actions/command/CommandFacet.java       |  2 +
 .../actions/command/CommandFacetAbstract.java      | 50 +++++++++++++++++--
 .../MustSatisfySpecificationFacetAbstract.java     | 20 +++++++-
 ...onFacetForMustSatisfyAnnotationOnParameter.java |  8 +--
 ...fySpecificationFacetForParameterAnnotation.java | 11 +---
 .../property/PropertyAnnotationFacetFactory.java   |  3 +-
 .../command/CommandFacetForPropertyAnnotation.java | 30 ++++++++---
 ...mandFacetForPropertyAnnotationAsConfigured.java |  5 +-
 ...tySetterOrClearFacetForDomainEventAbstract.java |  5 +-
 ...ionFacetForMustSatisfyAnnotationOnProperty.java |  9 +---
 ...sfySpecificationFacetForPropertyAnnotation.java | 11 +---
 .../services/appfeat/ApplicationFeatureId.java     | 16 ++++++
 .../metamodel/MetaModelServiceDefault.java         | 35 ++++++++++++-
 .../core/metamodel/spec/ObjectSpecification.java   |  3 ++
 .../specimpl/ObjectSpecificationAbstract.java      | 14 ++++++
 .../testspec/ObjectSpecificationStub.java          | 14 ++++++
 .../org/apache/isis/schema/common/common-1.1.xsd   | 32 ++++++++++++
 29 files changed, 445 insertions(+), 104 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Action.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
index a546ddd..1798a31 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Action.java
@@ -24,6 +24,12 @@ import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+
+import org.apache.isis.applib.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.conmap.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.command.CommandWithDto;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
+import org.apache.isis.applib.services.command.spi.CommandService;
 import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
 import org.apache.isis.applib.services.publish.PublisherService;
 
@@ -143,6 +149,22 @@ public @interface Action {
      */
     CommandExecuteIn commandExecuteIn() default CommandExecuteIn.FOREGROUND;
 
+    /**
+     * The {@link CommandWithDtoProcessor} to process this command's DTO.
+     *
+     * <p>
+     *     Specifying a processor requires that the implementation of {@link CommandService} provides a
+     *     custom implementation of {@link org.apache.isis.applib.services.command.Command} that additional extends
+     *     from {@link CommandWithDto}.
+     * </p>
+     *
+     * <p>
+     *     Tprocessor itself is used by {@link ContentMappingServiceForCommandDto} and
+     *     {@link ContentMappingServiceForCommandsDto} to dynamically transform the DTOs.
+     * </p>
+     */
+    Class<? extends CommandWithDtoProcessor> commandWithDtoProcessor() default CommandWithDtoProcessor.class;
+
 
     // //////////////////////////////////////
 
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
index d4377ee..106f380 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Property.java
@@ -27,6 +27,11 @@ import java.lang.annotation.Target;
 
 import javax.jdo.annotations.NotPersistent;
 
+import org.apache.isis.applib.conmap.ContentMappingServiceForCommandDto;
+import org.apache.isis.applib.conmap.ContentMappingServiceForCommandsDto;
+import org.apache.isis.applib.services.command.CommandWithDto;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
+import org.apache.isis.applib.services.command.spi.CommandService;
 import org.apache.isis.applib.services.eventbus.PropertyDomainEvent;
 import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.applib.value.Blob;
@@ -134,6 +139,22 @@ public @interface Property {
     Publishing publishing() default Publishing.AS_CONFIGURED;
 
 
+    /**
+     * The {@link CommandWithDtoProcessor} to process this command's DTO.
+     *
+     * <p>
+     *     Specifying a processor requires that the implementation of {@link CommandService} provides a
+     *     custom implementation of {@link org.apache.isis.applib.services.command.Command} that additional extends
+     *     from {@link CommandWithDto}.
+     * </p>
+     *
+     * <p>
+     *     Tprocessor itself is used by {@link ContentMappingServiceForCommandDto} and
+     *     {@link ContentMappingServiceForCommandsDto} to dynamically transform the DTOs.
+     * </p>
+     */
+    Class<? extends CommandWithDtoProcessor> commandWithDtoProcessor() default CommandWithDtoProcessor.class;
+
 
 
 
diff --git a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
index 0eb1235..5e520e2 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandDto.java
@@ -20,12 +20,15 @@ package org.apache.isis.applib.conmap;
 
 import java.util.List;
 
+import javax.inject.Inject;
 import javax.ws.rs.core.MediaType;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.services.command.CommandWithDto;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
+import org.apache.isis.applib.services.metamodel.MetaModelService5;
 import org.apache.isis.schema.cmd.v1.CommandDto;
 
 @DomainService(
@@ -41,20 +44,38 @@ public class ContentMappingServiceForCommandDto implements ContentMappingService
             return null;
         }
 
-        return asDto(object);
+        return asDto(object, metaModelService);
     }
 
+    static CommandDto asDto(
+            final Object object,
+            final MetaModelService5 metaModelService) {
 
-    static CommandDto asDto(final Object object) {
-        CommandDto commandDto = null;
         if(object instanceof CommandWithDto) {
             final CommandWithDto commandWithDto = (CommandWithDto) object;
-            commandDto = commandWithDto.asDto();
+            return process(commandWithDto, metaModelService);
         }
-        if(object instanceof CommandDto) {
-            commandDto = (CommandDto) object;
+        return null;
+    }
+
+    private static CommandDto process(
+            CommandWithDto commandWithDto,
+            final MetaModelService5 metaModelService) {
+        final CommandDto commandDto = commandWithDto.asDto();
+        final CommandWithDtoProcessor<?> commandWithDtoProcessor =
+                metaModelService.commandDtoProcessorFor(commandDto.getMember().getLogicalMemberIdentifier());
+        if (commandWithDtoProcessor == null) {
+            return commandDto;
         }
-        return commandDto;
+        return process(commandWithDtoProcessor, commandWithDto);
+    }
+
+    private static CommandDto process(
+            final CommandWithDtoProcessor commandWithDtoProcessor,
+            final CommandWithDto commandWithDto) {
+        return commandWithDtoProcessor.process(commandWithDto);
     }
 
+    @Inject
+    MetaModelService5 metaModelService;
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
index 4f886a9..50ecddd 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/conmap/ContentMappingServiceForCommandsDto.java
@@ -20,11 +20,13 @@ package org.apache.isis.applib.conmap;
 
 import java.util.List;
 
+import javax.inject.Inject;
 import javax.ws.rs.core.MediaType;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.metamodel.MetaModelService5;
 import org.apache.isis.schema.cmd.v1.CommandDto;
 import org.apache.isis.schema.cmd.v1.CommandsDto;
 
@@ -45,7 +47,7 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
             return object;
         }
 
-        CommandDto commandDto = asDto(object);
+        CommandDto commandDto = asDto(object, metaModelService5);
         if(commandDto != null) {
             final CommandsDto commandsDto = new CommandsDto();
             commandsDto.getCommandDto().add(commandDto);
@@ -55,24 +57,27 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
         if (object instanceof List) {
             final List list = (List) object;
             final CommandsDto commandsDto = new CommandsDto();
-            for (final Object o : list) {
-                final CommandDto objAsCommandDto = asDto(o);
+            for (final Object obj : list) {
+                final CommandDto objAsCommandDto = asDto(obj, metaModelService5);
                 if(objAsCommandDto != null) {
                     commandsDto.getCommandDto().add(objAsCommandDto);
                 } else {
                     // ignore entire list because found something that is not convertible.
-                    return null;
+                    return new CommandsDto();
                 }
             }
             return commandsDto;
         }
 
         // else
-        return null;
+        return new CommandsDto();
     }
 
-    private static CommandDto asDto(final Object object) {
-        return ContentMappingServiceForCommandDto.asDto(object);
+    private static CommandDto asDto(final Object object, MetaModelService5 metaModelService5) {
+        return ContentMappingServiceForCommandDto.asDto(object, metaModelService5);
     }
 
+    @Inject
+    MetaModelService5 metaModelService5;
+
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessor.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessor.java
new file mode 100644
index 0000000..8785ea8
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessor.java
@@ -0,0 +1,58 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.services.command;
+
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.eventbus.AbstractDomainEvent;
+import org.apache.isis.applib.services.eventbus.ActionDomainEvent;
+import org.apache.isis.applib.services.eventbus.PropertyDomainEvent;
+import org.apache.isis.schema.cmd.v1.ActionDto;
+import org.apache.isis.schema.cmd.v1.CommandDto;
+import org.apache.isis.schema.cmd.v1.ParamDto;
+import org.apache.isis.schema.cmd.v1.ParamsDto;
+import org.apache.isis.schema.cmd.v1.PropertyDto;
+
+public interface CommandWithDtoProcessor<E extends AbstractDomainEvent<?>> {
+
+    @Programmatic
+    CommandDto process(CommandWithDto commandWithDto);
+
+    abstract class Abstract<E extends AbstractDomainEvent<?>> implements CommandWithDtoProcessor<E> {
+        protected CommandDto asDto(final CommandWithDto commandWithDto) {
+            return commandWithDto.asDto();
+        }
+    }
+
+    abstract class ForActionAbstract<E extends ActionDomainEvent<?>> extends Abstract<E> {
+        protected ActionDto getActionDto(final CommandDto commandDto) {
+            return (ActionDto) commandDto.getMember();
+        }
+        protected ParamDto getParamDto(final CommandDto commandDto, final int paramNum) {
+            final ActionDto actionDto = getActionDto(commandDto);
+            final ParamsDto parameters = actionDto.getParameters();
+            return parameters.getParameter().get(paramNum);
+        }
+    }
+
+    abstract class ForPropertyAbstract<E extends PropertyDomainEvent<?,?>> extends Abstract<E> {
+        protected PropertyDto getPropertyDto(final CommandDto commandDto) {
+            return (PropertyDto) commandDto.getMember();
+        }
+    }
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
similarity index 56%
copy from core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java
copy to core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
index 1433500..0230694 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
@@ -16,21 +16,14 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.core.metamodel.facets.actions.action.command;
+package org.apache.isis.applib.services.metamodel;
 
-import org.apache.isis.applib.annotation.Command.ExecuteIn;
-import org.apache.isis.applib.annotation.Command.Persistence;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 
-public class CommandFacetForActionAnnotationAsConfigured extends CommandFacetForActionAnnotation {
-
-    CommandFacetForActionAnnotationAsConfigured(
-            final Persistence persistence,
-            final ExecuteIn executeIn,
-            final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
-    }
+public interface MetaModelService5 extends MetaModelService4 {
 
+    @Programmatic
+    CommandWithDtoProcessor<?> commandDtoProcessorFor(String memberIdentifier);
 
 }
diff --git a/core/applib/src/main/java/org/apache/isis/schema/utils/CommonDtoUtils.java b/core/applib/src/main/java/org/apache/isis/schema/utils/CommonDtoUtils.java
index a10c019..239aea8 100644
--- a/core/applib/src/main/java/org/apache/isis/schema/utils/CommonDtoUtils.java
+++ b/core/applib/src/main/java/org/apache/isis/schema/utils/CommonDtoUtils.java
@@ -33,7 +33,11 @@ import org.joda.time.LocalTime;
 
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.bookmark.BookmarkService;
+import org.apache.isis.applib.value.Blob;
+import org.apache.isis.applib.value.Clob;
 import org.apache.isis.schema.cmd.v1.ParamDto;
+import org.apache.isis.schema.common.v1.BlobDto;
+import org.apache.isis.schema.common.v1.ClobDto;
 import org.apache.isis.schema.common.v1.CollectionDto;
 import org.apache.isis.schema.common.v1.EnumDto;
 import org.apache.isis.schema.common.v1.OidDto;
@@ -89,6 +93,8 @@ public final class CommonDtoUtils {
                     .put(LocalDate.class, ValueType.JODA_LOCAL_DATE)
                     .put(LocalTime.class, ValueType.JODA_LOCAL_TIME)
                     .put(java.sql.Timestamp.class, ValueType.JAVA_SQL_TIMESTAMP)
+                    .put(Blob.class, ValueType.BLOB)
+                    .put(Clob.class, ValueType.CLOB)
                     .build();
 
     public static ValueType asValueType(final Class<?> type) {
@@ -124,9 +130,13 @@ public final class CommonDtoUtils {
             final ValueType valueType,
             final Object val,
             final BookmarkService bookmarkService) {
+        valueWithTypeDto.setType(valueType);
+
         setValueOn((ValueDto)valueWithTypeDto, valueType, val, bookmarkService);
         valueWithTypeDto.setNull(val == null);
+
         if(val instanceof Collection) {
+            // TODO: this is probably irrelevant
             valueWithTypeDto.setType(ValueType.COLLECTION);
         }
         return valueWithTypeDto;
@@ -246,6 +256,24 @@ public final class CommonDtoUtils {
             }
             return valueDto;
         }
+        case BLOB: {
+            final Blob blob = (Blob) val;
+            final BlobDto blobDto = new BlobDto();
+            blobDto.setName(blob.getName());
+            blobDto.setBytes(blob.getBytes());
+            blobDto.setMimeType(blob.getMimeType().toString());
+            valueDto.setBlob(blobDto);
+            return valueDto;
+        }
+        case CLOB: {
+            final Clob clob = (Clob) val;
+            final ClobDto clobDto = new ClobDto();
+            clobDto.setName(clob.getName());
+            clobDto.setChars(clob.getChars().toString());
+            clobDto.setMimeType(clob.getMimeType().toString());
+            valueDto.setClob(clobDto);
+            return valueDto;
+        }
         case VOID: {
             return null;
         }
@@ -321,6 +349,12 @@ public final class CommonDtoUtils {
             return (T) valueDto.getReference();
         case COLLECTION:
             return (T)valueDto.getCollection();
+        case BLOB:
+            final BlobDto blobDto = valueDto.getBlob();
+            return (T)new Blob(blobDto.getName(), blobDto.getMimeType(), blobDto.getBytes());
+        case CLOB:
+            final ClobDto clobDto = valueDto.getClob();
+            return (T)new Clob(clobDto.getName(), clobDto.getMimeType(), clobDto.getChars());
         case VOID:
             return null;
         default:
@@ -368,8 +402,6 @@ public final class CommonDtoUtils {
         final ValueWithTypeDto valueWithTypeDto = new ValueWithTypeDto();
 
         final ValueType valueType = asValueType(type);
-        valueWithTypeDto.setType(valueType);
-
         setValueOn(valueWithTypeDto, valueType, val, bookmarkService);
 
         return valueWithTypeDto;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
index b181bee..03e95e1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
@@ -367,11 +367,12 @@ public class ActionAnnotationFacetFactory extends FacetFactoryAbstract
         // check for deprecated @Command annotation first
         final Command annotation = Annotations.getAnnotation(method, Command.class);
         commandFacet = commandValidator.flagIfPresent(
-                CommandFacetForCommandAnnotation.create(annotation, processMethodContext.getFacetHolder()), processMethodContext);
+                CommandFacetForCommandAnnotation.create(annotation, processMethodContext.getFacetHolder(),
+                        servicesInjector), processMethodContext);
 
         // else check for @Action(command=...)
         if(commandFacet == null) {
-            commandFacet = CommandFacetForActionAnnotation.create(action, getConfiguration(), holder);
+            commandFacet = CommandFacetForActionAnnotation.create(action, getConfiguration(), servicesInjector, holder);
         }
 
         FacetUtil.addFacet(commandFacet);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotation.java
index 66c309b..4b0089e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotation.java
@@ -24,27 +24,37 @@ import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.applib.annotation.CommandExecuteIn;
 import org.apache.isis.applib.annotation.CommandPersistence;
 import org.apache.isis.applib.annotation.CommandReification;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 import org.apache.isis.core.commons.config.IsisConfiguration;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacetAbstract;
 import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFacet;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public class CommandFacetForActionAnnotation extends CommandFacetAbstract {
 
     public static CommandFacet create(
             final Action action,
             final IsisConfiguration configuration,
+            final ServicesInjector servicesInjector,
             final FacetHolder holder) {
 
-        final CommandReification command = action != null ? action.command() : CommandReification.AS_CONFIGURED;
+        CommandReification commandReification = action != null ? action.command() : CommandReification.AS_CONFIGURED;
         final CommandPersistence commandPersistence = action != null ? action.commandPersistence() : CommandPersistence.PERSISTED;
         final CommandExecuteIn commandExecuteIn = action != null? action.commandExecuteIn() :  CommandExecuteIn.FOREGROUND;
+        final Class<? extends CommandWithDtoProcessor> processorClass =
+                action != null ? action.commandWithDtoProcessor() : null;
+        final CommandWithDtoProcessor<?> processor = (CommandWithDtoProcessor<?>) newProcessorElseNull(processorClass);
 
+        if(processor != null) {
+            commandReification = CommandReification.ENABLED;
+        }
         final Persistence persistence = CommandPersistence.from(commandPersistence);
         final ExecuteIn executeIn = CommandExecuteIn.from(commandExecuteIn);
 
-        switch (command) {
+
+        switch (commandReification) {
             case AS_CONFIGURED:
                 final CommandActionsConfiguration setting = CommandActionsConfiguration.parse(configuration);
                 switch (setting) {
@@ -61,13 +71,16 @@ public class CommandFacetForActionAnnotation extends CommandFacetAbstract {
                         // else fall through
                     default:
                         return action != null
-                                ? new CommandFacetForActionAnnotationAsConfigured(persistence, executeIn, Enablement.ENABLED, holder)
-                                : CommandFacetFromConfiguration.create(holder);
+                                ? new CommandFacetForActionAnnotationAsConfigured(persistence, executeIn, Enablement.ENABLED, holder,
+                                servicesInjector)
+                                : CommandFacetFromConfiguration.create(holder, servicesInjector);
                 }
             case DISABLED:
                 return null;
             case ENABLED:
-                return new CommandFacetForActionAnnotation(persistence, executeIn, Enablement.ENABLED, holder);
+                return new CommandFacetForActionAnnotation(
+                        persistence, executeIn, Enablement.ENABLED, processor,
+                        holder, servicesInjector);
         }
 
         return null;
@@ -78,8 +91,10 @@ public class CommandFacetForActionAnnotation extends CommandFacetAbstract {
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
+            final CommandWithDtoProcessor<?> processor,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, enablement, processor, holder, servicesInjector);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java
index 1433500..8550b5a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForActionAnnotationAsConfigured.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.actions.action.command;
 import org.apache.isis.applib.annotation.Command.ExecuteIn;
 import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public class CommandFacetForActionAnnotationAsConfigured extends CommandFacetForActionAnnotation {
 
@@ -28,8 +29,10 @@ public class CommandFacetForActionAnnotationAsConfigured extends CommandFacetFor
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, enablement, null,
+                holder, servicesInjector);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForCommandAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForCommandAnnotation.java
index a4739f4..47a3a98 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForCommandAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetForCommandAnnotation.java
@@ -25,6 +25,7 @@ import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacetAbstract;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 /**
  * @deprecated
@@ -36,18 +37,23 @@ public class CommandFacetForCommandAnnotation extends CommandFacetAbstract {
      * @deprecated
      */
     @Deprecated
-    public static CommandFacet create(final Command annotation, final FacetHolder holder) {
+    public static CommandFacet create(
+            final Command annotation,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         return annotation == null
                 ? null
-                : new CommandFacetForCommandAnnotation(annotation.persistence(), annotation.executeIn(), Enablement.isDisabled(annotation.disabled()), holder);
+                : new CommandFacetForCommandAnnotation(annotation.persistence(), annotation.executeIn(), Enablement.isDisabled(annotation.disabled()), holder,
+                servicesInjector);
     }
 
     private CommandFacetForCommandAnnotation(
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, enablement, null, holder, servicesInjector);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetFromConfiguration.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetFromConfiguration.java
index 1533331..c1b7f71 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetFromConfiguration.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/command/CommandFacetFromConfiguration.java
@@ -24,18 +24,24 @@ import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacetAbstract;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public class CommandFacetFromConfiguration extends CommandFacetAbstract {
 
-    public static CommandFacet create(final FacetHolder holder) {
-        return new CommandFacetFromConfiguration(Persistence.PERSISTED, ExecuteIn.FOREGROUND, holder);
+    public static CommandFacet create(
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        return new CommandFacetFromConfiguration(Persistence.PERSISTED, ExecuteIn.FOREGROUND, holder,
+                servicesInjector);
     }
 
     private CommandFacetFromConfiguration(
-            final Persistence persistence, 
+            final Persistence persistence,
             final ExecuteIn executeIn,
-            final FacetHolder holder) {
-        super(persistence, executeIn, Enablement.ENABLED, holder);
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, Enablement.ENABLED, null,
+                holder, servicesInjector);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacet.java
index 2e70147..d3ab0b1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacet.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacet.java
@@ -22,6 +22,7 @@ package org.apache.isis.core.metamodel.facets.actions.command;
 import org.apache.isis.applib.annotation.Command.ExecuteIn;
 import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 
 /**
@@ -49,4 +50,5 @@ public interface CommandFacet extends Facet {
      */
     public boolean isDisabled();
 
+    public CommandWithDtoProcessor<?> getProcessor();
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacetAbstract.java
index bef8ae1..0c3576d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacetAbstract.java
@@ -21,10 +21,11 @@ package org.apache.isis.core.metamodel.facets.actions.command;
 
 import org.apache.isis.applib.annotation.Command.ExecuteIn;
 import org.apache.isis.applib.annotation.Command.Persistence;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.MarkerFacetAbstract;
-import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public abstract class CommandFacetAbstract extends MarkerFacetAbstract implements CommandFacet {
 
@@ -44,16 +45,29 @@ public abstract class CommandFacetAbstract extends MarkerFacetAbstract implement
     private final Persistence persistence;
     private final ExecuteIn executeIn;
     private final Enablement enablement;
+    private final CommandWithDtoProcessor<?> processor;
 
     public CommandFacetAbstract(
-            final Persistence persistence, 
-            final ExecuteIn executeIn, 
-            final Enablement enablement, 
-            final FacetHolder holder) {
+            final Persistence persistence,
+            final ExecuteIn executeIn,
+            final Enablement enablement,
+            final CommandWithDtoProcessor<?> processor,
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
         super(type(), holder);
+        inject(processor, servicesInjector);
         this.persistence = persistence;
         this.executeIn = executeIn;
         this.enablement = enablement;
+        this.processor = processor;
+    }
+
+    private static void inject(
+            final CommandWithDtoProcessor processor, final ServicesInjector servicesInjector) {
+        if(processor == null || servicesInjector == null) {
+            return;
+        }
+        servicesInjector.injectServicesInto(processor);
     }
 
     @Override
@@ -71,4 +85,30 @@ public abstract class CommandFacetAbstract extends MarkerFacetAbstract implement
         return this.enablement == Enablement.DISABLED;
     }
 
+    @Override
+    public CommandWithDtoProcessor<?> getProcessor() {
+        return processor;
+    }
+
+    /**
+     * For benefit of subclasses.
+     */
+    protected static CommandWithDtoProcessor newProcessorElseNull(final Class<?> cls) {
+        if(cls == null) {
+            return null;
+        }
+        if(cls == CommandWithDtoProcessor.class) {
+            // ie the default value, namely the interface
+            return null;
+        }
+        if (!(CommandWithDtoProcessor.class.isAssignableFrom(cls))) {
+            return null;
+        }
+        try {
+            return (CommandWithDtoProcessor) cls.newInstance();
+        } catch (final InstantiationException | IllegalAccessException e) {
+            return null;
+        }
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
index 1e98748..235b2dc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/objectvalue/mustsatisfyspec/MustSatisfySpecificationFacetAbstract.java
@@ -21,6 +21,8 @@ package org.apache.isis.core.metamodel.facets.objectvalue.mustsatisfyspec;
 
 import java.util.List;
 
+import com.google.common.collect.Lists;
+
 import org.apache.isis.applib.events.ValidityEvent;
 import org.apache.isis.applib.services.i18n.TranslationService;
 import org.apache.isis.applib.spec.Specification;
@@ -68,8 +70,7 @@ public abstract class MustSatisfySpecificationFacetAbstract extends FacetAbstrac
 
     private static void inject(
             final List specifications, final ServicesInjector servicesInjector) {
-        final List<Object> specificationsAsObjects = specifications;
-        servicesInjector.injectServicesInto(specificationsAsObjects);
+        servicesInjector.injectServicesInto(specifications);
     }
 
     @Override
@@ -91,6 +92,21 @@ public abstract class MustSatisfySpecificationFacetAbstract extends FacetAbstrac
     /**
      * For benefit of subclasses.
      */
+    protected static List<Specification> specificationsFor(final Class<?>[] values) {
+        final List<Specification> specifications = Lists.newArrayList();
+        for (final Class<?> value : values) {
+            final Specification specification = newSpecificationElseNull(value);
+            if (specification != null) {
+                specifications.add(specification);
+            }
+        }
+        return specifications;
+    }
+
+
+    /**
+     * For benefit of subclasses.
+     */
     protected static Specification newSpecificationElseNull(final Class<?> value) {
         if (!(Specification.class.isAssignableFrom(value))) {
             return null;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
index 2755d87..4251dcb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter.java
@@ -43,13 +43,7 @@ public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter ex
             return null;
         }
         final Class<?>[] values = annotation.value();
-        final List<Specification> specifications = Lists.newArrayList();
-        for (final Class<?> value : values) {
-            final Specification specification = newSpecificationElseNull(value);
-            if (specification != null) {
-                specifications.add(specification);
-            }
-        }
+        final List<Specification> specifications = specificationsFor(values);
         return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnParameter(specifications, holder, servicesInjector) : null;
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
index 1a099ae..f94da8e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/param/parameter/mustsatisfy/MustSatisfySpecificationFacetForParameterAnnotation.java
@@ -20,7 +20,7 @@
 package org.apache.isis.core.metamodel.facets.param.parameter.mustsatisfy;
 
 import java.util.List;
-import com.google.common.collect.Lists;
+
 import org.apache.isis.applib.annotation.Parameter;
 import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -40,17 +40,10 @@ public class MustSatisfySpecificationFacetForParameterAnnotation extends MustSat
         }
 
         final Class<?>[] values = parameter.mustSatisfy();
-        final List<Specification> specifications = Lists.newArrayList();
-        for (final Class<?> value : values) {
-            final Specification specification = newSpecificationElseNull(value);
-            if (specification != null) {
-                specifications.add(specification);
-            }
-        }
+        final List<Specification> specifications = specificationsFor(values);
         return specifications.size() > 0 ? new MustSatisfySpecificationFacetForParameterAnnotation(specifications, holder, servicesInjector) : null;
     }
 
-
     private MustSatisfySpecificationFacetForParameterAnnotation(
             final List<Specification> specifications,
             final FacetHolder holder,
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index 489ed15..7f919f7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -283,7 +283,8 @@ public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract impleme
         }
 
         // check for @Property(command=...)
-        final CommandFacet commandFacet = CommandFacetForPropertyAnnotation.create(property, getConfiguration(), holder);
+        final CommandFacet commandFacet = CommandFacetForPropertyAnnotation.create(property, getConfiguration(), holder,
+                servicesInjector);
 
         FacetUtil.addFacet(commandFacet);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotation.java
index 99db398..33b04f7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotation.java
@@ -24,27 +24,37 @@ import org.apache.isis.applib.annotation.CommandExecuteIn;
 import org.apache.isis.applib.annotation.CommandPersistence;
 import org.apache.isis.applib.annotation.CommandReification;
 import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 import org.apache.isis.core.commons.config.IsisConfiguration;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.actions.action.command.CommandFacetFromConfiguration;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.actions.command.CommandFacetAbstract;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public class CommandFacetForPropertyAnnotation extends CommandFacetAbstract {
 
     public static CommandFacet create(
             final Property property,
             final IsisConfiguration configuration,
-            final FacetHolder holder) {
+            final FacetHolder holder,
+            final ServicesInjector servicesInjector) {
 
-        final CommandReification command = property != null ? property.command() : CommandReification.AS_CONFIGURED;
+        CommandReification commandReification = property != null ? property.command() : CommandReification.AS_CONFIGURED;
         final CommandPersistence commandPersistence = property != null ? property.commandPersistence() : CommandPersistence.PERSISTED;
         final CommandExecuteIn commandExecuteIn = property != null? property.commandExecuteIn() :  CommandExecuteIn.FOREGROUND;
+        final Class<? extends CommandWithDtoProcessor> processorClass =
+                property != null ? property.commandWithDtoProcessor() : null;
+        final CommandWithDtoProcessor<?> processor = (CommandWithDtoProcessor<?>) newProcessorElseNull(processorClass);
 
+        if(processor != null) {
+            commandReification = CommandReification.ENABLED;
+        }
         final Persistence persistence = CommandPersistence.from(commandPersistence);
         final ExecuteIn executeIn = CommandExecuteIn.from(commandExecuteIn);
 
-        switch (command) {
+
+        switch (commandReification) {
             case AS_CONFIGURED:
                 final CommandPropertiesConfiguration setting = CommandPropertiesConfiguration.parse(configuration);
                 switch (setting) {
@@ -52,13 +62,15 @@ public class CommandFacetForPropertyAnnotation extends CommandFacetAbstract {
                     return null;
                 default:
                     return property != null
-                            ? new CommandFacetForPropertyAnnotationAsConfigured(persistence, executeIn, Enablement.ENABLED, holder)
-                            : CommandFacetFromConfiguration.create(holder);
+                            ? new CommandFacetForPropertyAnnotationAsConfigured(persistence, executeIn, Enablement.ENABLED, holder,
+                            servicesInjector)
+                            : CommandFacetFromConfiguration.create(holder, servicesInjector);
                 }
             case DISABLED:
                 return null;
             case ENABLED:
-                return new CommandFacetForPropertyAnnotation(persistence, executeIn, Enablement.ENABLED, holder);
+                return new CommandFacetForPropertyAnnotation(persistence, executeIn, Enablement.ENABLED, holder,
+                        processor, servicesInjector);
         }
 
         return null;
@@ -69,8 +81,10 @@ public class CommandFacetForPropertyAnnotation extends CommandFacetAbstract {
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
+            final FacetHolder holder,
+            final CommandWithDtoProcessor<?> processor,
+            final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, enablement, processor, holder, servicesInjector);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotationAsConfigured.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotationAsConfigured.java
index b048da1..ea71ae5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotationAsConfigured.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/command/CommandFacetForPropertyAnnotationAsConfigured.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.properties.property.command;
 import org.apache.isis.applib.annotation.Command.ExecuteIn;
 import org.apache.isis.applib.annotation.Command.Persistence;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
 
 public class CommandFacetForPropertyAnnotationAsConfigured extends CommandFacetForPropertyAnnotation {
 
@@ -28,8 +29,8 @@ public class CommandFacetForPropertyAnnotationAsConfigured extends CommandFacetF
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final FacetHolder holder) {
-        super(persistence, executeIn, enablement, holder);
+            final FacetHolder holder, final ServicesInjector servicesInjector) {
+        super(persistence, executeIn, enablement, holder, null, servicesInjector);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
index 789f859..ddacfc7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/modify/PropertySetterOrClearFacetForDomainEventAbstract.java
@@ -19,7 +19,10 @@
 
 package org.apache.isis.core.metamodel.facets.properties.property.modify;
 
+import java.sql.Timestamp;
+
 import com.google.common.base.Objects;
+
 import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.CommandContext;
@@ -48,8 +51,6 @@ import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.runtime.system.transaction.TransactionalClosure;
 import org.apache.isis.schema.ixn.v1.PropertyEditDto;
 
-import java.sql.Timestamp;
-
 public abstract class PropertySetterOrClearFacetForDomainEventAbstract
         extends SingleValueFacetAbstract<Class<? extends PropertyDomainEvent<?,?>>> {
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
index 4fd0542..df10e5e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.java
@@ -42,17 +42,10 @@ public class MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty ext
             return null;
         }
         final Class<?>[] values = annotation.value();
-        final List<Specification> specifications = Lists.newArrayList();
-        for (final Class<?> value : values) {
-            final Specification specification = newSpecificationElseNull(value);
-            if (specification != null) {
-                specifications.add(specification);
-            }
-        }
+        final List<Specification> specifications = specificationsFor(values);
         return specifications.size() > 0 ? new MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(specifications, holder, servicesInjector) : null;
     }
 
-
     private MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty(
             final List<Specification> specifications,
             final FacetHolder holder,
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
index 40c10fd..dcc56c9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/mustsatisfy/MustSatisfySpecificationFacetForPropertyAnnotation.java
@@ -20,7 +20,7 @@
 package org.apache.isis.core.metamodel.facets.properties.property.mustsatisfy;
 
 import java.util.List;
-import com.google.common.collect.Lists;
+
 import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.spec.Specification;
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -40,17 +40,10 @@ public class MustSatisfySpecificationFacetForPropertyAnnotation extends MustSati
         }
 
         final Class<?>[] values = property.mustSatisfy();
-        final List<Specification> specifications = Lists.newArrayList();
-        for (final Class<?> value : values) {
-            final Specification specification = newSpecificationElseNull(value);
-            if (specification != null) {
-                specifications.add(specification);
-            }
-        }
+        final List<Specification> specifications = specificationsFor(values);
         return specifications.size() > 0 ? new MustSatisfySpecificationFacetForPropertyAnnotation(specifications, holder, servicesInjector) : null;
     }
 
-
     private MustSatisfySpecificationFacetForPropertyAnnotation(final List<Specification> specifications, final FacetHolder holder, final ServicesInjector servicesInjector) {
         super(specifications, holder, servicesInjector);
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureId.java
index 9869ad1..787c8fe 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureId.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureId.java
@@ -40,6 +40,7 @@ import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.annotation.Value;
 import org.apache.isis.applib.services.appfeat.ApplicationMemberType;
 import org.apache.isis.applib.util.TitleBuffer;
+import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 
 /**
  * Value type representing a package, class or member.
@@ -178,6 +179,21 @@ public class ApplicationFeatureId implements Comparable<ApplicationFeatureId>, S
 
     //endregion
 
+    //region > objectSpecId (property)
+
+    @Programmatic
+    public ObjectSpecId getObjectSpecId() {
+        if (getClassName() == null) {
+            return null;
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append(getPackageName());
+        buf.append(".").append(getClassName());
+        return ObjectSpecId.of(buf.toString());
+    }
+
+    //endregion
+
     // //////////////////////////////////////
 
     //region > type (property)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
index 614d1a3..ad82e89 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefault.java
@@ -33,17 +33,23 @@ import org.apache.isis.applib.AppManifest2;
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
+import org.apache.isis.applib.services.appfeat.ApplicationFeatureRepository;
 import org.apache.isis.applib.services.bookmark.Bookmark;
+import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 import org.apache.isis.applib.services.grid.GridService;
 import org.apache.isis.applib.services.metamodel.DomainMember;
-import org.apache.isis.applib.services.metamodel.MetaModelService4;
+import org.apache.isis.applib.services.metamodel.MetaModelService5;
+import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
 import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
+import org.apache.isis.core.metamodel.services.appfeat.ApplicationFeatureId;
+import org.apache.isis.core.metamodel.services.appfeat.ApplicationFeatureType;
 import org.apache.isis.core.metamodel.services.appmanifest.AppManifestProvider;
 import org.apache.isis.core.metamodel.spec.ObjectSpecId;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -52,7 +58,7 @@ import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
         nature = NatureOfService.DOMAIN,
         menuOrder = "" + Integer.MAX_VALUE
 )
-public class MetaModelServiceDefault implements MetaModelService4 {
+public class MetaModelServiceDefault implements MetaModelService5 {
 
     @SuppressWarnings("unused")
     private final static Logger LOG = LoggerFactory.getLogger(MetaModelServiceDefault.class);
@@ -230,6 +236,31 @@ public class MetaModelServiceDefault implements MetaModelService4 {
     }
 
     @Override
+    public CommandWithDtoProcessor<?> commandDtoProcessorFor(final String memberIdentifier) {
+        final ApplicationFeatureId featureId = ApplicationFeatureId
+                .newFeature(ApplicationFeatureType.MEMBER, memberIdentifier);
+
+        final ObjectSpecId objectSpecId = featureId.getObjectSpecId();
+        if(objectSpecId == null) {
+            return null;
+        }
+
+        final ObjectSpecification spec = specificationLookup.lookupBySpecId(objectSpecId);
+        if(spec == null) {
+            return null;
+        }
+        final ObjectMember objectMemberIfAny = spec.getMember(featureId.getMemberName());
+        if (objectMemberIfAny == null) {
+            return null;
+        }
+        final CommandFacet commandFacet = objectMemberIfAny.getFacet(CommandFacet.class);
+        if(commandFacet == null) {
+            return null;
+        }
+        return commandFacet.getProcessor();
+    }
+
+    @Override
     public AppManifest2 getAppManifest2() {
         AppManifest appManifest = getAppManifest();
         return appManifest instanceof AppManifest2 ? (AppManifest2) appManifest : null;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index f1674ca..4e753eb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -53,6 +53,7 @@ import org.apache.isis.core.metamodel.interactions.ObjectTitleContext;
 import org.apache.isis.core.metamodel.interactions.ObjectValidityContext;
 import org.apache.isis.core.metamodel.spec.feature.ObjectActionContainer;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationContainer;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
 
@@ -79,6 +80,8 @@ public interface ObjectSpecification extends Specification, ObjectActionContaine
     @Deprecated
     public final static Comparator<ObjectSpecification> COMPARATOR_SHORT_IDENTIFIER_IGNORE_CASE = Comparators.SHORT_IDENTIFIER_IGNORE_CASE;
 
+    ObjectMember getMember(String memberId);
+
     class Comparators{
         private Comparators(){}
         public final static Comparator<ObjectSpecification> FULLY_QUALIFIED_CLASS_NAME = new Comparator<ObjectSpecification>() {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index cedeffe..a32eb65 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -673,6 +673,20 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
         };
     };
 
+    @Override
+    public ObjectMember getMember(final String memberId) {
+        final ObjectAction objectAction = getObjectAction(memberId);
+        if(objectAction != null) {
+            return objectAction;
+        }
+        final ObjectAssociation association = getAssociation(memberId);
+        if(association != null) {
+            return association;
+        }
+        return null;
+    }
+
+
     /**
      * The association with the given {@link ObjectAssociation#getId() id}.
      * 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index 8f0614a..3d5a942 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -49,6 +49,7 @@ import org.apache.isis.core.metamodel.spec.Persistability;
 import org.apache.isis.core.metamodel.spec.feature.Contributed;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 
@@ -76,6 +77,19 @@ public class ObjectSpecificationStub extends FacetHolderImpl implements ObjectSp
     }
 
     @Override
+    public ObjectMember getMember(final String memberId) {
+        final ObjectAction objectAction = getObjectAction(memberId);
+        if(objectAction != null) {
+            return objectAction;
+        }
+        final ObjectAssociation association = getAssociation(memberId);
+        if(association != null) {
+            return association;
+        }
+        return null;
+    }
+
+    @Override
     public Class<?> getCorrespondingClass() {
         try {
             return Class.forName(name);
diff --git a/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.1.xsd b/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.1.xsd
index ab26815..eb61cb1 100644
--- a/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.1.xsd
+++ b/core/schema/src/main/resources/org/apache/isis/schema/common/common-1.1.xsd
@@ -43,6 +43,8 @@
             <xs:element name="enum" type="enumDto"/>
             <xs:element name="reference" type="oidDto"/>
             <xs:element name="collection" type="collectionDto"/>
+            <xs:element name="blob" type="blobDto"/>
+            <xs:element name="clob" type="clobDto"/>
         </xs:choice>
     </xs:complexType>
 
@@ -80,6 +82,34 @@
         <xs:attribute name="null" use="optional" type="xs:boolean"/>
     </xs:complexType>
 
+    <xs:complexType name="blobDto">
+        <xs:annotation>
+            <xs:documentation>A collection of (argument) values
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="name" type="xs:string"/>
+            <xs:element name="mimeType" type="xs:string"/>
+            <xs:element name="bytes" type="xs:hexBinary"/>
+        </xs:sequence>
+        <xs:attribute name="type" use="required" type="valueType"/>
+        <xs:attribute name="null" use="optional" type="xs:boolean"/>
+    </xs:complexType>
+
+    <xs:complexType name="clobDto">
+        <xs:annotation>
+            <xs:documentation>A collection of (argument) values
+            </xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:element name="name" type="xs:string"/>
+            <xs:element name="mimeType" type="xs:string"/>
+            <xs:element name="chars" type="xs:string"/>
+        </xs:sequence>
+        <xs:attribute name="type" use="required" type="valueType"/>
+        <xs:attribute name="null" use="optional" type="xs:boolean"/>
+    </xs:complexType>
+
     <xs:complexType name="oidsDto">
         <xs:annotation>
             <xs:documentation>A list of OIDs
@@ -126,6 +156,8 @@
             <xs:enumeration value="enum"/>
             <xs:enumeration value="reference"/>
             <xs:enumeration value="collection"/>
+            <xs:enumeration value="blob"/>
+            <xs:enumeration value="clob"/>
             <xs:enumeration value="void">
                 <xs:annotation>
                     <xs:documentation>Not valid to be used as the parameter type of an action; can be used as its return type.

-- 
To stop receiving notification emails like this one, please contact
danhaywood@apache.org.