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:56 UTC

[isis] 05/05: ISIS-1569: various fixes for ContentMappingService (while manual testing of replication)

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 dfe09c4a0c53d560a8c3c43735e24f7d57814b37
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Jan 29 09:20:25 2018 +0000

    ISIS-1569: various fixes for ContentMappingService (while manual testing of replication)
    
    ContentMappingServiceForCommandsDto
    - allows to be used from menu services
    - if mapping returns null, then just ignore (so can avoid replicating certain commands)
    
    CommandWithDtoProcessor
    - decided to move abstract implementations up to top-level
    - introduced new Null implementation (to ignore)
    
    CommandWithDtoProcessor
    - no longer generic
    
    CommonDtoUtils
    - for blob and clob values of parameters, continue to save as REFERENCE rather than BLOB/CLOB
    
    ApplicationFeatureId
    - fix for getObjectSpecId for classes whose objectType doesn't include a "packageName"
---
 .../guides/rgant/_rgant-Action_command.adoc        | 122 +++++++++++++++++++--
 .../conmap/ContentMappingServiceForCommandDto.java |  31 +++---
 .../ContentMappingServiceForCommandsDto.java       |  16 ++-
 .../services/command/CommandWithDtoProcessor.java  |  39 +++----
 .../CommandWithDtoProcessorForActionAbstract.java  |  41 +++++++
 ...ommandWithDtoProcessorForPropertyAbstract.java} |  22 ++--
 .../services/metamodel/MetaModelService5.java      |   2 +-
 .../apache/isis/schema/utils/CommonDtoUtils.java   |   8 +-
 .../command/CommandFacetForActionAnnotation.java   |   4 +-
 .../facets/actions/command/CommandFacet.java       |   2 +-
 .../actions/command/CommandFacetAbstract.java      |   6 +-
 .../command/CommandFacetForPropertyAnnotation.java |   4 +-
 .../services/appfeat/ApplicationFeatureId.java     |   9 +-
 .../metamodel/MetaModelServiceDefault.java         |   3 +-
 14 files changed, 236 insertions(+), 73 deletions(-)

diff --git a/adocs/documentation/src/main/asciidoc/guides/rgant/_rgant-Action_command.adoc b/adocs/documentation/src/main/asciidoc/guides/rgant/_rgant-Action_command.adoc
index 99adbdd..cf2a952 100644
--- a/adocs/documentation/src/main/asciidoc/guides/rgant/_rgant-Action_command.adoc
+++ b/adocs/documentation/src/main/asciidoc/guides/rgant/_rgant-Action_command.adoc
@@ -5,7 +5,10 @@
 :_imagesdir: images/
 
 
-The `@Action(command=...)` attribute (and the related `@Action(commandPersistence=...)` and  `@Action(commandExecuteIn=...)` attributes) allows an action invocation to be made into a concrete object such that it can be inspected and persisted.  The primary use case for this is enhanced profiling/auditing, and it also supports the deferring the execution of the action such that it can be invoked in the background.
+The `@Action(command=...)` attribute (and the related `@Action(commandPersistence=...)` and  `@Action(commandExecuteIn=...)` attributes) allows an action invocation to be made into a concrete object such that it can be inspected and persisted.
+The primary use case for this are to support the deferring the execution of the action such that it can be invoked in the background, and to replay commands in a master/slave configuration.
+
+
 
 The annotation works with (and is influenced by the behaviour of) a number of domain services:
 
@@ -19,7 +22,8 @@ Each action invocation is reified by the xref:../rgsvc/rgsvc.adoc#_rgsvc_applica
 
 If an appropriate `CommandService` is configured (for example using (non-ASF) link:http://platform.incode.org/modules/spi/command/spi-command.html[Incode Platform's command] module), then the `Command` itself is persisted.
 
-By default, actions are invoked in directly in the thread of the invocation.   If there is an implementation of `BackgroundCommandService` (as the (non-ASF) link:http://platform.incode.org[Incode Platform^]'s command module does provide), then this means in turn that the `BackgroundService` can be used by the domain object code to programmatically create background ``Command``s.
+By default, actions are invoked in directly in the thread of the invocation.
+If there is an implementation of `BackgroundCommandService` (as the (non-ASF) link:http://platform.incode.org[Incode Platform^]'s command module does provide), then this means in turn that the `BackgroundService` can be used by the domain object code to programmatically create background ``Command``s.
 
 [NOTE]
 ====
@@ -70,8 +74,7 @@ corresponds to the behaviour described above; the `Command` object is persisted
 
 == `commandPersistence()`
 
-If the action has been reified, then the `commandPersistence()` attribute determines whether that `Command` object
-should then also be persisted (the default), or not persisted, or only if hinted.
+If the action has been reified, then the `commandPersistence()` attribute determines whether that `Command` object should then also be persisted (the default), or not persisted, or only if hinted.
 
 To explain this last alternative:
 
@@ -107,7 +110,6 @@ will prevent the parent `Command` object from being persisted, _even if_ a child
 
 
 
-
 == `commandExecuteIn()`
 
 For persisted commands, the `commandExecuteIn()` attribute determines whether the `Command` should be executed in the foreground (the default) or executed in the background.
@@ -126,9 +128,115 @@ public class Order {
 }
 ----
 
-will result in the `Command` being persisted but its execution deferred to a background execution mechanism. The
-returned object from this action is the persisted `Command` itself.
+will result in the `Command` being persisted but its execution deferred to a background execution mechanism.
+The returned object from this action is the persisted `Command` itself.
+
+
+
 
+== `commandWithDtoProcessor()`
+
+The `commandWithDtoProcessor()` attribute allows an implementation of `CommandWithDtoProcessor` to be specified.
+This has the following API:
+
+[source,java]
+----
+public interface CommandWithDtoProcessor {
+    CommandDto process(CommandWithDto commandWithDto);
+}
+----
+
+This interface is used by the framework-provided implementations of `ContentMappingService` for the REST API, allowing ``Command``s implementations that also implement `CommandWithDto` to be further customised as they are serialized out.
+The primary use case for this capability is in support of master/slave replication.
+
+* on the master, ``Command``s are serialized to XML.
+This includes the identity of the target object and the value of all parameters.
+However, any ``Blob``s and ``Clob``s are deliberately excluded from this XML (they are instead stored as references).
+
+* replaying ``Command``s requires this missing parameter information to be reinstated.
+The `CommandWithDtoProcessor` therefore offers a hook to dynamically re-attach the missing `Blob` or `Clob` argument.
+
+
+As a special case, returning `null` means that the command's DTO is effectively excluded when retrieving the list of commands.
+If replicating from master to slave, this effectively allows certain commands to be ignored.
+The `CommandWithDtoProcessor.Null` class provides a convenience implementation for this requirement.
+
+[NOTE]
+====
+If `commandWithDtoProcessor()` is specified, then `command()` is assumed to be ENABLED.
+====
 
 
 
+=== Example implementation
+
+Consider the following method:
+
+[source,java]
+----
+@Action(
+    domainEvent = IncomingDocumentRepository.UploadDomainEvent.class,
+    commandWithDtoProcessor = DeriveBlobArg0FromReturnedDocument.class
+)
+public Document upload(final Blob blob) {
+    final String name = blob.getName();
+    final DocumentType type = DocumentTypeData.INCOMING.findUsing(documentTypeRepository);
+    final ApplicationUser me = meService.me();
+    String atPath = me != null ? me.getAtPath() : null;
+    if (atPath == null) {
+        atPath = "/";
+    }
+    return incomingDocumentRepository.upsertAndArchive(type, atPath, name, blob);
+}
+----
+
+The `Blob` argument will not be persisted in the memento of the `Command`, but the information is implicitly available in the `Document` that is returned by the action.
+The `DeriveBlobArg0FromReturnedDocument` processor retrieves this information and dynamically adds:
+
+[source,java]
+----
+public class DeriveBlobArg0FromReturnedDocument
+        extends CommandWithDtoProcessorForActionAbstract {
+
+    @Override
+    public CommandDto process(final CommandWithDto commandWithDto) {
+        final CommandDto commandDto = commandWithDto.asDto();
+        final Bookmark result = commandWithDto.getResult();
+        if(result == null) {
+            return commandDto;
+        }
+        try {
+            final Document document = bookmarkService.lookup(result, Document.class);
+            if (document != null) {
+                ParamDto paramDto = getParamDto(commandDto, 0);
+                CommonDtoUtils.setValueOn(paramDto, ValueType.BLOB, document.getBlob(), bookmarkService);
+            }
+        } catch(Exception ex) {
+            return commandDto;
+        }
+        return commandDto;
+    }
+    @Inject
+    BookmarkService bookmarkService;
+}
+----
+
+
+=== Null implementation
+
+
+
+[source,xml]
+----
+    /**
+     * Convenience implementation to simply indicate that no DTO should be returned for a command,
+     * effectively ignoring it for replay purposes.
+     */
+    public static class Null implements CommandWithDtoProcessor {
+        @Override
+        public CommandDto process(final CommandWithDto commandWithDto) {
+            return null;
+        }
+    }
+
+----
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 5e520e2..e3050c3 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
@@ -44,38 +44,41 @@ public class ContentMappingServiceForCommandDto implements ContentMappingService
             return null;
         }
 
-        return asDto(object, metaModelService);
+        return asProcessedDto(object, metaModelService);
     }
 
-    static CommandDto asDto(
+    /**
+     * Not part of the {@link ContentMappingService} API.
+     */
+    @Programmatic
+    public CommandDto map(final CommandWithDto commandWithDto) {
+        return asProcessedDto(commandWithDto, metaModelService);
+    }
+
+    static CommandDto asProcessedDto(
             final Object object,
             final MetaModelService5 metaModelService) {
 
-        if(object instanceof CommandWithDto) {
-            final CommandWithDto commandWithDto = (CommandWithDto) object;
-            return process(commandWithDto, metaModelService);
+        if (!(object instanceof CommandWithDto)) {
+            return null;
         }
-        return null;
+        final CommandWithDto commandWithDto = (CommandWithDto) object;
+        return asProcessedDto(commandWithDto, metaModelService);
     }
 
-    private static CommandDto process(
+    private static CommandDto asProcessedDto(
             CommandWithDto commandWithDto,
             final MetaModelService5 metaModelService) {
         final CommandDto commandDto = commandWithDto.asDto();
-        final CommandWithDtoProcessor<?> commandWithDtoProcessor =
+        final CommandWithDtoProcessor commandWithDtoProcessor =
                 metaModelService.commandDtoProcessorFor(commandDto.getMember().getLogicalMemberIdentifier());
         if (commandWithDtoProcessor == null) {
             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 50ecddd..4ba434e 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
@@ -43,8 +43,16 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
             return null;
         }
 
+        return map(object);
+    }
+
+    /**
+     * Not part of the {@link ContentMappingService} API.
+     */
+    @Programmatic
+    public CommandsDto map(final Object object) {
         if(object instanceof CommandsDto) {
-            return object;
+            return ((CommandsDto) object);
         }
 
         CommandDto commandDto = asDto(object, metaModelService5);
@@ -62,8 +70,8 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
                 if(objAsCommandDto != null) {
                     commandsDto.getCommandDto().add(objAsCommandDto);
                 } else {
-                    // ignore entire list because found something that is not convertible.
-                    return new CommandsDto();
+                    // simply ignore.
+                    // this is the means by which we can avoid replicating commands.
                 }
             }
             return commandsDto;
@@ -74,7 +82,7 @@ public class ContentMappingServiceForCommandsDto implements ContentMappingServic
     }
 
     private static CommandDto asDto(final Object object, MetaModelService5 metaModelService5) {
-        return ContentMappingServiceForCommandDto.asDto(object, metaModelService5);
+        return ContentMappingServiceForCommandDto.asProcessedDto(object, metaModelService5);
     }
 
     @Inject
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
index 8785ea8..7511a9e 100644
--- 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
@@ -19,40 +19,27 @@
 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<?>> {
+public interface CommandWithDtoProcessor {
 
+    /**
+     * Returning <tt>null</tt> means that the command's DTO is effectively excluded from any list.
+     * If replicating from master to slave, this allows commands that can't be replicated to be ignored.
+     */
     @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);
+    /**
+     * Convenience implementation to simply indicate that no DTO should be returned for a command,
+     * effectively ignoring it for replay purposes.
+     */
+    public static class Null implements CommandWithDtoProcessor {
+        @Override
+        public CommandDto process(final CommandWithDto commandWithDto) {
+            return null;
         }
     }
 
-    abstract class ForPropertyAbstract<E extends PropertyDomainEvent<?,?>> extends Abstract<E> {
-        protected PropertyDto getPropertyDto(final CommandDto commandDto) {
-            return (PropertyDto) commandDto.getMember();
-        }
-    }
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForActionAbstract.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForActionAbstract.java
new file mode 100644
index 0000000..8b755b3
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForActionAbstract.java
@@ -0,0 +1,41 @@
+/*
+ *  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.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;
+
+/**
+ * Convenience adapter for command processors for action invocations.
+ */
+public abstract class CommandWithDtoProcessorForActionAbstract implements CommandWithDtoProcessor {
+    protected CommandDto asDto(final CommandWithDto commandWithDto) {
+        return commandWithDto.asDto();
+    }
+    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);
+    }
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForPropertyAbstract.java
similarity index 59%
copy from core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
copy to core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForPropertyAbstract.java
index 0230694..6f0251f 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/command/CommandWithDtoProcessorForPropertyAbstract.java
@@ -16,14 +16,20 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.applib.services.metamodel;
+package org.apache.isis.applib.services.command;
 
-import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
-
-public interface MetaModelService5 extends MetaModelService4 {
-
-    @Programmatic
-    CommandWithDtoProcessor<?> commandDtoProcessorFor(String memberIdentifier);
+import org.apache.isis.schema.cmd.v1.CommandDto;
+import org.apache.isis.schema.cmd.v1.PropertyDto;
 
+/**
+ * Convenience adapter for command processors for property edits.
+ */
+public abstract class CommandWithDtoProcessorForPropertyAbstract
+        implements CommandWithDtoProcessor {
+    protected CommandDto asDto(final CommandWithDto commandWithDto) {
+        return commandWithDto.asDto();
+    }
+    protected PropertyDto getPropertyDto(final CommandDto commandDto) {
+        return (PropertyDto) commandDto.getMember();
+    }
 }
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
index 0230694..458f609 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/metamodel/MetaModelService5.java
@@ -24,6 +24,6 @@ import org.apache.isis.applib.services.command.CommandWithDtoProcessor;
 public interface MetaModelService5 extends MetaModelService4 {
 
     @Programmatic
-    CommandWithDtoProcessor<?> commandDtoProcessorFor(String memberIdentifier);
+    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 239aea8..bba378f 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
@@ -435,7 +435,13 @@ public final class CommonDtoUtils {
 
         paramDto.setName(parameterName);
 
-        final ValueType valueType = CommonDtoUtils.asValueType(parameterType);
+        ValueType valueType = CommonDtoUtils.asValueType(parameterType);
+        // this hack preserves previous behaviour before we were able to serialize blobs and clobs into XML
+        // however, we also don't want this new behaviour for parameter arguments
+        // (else these large objects could end up being persisted).
+        if(valueType == ValueType.BLOB) valueType = ValueType.REFERENCE;
+        if(valueType == ValueType.CLOB) valueType = ValueType.REFERENCE;
+
         paramDto.setType(valueType);
 
         CommonDtoUtils.setValueOn(paramDto, valueType, arg, bookmarkService);
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 4b0089e..2a455bb 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
@@ -45,7 +45,7 @@ public class CommandFacetForActionAnnotation extends CommandFacetAbstract {
         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);
+        final CommandWithDtoProcessor processor = (CommandWithDtoProcessor) newProcessorElseNull(processorClass);
 
         if(processor != null) {
             commandReification = CommandReification.ENABLED;
@@ -91,7 +91,7 @@ public class CommandFacetForActionAnnotation extends CommandFacetAbstract {
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final CommandWithDtoProcessor<?> processor,
+            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/command/CommandFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/command/CommandFacet.java
index d3ab0b1..45d842c 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
@@ -50,5 +50,5 @@ public interface CommandFacet extends Facet {
      */
     public boolean isDisabled();
 
-    public CommandWithDtoProcessor<?> getProcessor();
+    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 0c3576d..22812ef 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
@@ -45,13 +45,13 @@ public abstract class CommandFacetAbstract extends MarkerFacetAbstract implement
     private final Persistence persistence;
     private final ExecuteIn executeIn;
     private final Enablement enablement;
-    private final CommandWithDtoProcessor<?> processor;
+    private final CommandWithDtoProcessor processor;
 
     public CommandFacetAbstract(
             final Persistence persistence,
             final ExecuteIn executeIn,
             final Enablement enablement,
-            final CommandWithDtoProcessor<?> processor,
+            final CommandWithDtoProcessor processor,
             final FacetHolder holder,
             final ServicesInjector servicesInjector) {
         super(type(), holder);
@@ -86,7 +86,7 @@ public abstract class CommandFacetAbstract extends MarkerFacetAbstract implement
     }
 
     @Override
-    public CommandWithDtoProcessor<?> getProcessor() {
+    public CommandWithDtoProcessor getProcessor() {
         return processor;
     }
 
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 33b04f7..12971c4 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
@@ -45,7 +45,7 @@ public class CommandFacetForPropertyAnnotation extends CommandFacetAbstract {
         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);
+        final CommandWithDtoProcessor processor = (CommandWithDtoProcessor) newProcessorElseNull(processorClass);
 
         if(processor != null) {
             commandReification = CommandReification.ENABLED;
@@ -82,7 +82,7 @@ public class CommandFacetForPropertyAnnotation extends CommandFacetAbstract {
             final ExecuteIn executeIn,
             final Enablement enablement,
             final FacetHolder holder,
-            final CommandWithDtoProcessor<?> processor,
+            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/services/appfeat/ApplicationFeatureId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/appfeat/ApplicationFeatureId.java
index 787c8fe..76a81fc 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
@@ -32,6 +32,7 @@ import com.google.common.base.Joiner;
 import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 import com.google.common.io.BaseEncoding;
@@ -186,9 +187,13 @@ public class ApplicationFeatureId implements Comparable<ApplicationFeatureId>, S
         if (getClassName() == null) {
             return null;
         }
+
         final StringBuilder buf = new StringBuilder();
-        buf.append(getPackageName());
-        buf.append(".").append(getClassName());
+        if(!Strings.isNullOrEmpty(getPackageName())) {
+            buf.append(getPackageName()).append(".");
+        }
+        buf.append(getClassName());
+
         return ObjectSpecId.of(buf.toString());
     }
 
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 ad82e89..8bd0b71 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,7 +33,6 @@ 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;
@@ -236,7 +235,7 @@ public class MetaModelServiceDefault implements MetaModelService5 {
     }
 
     @Override
-    public CommandWithDtoProcessor<?> commandDtoProcessorFor(final String memberIdentifier) {
+    public CommandWithDtoProcessor commandDtoProcessorFor(final String memberIdentifier) {
         final ApplicationFeatureId featureId = ApplicationFeatureId
                 .newFeature(ApplicationFeatureType.MEMBER, memberIdentifier);
 

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