You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2021/05/05 11:49:03 UTC

[isis] branch master updated: ISIS-2645: MM: make Identifier a first class citizen of ObjectMember(s)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2892201  ISIS-2645: MM: make Identifier a first class citizen of ObjectMember(s)
2892201 is described below

commit 2892201c4cc402f7ad3c9de2c35cb8523a478809
Author: Andi Huber <ah...@apache.org>
AuthorDate: Wed May 5 13:48:45 2021 +0200

    ISIS-2645: MM: make Identifier a first class citizen of ObjectMember(s)
---
 ...reteTypeToBeIncludedWithMetamodelValidator.java |  12 +-
 .../DeriveMixinMembersPostProcessor.java           |  11 +-
 .../core/metamodel/spec/ObjectSpecification.java   |  72 ++++++------
 .../core/metamodel/spec/feature/ObjectMember.java  |   7 ++
 .../specloader/specimpl/MixedInMember.java         |  11 +-
 .../specloader/specimpl/ObjectActionDefault.java   |  16 ++-
 .../specloader/specimpl/ObjectActionMixedIn.java   |  42 ++-----
 .../specimpl/ObjectAssociationAbstract.java        |   4 +-
 .../specloader/specimpl/ObjectMemberAbstract.java  |  21 ++--
 .../specloader/specimpl/ObjectMemberContainer.java |  76 +++++++------
 .../specimpl/ObjectSpecificationAbstract.java      | 126 ++++++++++-----------
 .../specimpl/OneToManyAssociationDefault.java      |  16 ++-
 .../specimpl/OneToManyAssociationMixedIn.java      |  35 ++----
 .../specimpl/OneToOneAssociationDefault.java       |  13 ++-
 .../specimpl/OneToOneAssociationMixedIn.java       |  36 ++----
 .../{Predicates.java => _SpecPredicates.java}      |   2 +-
 .../specimpl/dflt/ObjectSpecificationDefault.java  |   6 +-
 .../objects/ObjectActionLayoutXmlDefaultTest.java  |  12 +-
 .../objects/OneToManyAssociationDefaultTest.java   |  10 +-
 .../metamodel/MetaModelServiceDefaultTest.java     |   4 +-
 .../specimpl/ObjectAssociationAbstractTest.java    |   7 +-
 ...ObjectAssociationAbstractTest_alwaysHidden.java |  11 +-
 .../specimpl/OneToOneAssociationAbstractTest.java  |  10 +-
 .../objectref/ObjectReferenceFieldFactory.java     |  16 +--
 .../resources/DomainTypeResourceServerside.java    |  24 ++--
 25 files changed, 287 insertions(+), 313 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator.java
index 62fbbfd..cf5aed7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator.java
@@ -31,16 +31,16 @@ import org.apache.isis.core.metamodel.specloader.validator.ValidationFailure;
 import lombok.NonNull;
 import lombok.val;
 
-public class ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator 
+public class ActionAnnotationShouldEnforceConcreteTypeToBeIncludedWithMetamodelValidator
 extends MetaModelVisitingValidatorAbstract {
 
     @Override
     public void validate(@NonNull ObjectSpecification spec) {
-        if(spec.getBeanSort()==BeanSort.UNKNOWN 
+        if(spec.getBeanSort()==BeanSort.UNKNOWN
                 && !spec.isAbstract()) {
-        
-            val actions = spec.streamActions(MixedIn.INCLUDED).collect(Collectors.toList());
-            
+
+            val actions = spec.streamActions(MixedIn.EXCLUDED).collect(Collectors.toList());
+
             final int numActions = actions.size();
             if (numActions > 0) {
 
@@ -56,7 +56,7 @@ extends MetaModelVisitingValidatorAbstract {
                         numActions,
                         actionIds);
             }
-            
+
         }
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/DeriveMixinMembersPostProcessor.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/DeriveMixinMembersPostProcessor.java
index 62fe6bf..12c5d61 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/DeriveMixinMembersPostProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/postprocessors/DeriveMixinMembersPostProcessor.java
@@ -38,19 +38,20 @@ implements ObjectSpecificationPostProcessor, MetaModelContextAware {
     @Override
     public void postProcess(final ObjectSpecification objectSpecification) {
 
-        //TODO none of these 3 streams are actually consumed!
-        
+        // calling count on these 3 streams so these are actually consumed,
+        // as a side-effect the meta-model gets (further) populated
+
         // all the actions of this type
         val actionTypes = metaModelContext.getSystemEnvironment().isPrototyping()
                 ? ActionType.USER_AND_PROTOTYPE
                 : ActionType.USER_ONLY;
-        objectSpecification.streamActions(actionTypes, MixedIn.INCLUDED);
+        objectSpecification.streamActions(actionTypes, MixedIn.INCLUDED).count();
 
         // and all the collections of this type
-        objectSpecification.streamCollections(MixedIn.INCLUDED);
+        objectSpecification.streamCollections(MixedIn.INCLUDED).count();
 
         // and all the properties of this type
-        objectSpecification.streamProperties(MixedIn.INCLUDED);
+        objectSpecification.streamProperties(MixedIn.INCLUDED).count();
 
     }
 
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 f1ecbf6..d2bc4b0 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
@@ -81,41 +81,41 @@ import lombok.val;
  * first, and then later work out its internals. Hence we create
  * {@link ObjectSpecification}s as we need them, and then introspect them later.
  */
-public interface ObjectSpecification 
-extends 
-    Specification, 
+public interface ObjectSpecification
+extends
+    Specification,
     ObjectActionContainer,
-    ObjectAssociationContainer, 
-    Hierarchical, 
+    ObjectAssociationContainer,
+    Hierarchical,
     DefaultProvider,
     HasLogicalType {
 
     final class Comparators{
         private Comparators(){}
 
-        public static final Comparator<ObjectSpecification> FULLY_QUALIFIED_CLASS_NAME = 
-                (final ObjectSpecification o1, final ObjectSpecification o2) -> 
+        public static final Comparator<ObjectSpecification> FULLY_QUALIFIED_CLASS_NAME =
+                (final ObjectSpecification o1, final ObjectSpecification o2) ->
         o1.getFullIdentifier().compareTo(o2.getFullIdentifier());
 
-        public static final Comparator<ObjectSpecification> SHORT_IDENTIFIER_IGNORE_CASE = 
-                (final ObjectSpecification s1, final ObjectSpecification s2) -> 
+        public static final Comparator<ObjectSpecification> SHORT_IDENTIFIER_IGNORE_CASE =
+                (final ObjectSpecification s1, final ObjectSpecification s2) ->
         s1.getShortIdentifier().compareToIgnoreCase(s2.getShortIdentifier());
     }
-    
+
     /**
      * @param memberId
-     * @return optionally the ObjectMember associated with given {@code memberId}, 
+     * @return optionally the ObjectMember associated with given {@code memberId},
      * based on whether given memberId exists
      */
     Optional<? extends ObjectMember> getMember(String memberId);
-    
+
     /**
      * @param method
-     * @return optionally the ObjectMember associated with given {@code method}, 
+     * @return optionally the ObjectMember associated with given {@code method},
      * based on whether such an association exists
      */
     Optional<? extends ObjectMember> getMember(Method method);
-    
+
     default ObjectMember getMemberElseFail(final @NonNull Method method) {
         return getMember(method).orElseThrow(()->{
             val methodName = method.getName();
@@ -242,7 +242,7 @@ extends
     Optional<ObjectSpecification> getElementSpecification();
 
     /**
-     * 
+     *
      * @since 2.0
      */
     BeanSort getBeanSort();
@@ -372,16 +372,16 @@ extends
 
     /**
      * Whether this specification represents a bean, that is a managed bean
-     * with scoped life-cycle, available for dependency injection. 
+     * with scoped life-cycle, available for dependency injection.
      */
     default boolean isManagedBean() {
         return getManagedBeanName()!=null;
     }
-    
+
     /**
      * If this specification represents a bean, that is a managed bean, then
      * returns the bean's name/id as recognized by the IoC container.
-     * <p>Otherwise returns {@code null}. 
+     * <p>Otherwise returns {@code null}.
      */
     String getManagedBeanName();
 
@@ -400,6 +400,10 @@ extends
         return isViewModel() || isEntity();
     }
 
+    default boolean isEntityOrViewModelOrAbstract() {
+        return isViewModel() || isEntity() || isAbstract();
+    }
+
     default boolean isEntity() {
         return getBeanSort().isEntity();
     }
@@ -425,7 +429,7 @@ extends
             throw new UnrecoverableException("Failed to create instance of type " + getFullIdentifier(), e);
         }
 
-        return newInstance; 
+        return newInstance;
     }
 
     /**
@@ -433,7 +437,7 @@ extends
      * @since 2.0
      */
     default Stream<FacetHolder> streamFacetHolders(){
-        
+
         val self = Stream.of(this);
         val actions = streamActions(MixedIn.EXCLUDED);
         val actionParameters = streamActions(MixedIn.EXCLUDED)
@@ -444,7 +448,7 @@ extends
         val collections = streamCollections(MixedIn.EXCLUDED);
 
         return _Streams.concat(self, actions, actionParameters, properties, collections);
-        
+
     }
 
     /**
@@ -455,13 +459,13 @@ extends
 
     /**
      * @return whether the corresponding type can be mapped onto a REFERENCE (schema) or an Oid,
-     * that is the type is 'identifiable' (aka 'referencable' or 'bookmarkable') 
+     * that is the type is 'identifiable' (aka 'referencable' or 'bookmarkable')
      * @since 2.0
      */
     default boolean isIdentifiable() {
         return isManagedBean() || isViewModel() || isEntity();
     }
-    
+
     /**
      * Delegates to {@link ObjectManager#createObject(org.apache.isis.core.metamodel.objectmanager.create.ObjectCreator.Request)}
      * @since 2.0
@@ -472,16 +476,16 @@ extends
         val managedObject = mmc.getObjectManager().createObject(objectCreateRequest);
         return managedObject;
     }
-    
+
     // -- TYPE COMPATIBILITY UTILITIES
-    
+
     default public void assertPojoCompatible(@Nullable Object pojo) {
-        
+
         // can do this check only when the pojo is not null, otherwise is always considered valid
         if(pojo==null) {
             return;
         }
-        
+
         if(!isPojoCompatible(pojo)) {
             val expectedType = getCorrespondingClass();
             throw _Exceptions.illegalArgument(
@@ -492,17 +496,17 @@ extends
                     expectedType, pojo.getClass(), pojo.toString());
         }
     }
-    
+
     default public boolean isPojoCompatible(Object pojo) {
-        
+
         val expectedType = getCorrespondingClass();
         val actualType = pojo.getClass();
-        
+
         if(expectedType.isAssignableFrom(actualType)
                 || ClassExtensions.equalsWhenBoxing(expectedType, actualType)) {
             return true;
         }
-        
+
         // XXX rather hard to understand ...
         // for non-scalar param types, param-spec is always the element-type spec (not the spec of any container)
         val elementSpec = getElementSpecification()
@@ -513,12 +517,12 @@ extends
     }
 
     /**
-     * @return whether corresponding class implements {@link java.io.Serializable} or 
+     * @return whether corresponding class implements {@link java.io.Serializable} or
      * {@link java.io.Externalizable}.
-     * @apiNote: per se does not tell what recreation strategy to use, the corresponding class 
+     * @apiNote: per se does not tell what recreation strategy to use, the corresponding class
      * might be an entity or a view-model or a value with eg. encodable semantics, which have
      * different object recreation mechanics
-     * @since 2.0.0 
+     * @since 2.0.0
      */
     default boolean isSerializable() {
         return
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
index 89263e7..5fb7b26 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
@@ -135,6 +135,13 @@ public interface ObjectMember extends ObjectFeature {
      */
     boolean isAction();
 
+    /**
+     * Whether this member originates from a mixin.
+     */
+    default boolean isMixedIn() {
+        return false;
+    }
+
     // /////////////////////////////////////////////////////////////
     // Debugging
     // /////////////////////////////////////////////////////////////
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/MixedInMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/MixedInMember.java
index 0d5c7d9..a049c15 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/MixedInMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/MixedInMember.java
@@ -18,7 +18,6 @@
  */
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
-import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
@@ -28,13 +27,13 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
  */
 public interface MixedInMember extends ObjectMember {
 
-    /**
-     * The id as it was originally {@link DomainObject#mixinMethod()}.
-     */
-    String getOriginalId();
-
     ObjectSpecification getMixinType();
 
     boolean hasMixinAction(ObjectAction objectAction);
+
+    @Override
+    default boolean isMixedIn() {
+        return true;
+    }
 }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
index 2ff5d1d..530b4e6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionDefault.java
@@ -25,6 +25,7 @@ import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.function.Predicate;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.exceptions.RecoverableException;
@@ -79,15 +80,22 @@ implements ObjectAction {
         return type;
     }
 
-    // -- fields
+    // -- FACTORY
+
+    public static ObjectActionDefault forMethod(final FacetedMethod facetedMethod) {
+        return new ObjectActionDefault(facetedMethod.getIdentifier(), facetedMethod);
+    }
+
+    // -- FIELDS
 
     private final _Lazy<Can<ObjectActionParameter>> parameters = _Lazy.threadSafe(this::determineParameters);
 
-    // -- constructors
+    // -- CONSTRUCTOR
 
-    public ObjectActionDefault(
+    protected ObjectActionDefault(
+            final Identifier identifier,
             final FacetedMethod facetedMethod) {
-        super(facetedMethod, FeatureType.ACTION);
+        super(identifier, facetedMethod, FeatureType.ACTION);
     }
 
     // -- ReturnType, OnType, Actions (set)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
index 0ff85e2..82ba8de 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
@@ -62,15 +62,19 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
     @Getter(onMethod = @__(@Override))
     private final FacetHolder facetHolder = new FacetHolderImpl();
 
-    private final Identifier identifier;
-
     public ObjectActionMixedIn(
             final Class<?> mixinType,
             final String mixinMethodName,
             final ObjectActionDefault mixinAction,
             final ObjectSpecification mixedInType) {
 
-        super(mixinAction.getFacetedMethod());
+        super(Identifier.actionIdentifier(
+                    LogicalType.eager(
+                            mixedInType.getCorrespondingClass(),
+                            mixedInType.getLogicalTypeName()),
+                    determineIdFrom(mixinAction),
+                    mixinAction.getFacetedMethod().getIdentifier().getMemberParameterClassNames()),
+                mixinAction.getFacetedMethod());
 
         this.mixinType = mixinType;
         this.mixinAction = mixinAction;
@@ -86,16 +90,6 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
             String memberName = determineNameFrom(mixinAction);
             this.addFacet(new NamedFacetInferred(memberName, facetHolder));
         }
-
-        // calculate the identifier
-        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
-        val memberParameterClassNames = mixinIdentifier.getMemberParameterClassNames();
-        identifier = Identifier.actionIdentifier(
-                LogicalType.eager(
-                        getOnType().getCorrespondingClass(),
-                        getOnType().getLogicalTypeName()),
-                getId(), 
-                memberParameterClassNames);
     }
 
     @Override
@@ -103,17 +97,8 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
         val target = mixinAdapterFor(mixinType, owner);
         return InteractionHead.of(owner, target);
     }
-    
-    @Override
-    public String getId() {
-        return determineIdFrom(this.mixinAction);
-    }
 
     @Override
-    public String getOriginalId() {
-        return super.getId();
-    }
-
     public boolean hasMixinAction(final ObjectAction mixinAction) {
         return this.mixinAction == mixinAction;
     }
@@ -164,28 +149,19 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
 
         final ManagedObject owner = head.getOwner();
         final ManagedObject target = mixinAdapterFor(mixinType, owner);
-        _Assert.assertEquals(target.getSpecification(), head.getTarget().getSpecification(), 
+        _Assert.assertEquals(target.getSpecification(), head.getTarget().getSpecification(),
                 "head has the wrong target (should be a mixin adapter, but is the owner adapter)");
-        
+
         setupCommand(head.getTarget(), arguments);
         return mixinAction.executeInternal(
                 head, arguments,
                 interactionInitiatedBy);
     }
 
-    /* (non-Javadoc)
-     * @see ObjectMemberAbstract#getIdentifier()
-     */
-    @Override
-    public Identifier getIdentifier() {
-        return identifier;
-    }
-
     @Override
     public ObjectSpecification getMixinType() {
         return getSpecificationLoader().loadSpecification(mixinType);
 
     }
 
-
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstract.java
index 1cd629f..cae288c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstract.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
@@ -38,11 +39,12 @@ implements ObjectAssociation {
     private final ObjectSpecification specification;
 
     public ObjectAssociationAbstract(
+            final Identifier identifier,
             final FacetedMethod facetedMethod,
             final FeatureType featureType,
             final ObjectSpecification specification) {
 
-        super(facetedMethod, featureType);
+        super(identifier, facetedMethod, featureType);
         if (specification == null) {
             throw new IllegalArgumentException("field type for '" + getId() + "' must exist");
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
index 9fd36d3..a067243 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
@@ -67,33 +67,34 @@ implements ObjectMember, HasMetaModelContext, HasFacetHolder {
     }
 
     // -- fields
-    private final String id;
+
+    private final Identifier identifier;
     private final FacetedMethod facetedMethod;
     private final FeatureType featureType;
 
     protected ObjectMemberAbstract(
+            final Identifier identifier,
             final FacetedMethod facetedMethod,
             final FeatureType featureType) {
 
-        final String id = facetedMethod.getIdentifier().getMemberName();
-        if (id == null) {
-            throw new IllegalArgumentException("Id must always be set");
-        }
+        this.identifier = identifier;
         this.facetedMethod = facetedMethod;
         this.featureType = featureType;
-        this.id = id;
+        if (getId() == null) {
+            throw new IllegalArgumentException("Id must always be set");
+        }
     }
 
     // -- Identifiers
 
     @Override
-    public String getId() {
-        return id;
+    public final String getId() {
+        return getIdentifier().getMemberName();
     }
 
     @Override
-    public Identifier getIdentifier() {
-        return getFacetedMethod().getIdentifier();
+    public final Identifier getIdentifier() {
+        return identifier;
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberContainer.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberContainer.java
index 163de52..6ceb7db 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberContainer.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberContainer.java
@@ -38,64 +38,70 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationContainer;
 import lombok.val;
 
 /**
- * Responsibility: member lookup and streaming with support for inheritance, 
+ * Responsibility: member lookup and streaming with support for inheritance,
  * based on access to declared members, super-classes and interfaces.
  * <p>
  * TODO performance: add memoization
  * <p>
- * TODO future extensions should also search the interfaces, 
+ * TODO future extensions should also search the interfaces,
  * but avoid doing redundant work when walking the type-hierarchy;
  * (current elegant recursive solution will then need some tweaks to be efficient)
  */
 public abstract class ObjectMemberContainer
-extends FacetHolderImpl 
-implements 
+extends FacetHolderImpl
+implements
     ObjectActionContainer,
-    ObjectAssociationContainer, 
+    ObjectAssociationContainer,
     Hierarchical {
 
     // -- ACTIONS
-    
+
     @Override
     public Optional<ObjectAction> getAction(String id, @Nullable ActionType type) {
 
         val declaredAction = getDeclaredAction(id); // no inheritance nor type considered
-                
+
         if(declaredAction.isPresent()) {
             // action found but if its not the right type, stop searching
             if(type!=null
                     && declaredAction.get().getType() != type) {
                 return Optional.empty();
             }
-            return declaredAction; 
+            return declaredAction;
         }
 
         return isTypeHierarchyRoot()
                 ? Optional.empty() // stop searching
                 : superclass().getAction(id, type);
     }
-    
+
     @Override
     public Stream<ObjectAction> streamActions(
-            final ImmutableEnumSet<ActionType> types, 
-            final MixedIn contributed,
+            final ImmutableEnumSet<ActionType> actionTypes,
+            final MixedIn mixedIn,
             final Consumer<ObjectAction> onActionOverloaded) {
-        
+
         val actionStream = isTypeHierarchyRoot()
-                ? streamDeclaredActions(types, contributed) // stop going deeper
+                ? streamDeclaredActions(actionTypes, mixedIn) // stop going deeper
                 : Stream.concat(
-                        streamDeclaredActions(types, contributed), 
-                        superclass().streamActions(types, contributed));
-        
+                        streamDeclaredActions(actionTypes, mixedIn),
+                        superclass().streamActions(actionTypes, mixedIn));
+
         val actionSignatures = _Sets.<String>newHashSet();
         val actionIds = _Sets.<String>newHashSet();
-        
+
         return actionStream
-        
-        // as of contributing super-classes same actions might appear more than once (overriding) 
-        .filter(action->actionSignatures.add(
-                action.getIdentifier().getMemberNameAndParameterClassNamesIdentityString()))
-        
+
+        // as of contributing super-classes same actions might appear more than once (overriding)
+        .filter(action->{
+            if(action.isMixedIn()) {
+                return true; // do not filter mixedIn actions based on signature
+            }
+            val isUnique = actionSignatures
+                    .add(action.getIdentifier().getMemberNameAndParameterClassNamesIdentityString());
+            return isUnique;
+        })
+
         // ensure we don't emit duplicates
         .filter(action->{
             val isUnique = actionIds.add(action.getId());
@@ -103,39 +109,39 @@ implements
                 onActionOverloaded.accept(action);
             }
             return isUnique;
-        }); 
+        });
     }
-    
+
     // -- ASSOCIATIONS
-    
+
     @Override
     public Optional<ObjectAssociation> getAssociation(String id) {
 
         val declaredAssociation = getDeclaredAssociation(id); // no inheritance considered
-                
+
         if(declaredAssociation.isPresent()) {
-            return declaredAssociation; 
+            return declaredAssociation;
         }
-        
+
         return isTypeHierarchyRoot()
-               ? Optional.empty() // stop searching 
+               ? Optional.empty() // stop searching
                : superclass().getAssociation(id);
     }
-    
+
     @Override
     public Stream<ObjectAssociation> streamAssociations(MixedIn contributed) {
-        
+
         if(isTypeHierarchyRoot()) {
             return streamDeclaredAssociations(contributed); // stop going deeper
         }
-        
+
         val ids = _Sets.<String>newHashSet();
-        
+
         return Stream.concat(
-                streamDeclaredAssociations(contributed), 
+                streamDeclaredAssociations(contributed),
                 superclass().streamAssociations(contributed)
         )
         .filter(association->ids.add(association.getId())); // ensure we don't emit duplicates
     }
-    
+
 }
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 e297e11..455cd8f 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
@@ -89,23 +89,23 @@ import lombok.extern.log4j.Log4j2;
 
 @EqualsAndHashCode(of = "correspondingClass", callSuper = false)
 @lombok.ToString(of = {"correspondingClass", "fullName", "beanSort"})
-@Log4j2 
-public abstract class ObjectSpecificationAbstract 
+@Log4j2
+public abstract class ObjectSpecificationAbstract
 extends ObjectMemberContainer
 implements ObjectSpecification {
 
-    /** 
+    /**
      * @implNote thread-safe
      */
     private static class Subclasses {
-        
+
         // List performs better compared to a Set, when the number of elements is low
         private Can<ObjectSpecification> classes = Can.empty();
         private final Object $lock = new Object();
 
         public void addSubclass(final ObjectSpecification subclass) {
             synchronized($lock) {
-                classes = classes.addUnique(subclass);    
+                classes = classes.addUnique(subclass);
             }
         }
 
@@ -121,48 +121,48 @@ implements ObjectSpecification {
             }
         }
     }
-    
-    
+
+
     // -- FIELDS
 
     private final PostProcessor postProcessor;
     private final FacetProcessor facetProcessor;
-    
+
     @Getter private final BeanSort beanSort;
 
     // -- ASSOCIATIONS
-    
+
     private final List<ObjectAssociation> associations = _Lists.newArrayList();
-    
+
     // defensive immutable lazy copy of associations
-    private final _Lazy<Can<ObjectAssociation>> unmodifiableAssociations = 
+    private final _Lazy<Can<ObjectAssociation>> unmodifiableAssociations =
             _Lazy.threadSafe(()->Can.ofCollection(associations));
-    
+
     // -- ACTIONS
-    
+
     private final List<ObjectAction> objectActions = _Lists.newArrayList();
-    
+
     /** not API, used for validation */
     @Getter private final Set<Method> potentialOrphans = _Sets.newHashSet();
 
     // defensive immutable lazy copy of objectActions
-    private final _Lazy<Can<ObjectAction>> unmodifiableActions = 
+    private final _Lazy<Can<ObjectAction>> unmodifiableActions =
             _Lazy.threadSafe(()->Can.ofCollection(objectActions));
 
     // partitions and caches objectActions by type; updated in sortCacheAndUpdateActions()
-    private final ListMultimap<ActionType, ObjectAction> objectActionsByType = 
+    private final ListMultimap<ActionType, ObjectAction> objectActionsByType =
             _Multimaps.newConcurrentListMultimap();
-    
+
     // -- INTERFACES
 
     private final List<ObjectSpecification> interfaces = _Lists.newArrayList();
-    
+
     // defensive immutable lazy copy of interfaces
-    private final _Lazy<Can<ObjectSpecification>> unmodifiableInterfaces = 
+    private final _Lazy<Can<ObjectSpecification>> unmodifiableInterfaces =
             _Lazy.threadSafe(()->Can.ofCollection(interfaces));
-    
-    
-    
+
+
+
     private final Subclasses directSubclasses = new Subclasses();
     // built lazily
     private Subclasses transitiveSubclasses;
@@ -220,7 +220,7 @@ implements ObjectSpecification {
     public LogicalType getLogicalType() {
         return logicalTypeLazy.get();
     }
-        
+
     private LogicalType lookupLogicalType() {
         val objectSpecIdFacet = getFacet(ObjectSpecIdFacet.class);
         if(objectSpecIdFacet == null) {
@@ -257,7 +257,7 @@ implements ObjectSpecification {
 
     @Override
     public void introspectUpTo(final IntrospectionState upTo) {
-        
+
         if(!isLessThan(upTo)) {
             return; // optimization
         }
@@ -315,7 +315,7 @@ implements ObjectSpecification {
     boolean isLessThan(IntrospectionState upTo) {
         return this.introspectionState.compareTo(upTo) < 0;
     }
-    
+
     protected abstract void introspectTypeHierarchy();
     protected abstract void introspectMembers();
 
@@ -406,7 +406,7 @@ implements ObjectSpecification {
     public String getTitle(
             final ManagedObject contextAdapterIfAny,
             final ManagedObject targetAdapter) {
-        
+
         if (titleFacet != null) {
             val titleString = titleFacet.title(contextAdapterIfAny, targetAdapter);
             if (!_Strings.isEmpty(titleString)) {
@@ -435,7 +435,7 @@ implements ObjectSpecification {
     }
 
     // -- HIERARCHICAL
-    
+
     /**
      * Determines if this class represents the same class, or a subclass, of the
      * specified class.
@@ -459,15 +459,15 @@ implements ObjectSpecification {
      */
     @Override
     public boolean isOfType(final ObjectSpecification other) {
-        
+
         val thisClass = this.getCorrespondingClass();
         val otherClass = other.getCorrespondingClass();
-        
-        return thisClass == otherClass 
+
+        return thisClass == otherClass
                 || otherClass.isAssignableFrom(thisClass);
-        
-//XXX legacy of ...        
-//        
+
+//XXX legacy of ...
+//
 //        for (val interfaceSpec : interfaces()) {
 //            if (interfaceSpec.isOfType(other)) {
 //                return true;
@@ -489,7 +489,7 @@ implements ObjectSpecification {
     }
 
     // -- NAME, DESCRIPTION, PERSISTABILITY
-    
+
     /**
      * The name according to any available {@link NamedFacet},
      * but falling back to {@link #getFullIdentifier()} otherwise.
@@ -538,28 +538,28 @@ implements ObjectSpecification {
     public <Q extends Facet> Q getFacet(final Class<Q> facetType) {
 
         synchronized(unmodifiableInterfaces) {
-        
+
             // lookup facet holder's facet
             val facets1 = _NullSafe.streamNullable(super.getFacet(facetType));
-    
+
             // lookup all interfaces
             val facets2 = _NullSafe.stream(interfaces())
                     .filter(_NullSafe::isPresent) // just in case
                     .map(interfaceSpec->interfaceSpec.getFacet(facetType));
-    
+
             // search up the inheritance hierarchy
-            val facets3 = _NullSafe.streamNullable(superclass()) 
+            val facets3 = _NullSafe.streamNullable(superclass())
                     .map(superSpec->superSpec.getFacet(facetType));
-    
+
             val facetsCombined = _Streams.concat(facets1, facets2, facets3);
 
             val notANoopFacetFilter = new NotANoopFacetFilter<Q>();
-    
+
             return facetsCombined
                     .filter(notANoopFacetFilter)
                     .findFirst()
                     .orElse(notANoopFacetFilter.noopFacet);
-        
+
         }
     }
 
@@ -601,7 +601,7 @@ implements ObjectSpecification {
 
     @Override
     public ObjectTitleContext createTitleInteractionContext(
-            final ManagedObject targetObjectAdapter, 
+            final ManagedObject targetObjectAdapter,
             final InteractionInitiatedBy interactionMethod) {
 
         return new ObjectTitleContext(targetObjectAdapter, getIdentifier(), targetObjectAdapter.titleString(null),
@@ -609,7 +609,7 @@ implements ObjectSpecification {
     }
 
     // -- SUPERCLASS, INTERFACES, SUBCLASSES, IS-ABSTRACT
-    
+
     @Override
     public ObjectSpecification superclass() {
         return superclassSpec;
@@ -675,12 +675,12 @@ implements ObjectSpecification {
                 return stream(unmodifiableAssociations.get());
             }
         }
-        
+
         synchronized(unmodifiableAssociations) {
             return stream(unmodifiableAssociations.get())
-                    .filter(MixedIn::isNotMixedIn);    
+                    .filter(MixedIn::isNotMixedIn);
         }
-        
+
     }
 
     @Override
@@ -710,23 +710,23 @@ implements ObjectSpecification {
                 .filter(objectAssociation->objectAssociation.getId().equals(id))
                 .findFirst();
     }
-    
+
 
 
     @Override
     public Stream<ObjectAction> streamDeclaredActions(
-            final ImmutableEnumSet<ActionType> types, 
-            final MixedIn contributed) {
+            final ImmutableEnumSet<ActionType> actionTypes,
+            final MixedIn mixedIn) {
         introspectUpTo(IntrospectionState.FULLY_INTROSPECTED);
 
-        if(contributed.isIncluded()) { // conditional not strictly required, could instead always go this code path 
+        if(mixedIn.isIncluded()) { // conditional not strictly required, could instead always go this code path
             createMixedInActions(); // only if not already
         }
-        
-        return types.stream()
-                .flatMap(type->stream(objectActionsByType.get(type)))
-                .filter(contributed.isIncluded()
-                        ? _Predicates.alwaysTrue() 
+
+        return actionTypes.stream()
+                .flatMap(actionType->stream(objectActionsByType.get(actionType)))
+                .filter(mixedIn.isIncluded()
+                        ? _Predicates.alwaysTrue()
                         : MixedIn::isNotMixedIn);
     }
 
@@ -746,7 +746,7 @@ implements ObjectSpecification {
     }
 
     private void forEachMixedInAssociation(
-            final Class<?> mixinType, 
+            final Class<?> mixinType,
             final Consumer<ObjectAssociation> onNewMixedInAssociation) {
 
         val specification = getSpecificationLoader().loadSpecification(mixinType,
@@ -765,7 +765,7 @@ implements ObjectSpecification {
         val mixinMethodName = mixinFacet.value();
 
         specification.streamActions(ActionType.ANY, MixedIn.INCLUDED)
-        .filter(Predicates::isMixedInAssociation)
+        .filter(_SpecPredicates::isMixedInAssociation)
         .map(ObjectActionDefault.class::cast)
         .map(Factories.mixedInAssociation(this, mixinType, mixinMethodName))
         .peek(facetProcessor::processMemberOrder)
@@ -817,7 +817,7 @@ implements ObjectSpecification {
         val mixinMethodName = mixinFacet.value();
 
         mixinSpec.streamActions(ActionType.ANY, MixedIn.INCLUDED)
-        .filter(Predicates::isMixedInAction)
+        .filter(_SpecPredicates::isMixedInAction)
         .map(ObjectActionDefault.class::cast)
         .map(Factories.mixedInAction(this, mixinType, mixinMethodName))
         .peek(facetProcessor::processMemberOrder)
@@ -826,12 +826,12 @@ implements ObjectSpecification {
     }
 
     // -- VALIDITY
-    
+
     @Override
     public Consent isValid(
-            final ManagedObject targetAdapter, 
+            final ManagedObject targetAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        
+
         return isValidResult(targetAdapter, interactionInitiatedBy).createConsent();
     }
 
@@ -894,7 +894,7 @@ implements ObjectSpecification {
         synchronized (unmodifiableActions) {
             if(!contributeeAndMixedInActionsAdded) {
                 val actions = _Lists.newArrayList(this.objectActions);
-                if (isEntityOrViewModel()) {
+                if (isEntityOrViewModelOrAbstract()) {
                     createMixedInActions(actions::add);
                 }
                 sortCacheAndUpdateActions(actions);
@@ -909,7 +909,7 @@ implements ObjectSpecification {
         synchronized (unmodifiableAssociations) {
             if(!contributeeAndMixedInAssociationsAdded) {
                 val associations = _Lists.newArrayList(this.associations);
-                if(isEntityOrViewModel()) {
+                if(isEntityOrViewModelOrAbstract()) {
                     createMixedInAssociations(associations::add);
                 }
                 sortAndUpdateAssociations(associations);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationDefault.java
index 7e41355..d40a6bf 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationDefault.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.metamodel.commons.ToString;
@@ -42,16 +43,19 @@ import lombok.val;
 public class OneToManyAssociationDefault
 extends ObjectAssociationAbstract implements OneToManyAssociation {
 
-    public OneToManyAssociationDefault(final FacetedMethod facetedMethod) {
-        this(facetedMethod, facetedMethod.getMetaModelContext()
-                .getSpecificationLoader().loadSpecification(facetedMethod.getType()));
+    public static OneToManyAssociationDefault forMethod(final FacetedMethod facetedMethod) {
+        return new OneToManyAssociationDefault(
+                facetedMethod.getIdentifier(),
+                facetedMethod,
+                facetedMethod.getMetaModelContext().getSpecificationLoader()
+                    .loadSpecification(facetedMethod.getType()));
     }
 
     protected OneToManyAssociationDefault(
+            final Identifier identifier,
             final FacetedMethod facetedMethod,
             final ObjectSpecification objectSpec) {
-
-        super(facetedMethod, FeatureType.COLLECTION, objectSpec);
+        super(identifier, facetedMethod, FeatureType.COLLECTION, objectSpec);
     }
 
     @Override
@@ -106,7 +110,7 @@ extends ObjectAssociationAbstract implements OneToManyAssociation {
 
     @Override
     public boolean isEmpty(
-            final ManagedObject parentAdapter, 
+            final ManagedObject parentAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
         // REVIEW should we be able to determine if a collection is empty
         // without loading it?
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
index 5e8cfca..41770cc 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -68,8 +68,6 @@ public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault imp
     @Getter(onMethod = @__(@Override))
     private final FacetHolder facetHolder = new FacetHolderImpl();
 
-    private final Identifier identifier;
-
     private static ObjectSpecification typeOfSpec(
             final ObjectActionDefault objectAction) {
 
@@ -90,7 +88,13 @@ public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault imp
             final Class<?> mixinType,
             final String mixinMethodName) {
 
-        super(mixinAction.getFacetedMethod(), typeOfSpec(mixinAction));
+        super(Identifier.actionIdentifier(
+                    LogicalType.eager(
+                            mixeeSpec.getCorrespondingClass(),
+                            mixeeSpec.getLogicalTypeName()),
+                    determineIdFrom(mixinAction),
+                    mixinAction.getFacetedMethod().getIdentifier().getMemberParameterClassNames()),
+                mixinAction.getFacetedMethod(), typeOfSpec(mixinAction));
 
         this.mixinType = mixinType;
         this.mixinAction = mixinAction;
@@ -124,16 +128,6 @@ public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault imp
             FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
         }
 
-        // calculate the identifier
-        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
-        val memberParameterNames = mixinIdentifier.getMemberParameterClassNames();
-
-        identifier = Identifier.actionIdentifier(
-                LogicalType.eager(
-                        mixeeSpec.getCorrespondingClass(), 
-                        mixeeSpec.getLogicalTypeName()), 
-                getId(), 
-                memberParameterNames);
     }
 
     @Override
@@ -163,21 +157,6 @@ public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault imp
     }
 
     @Override
-    public Identifier getIdentifier() {
-        return identifier;
-    }
-
-    @Override
-    public String getId() {
-        return determineIdFrom(this.mixinAction);
-    }
-
-    @Override
-    public String getOriginalId() {
-        return super.getId();
-    }
-
-    @Override
     public ObjectSpecification getOnType() {
         return mixeeSpec;
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationDefault.java
index 4322cb6..4265e14 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationDefault.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.core.metamodel.specloader.specimpl;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.commons.collections.Can;
@@ -57,16 +58,20 @@ public class OneToOneAssociationDefault
 extends ObjectAssociationAbstract
 implements OneToOneAssociation {
 
-    public OneToOneAssociationDefault(final FacetedMethod facetedMethod) {
-        this(facetedMethod, facetedMethod.getMetaModelContext()
-                .getSpecificationLoader().loadSpecification(facetedMethod.getType()));
+    public static OneToOneAssociationDefault forMethod(final FacetedMethod facetedMethod) {
+        return new OneToOneAssociationDefault(
+                facetedMethod.getIdentifier(),
+                facetedMethod,
+                facetedMethod.getMetaModelContext().getSpecificationLoader()
+                    .loadSpecification(facetedMethod.getType()));
     }
 
     protected OneToOneAssociationDefault(
+            final Identifier identifier,
             final FacetedMethod facetedMethod,
             final ObjectSpecification objectSpec) {
 
-        super(facetedMethod, FeatureType.PROPERTY, objectSpec);
+        super(identifier, facetedMethod, FeatureType.PROPERTY, objectSpec);
     }
 
     // -- visible, usable
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
index 6c591a0..317983b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
@@ -64,15 +64,19 @@ public class OneToOneAssociationMixedIn extends OneToOneAssociationDefault imple
     @Getter(onMethod = @__(@Override))
     private final FacetHolder facetHolder = new FacetHolderImpl();
 
-    private final Identifier identifier;
-
     public OneToOneAssociationMixedIn(
             final ObjectActionDefault mixinAction,
             final ObjectSpecification mixeeSpec,
             final Class<?> mixinType,
             final String mixinMethodName) {
 
-        super(mixinAction.getFacetedMethod(), mixinAction.getReturnType());
+        super(Identifier.actionIdentifier(
+                    LogicalType.eager(
+                            mixeeSpec.getCorrespondingClass(),
+                            mixeeSpec.getLogicalTypeName()),
+                    determineIdFrom(mixinAction),
+                    mixinAction.getFacetedMethod().getIdentifier().getMemberParameterClassNames()),
+                mixinAction.getFacetedMethod(), mixinAction.getReturnType());
 
         this.mixinType = mixinType;
         this.mixinAction = mixinAction;
@@ -103,17 +107,6 @@ public class OneToOneAssociationMixedIn extends OneToOneAssociationDefault imple
             FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
         }
 
-
-        // calculate the identifier
-        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
-        val memberParameterClassNames = mixinIdentifier.getMemberParameterClassNames();
-
-        identifier = Identifier.actionIdentifier(
-                LogicalType.eager(
-                        mixeeSpec.getCorrespondingClass(), 
-                        mixeeSpec.getLogicalTypeName()),
-                getId(), 
-                memberParameterClassNames);
     }
 
     @Override
@@ -145,21 +138,6 @@ public class OneToOneAssociationMixedIn extends OneToOneAssociationDefault imple
     }
 
     @Override
-    public Identifier getIdentifier() {
-        return identifier;
-    }
-
-    @Override
-    public String getId() {
-        return determineIdFrom(this.mixinAction);
-    }
-
-    @Override
-    public String getOriginalId() {
-        return super.getId();
-    }
-
-    @Override
     public ObjectSpecification getOnType() {
         return mixeeSpec;
     }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/Predicates.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/_SpecPredicates.java
similarity index 98%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/Predicates.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/_SpecPredicates.java
index a74443b..15ee2a0 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/Predicates.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/_SpecPredicates.java
@@ -27,7 +27,7 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import lombok.val;
 
 /** package private utility */
-final class Predicates {
+final class _SpecPredicates {
     
     // -- LOWER LEVEL
     
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
index e3151ff..5459595 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/dflt/ObjectSpecificationDefault.java
@@ -216,9 +216,9 @@ implements FacetHolder {
 
     private ObjectAssociation createAssociation(final FacetedMethod facetMethod) {
         if (facetMethod.getFeatureType().isCollection()) {
-            return new OneToManyAssociationDefault(facetMethod);
+            return OneToManyAssociationDefault.forMethod(facetMethod);
         } else if (facetMethod.getFeatureType().isProperty()) {
-            return new OneToOneAssociationDefault(facetMethod);
+            return OneToOneAssociationDefault.forMethod(facetMethod);
         } else {
             return null;
         }
@@ -239,7 +239,7 @@ implements FacetHolder {
 
     private ObjectAction createAction(final FacetedMethod facetedMethod) {
         if (facetedMethod.getFeatureType().isAction()) {
-            return new ObjectActionDefault(facetedMethod);
+            return ObjectActionDefault.forMethod(facetedMethod);
         } else {
             return null;
         }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
index f3af061..7dd7374 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/ObjectActionLayoutXmlDefaultTest.java
@@ -24,6 +24,11 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2.Mode;
@@ -37,11 +42,6 @@ import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionDefault;
 import org.apache.isis.core.security.authentication.AuthenticationContext;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertEquals;
-
 public class ObjectActionLayoutXmlDefaultTest {
 
     @Rule public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(Mode.INTERFACES_AND_CLASSES);
@@ -70,7 +70,7 @@ public class ObjectActionLayoutXmlDefaultTest {
             }
         });
 
-        action = new ObjectActionDefault(mockFacetedMethod);
+        action = ObjectActionDefault.forMethod(mockFacetedMethod);
     }
 
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
index a1ca135..966a6db 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/objects/OneToManyAssociationDefaultTest.java
@@ -25,6 +25,10 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.services.message.MessageService;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
@@ -40,10 +44,6 @@ import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.specimpl.OneToManyAssociationDefault;
 import org.apache.isis.core.security.authentication.AuthenticationContext;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.is;
-
 public class OneToManyAssociationDefaultTest {
 
     private static final String COLLECTION_ID = "orders";
@@ -80,7 +80,7 @@ public class OneToManyAssociationDefaultTest {
         allowingPeerToReturnCollectionType();
         allowingPeerToReturnIdentifier();
         allowingSpecLoaderToReturnSpecs();
-        association = new OneToManyAssociationDefault(mockPeer);
+        association = OneToManyAssociationDefault.forMethod(mockPeer);
     }
 
     private void allowingSpecLoaderToReturnSpecs() {
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java
index 3bd3cc8..23d144b 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/metamodel/MetaModelServiceDefaultTest.java
@@ -105,7 +105,7 @@ class MetaModelServiceDefaultTest {
             }
         });
 
-        action = new ObjectActionDefault(mockFacetedMethod);
+        action = ObjectActionDefault.forMethod(mockFacetedMethod);
 
         mockMetaModelService = context.mock(MetaModelServiceDefault.class);
         context.checking(new Expectations() {
@@ -160,7 +160,7 @@ class MetaModelServiceDefaultTest {
     private OutputStream noopOutput(){
         return new OutputStream() {
             @Override public void write(int b) throws IOException {}
-        };  
+        };
     }
 
     @XmlRootElement(name = "employee")
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest.java
index 6bf2dd4..b075037 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest.java
@@ -25,6 +25,9 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.collections.Can;
@@ -42,9 +45,6 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 public class ObjectAssociationAbstractTest {
 
     @Rule
@@ -78,6 +78,7 @@ public class ObjectAssociationAbstractTest {
         }});
 
         objectAssociation = new ObjectAssociationAbstract(
+                facetedMethod.getIdentifier(),
                 facetedMethod, FeatureType.PROPERTY, objectSpecification) {
 
             @Override
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest_alwaysHidden.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest_alwaysHidden.java
index c79c609..f116e5d 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest_alwaysHidden.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectAssociationAbstractTest_alwaysHidden.java
@@ -25,6 +25,11 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.collections.Can;
@@ -43,11 +48,6 @@ import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 public class ObjectAssociationAbstractTest_alwaysHidden {
 
     @Rule
@@ -80,6 +80,7 @@ public class ObjectAssociationAbstractTest_alwaysHidden {
         }});
 
         objectAssociation = new ObjectAssociationAbstract(
+                facetedMethod.getIdentifier(),
                 facetedMethod, FeatureType.PROPERTY, mockObjectSpecification) {
 
             @Override
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationAbstractTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationAbstractTest.java
index 718cf5c..00bf181 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationAbstractTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationAbstractTest.java
@@ -25,6 +25,9 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.collections.Can;
@@ -41,9 +44,6 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
 public class OneToOneAssociationAbstractTest {
 
     @Rule
@@ -76,7 +76,9 @@ public class OneToOneAssociationAbstractTest {
             //            will(returnValue(mockPersistenceSessionServiceInternal));
         }});
 
-        objectAssociation = new OneToOneAssociationDefault(facetedMethod, objectSpecification) {
+        objectAssociation = new OneToOneAssociationDefault(
+                facetedMethod.getIdentifier(),
+                facetedMethod, objectSpecification) {
 
             @Override
             public ManagedObject get(
diff --git a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/objectref/ObjectReferenceFieldFactory.java b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/objectref/ObjectReferenceFieldFactory.java
index 989f7de..60927c5 100644
--- a/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/objectref/ObjectReferenceFieldFactory.java
+++ b/incubator/viewers/javafx/ui/src/main/java/org/apache/isis/incubator/viewer/javafx/ui/components/objectref/ObjectReferenceFieldFactory.java
@@ -42,7 +42,7 @@ public class ObjectReferenceFieldFactory implements UiComponentHandlerFx {
     @Override
     public boolean isHandling(ComponentRequest request) {
         //return request.hasFeatureFacet(StringValueFacet.class);
-        return request.getFeatureTypeSpec().isEntityOrViewModel()
+        return request.getFeatureTypeSpec().isEntityOrViewModelOrAbstract()
                 || request.getFeatureType().isEnum();
     }
 
@@ -57,19 +57,19 @@ public class ObjectReferenceFieldFactory implements UiComponentHandlerFx {
 //                .orElse("");
 
         val uiComponent = new Label(request.getManagedFeature().getIdentifier().toString());
-        
+
         if(request.getManagedFeature() instanceof ManagedParameter) {
-            
+
             val managedParameter = (ManagedParameter)request.getManagedFeature();
-            
+
 //            uiComponent.textProperty().
-//            
+//
 //            managedParameter.validate(proposedValue)
-            
+
             //TODO bind to parameter model
-            
+
         } else if(request.getManagedFeature() instanceof ManagedProperty) {
-            
+
             val managedProperty = (ManagedProperty)request.getManagedFeature();
             //TODO bind to property model
         }
diff --git a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
index 38e0834..d274727 100644
--- a/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
+++ b/viewers/restfulobjects/viewer/src/main/java/org/apache/isis/viewer/restfulobjects/viewer/resources/DomainTypeResourceServerside.java
@@ -94,9 +94,9 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
                 RepresentationType.TYPE_LIST, Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE);
 
         val domainTypeSpecifications = getSpecificationLoader().snapshotSpecifications()
-                .filter(spec->spec.isEntityOrViewModel());
+                .filter(spec->spec.isEntityOrViewModel()); // concrete types only, no abstract types
 
-        final TypeListReprRenderer renderer = 
+        final TypeListReprRenderer renderer =
                 new TypeListReprRenderer(resourceContext, null, JsonRepresentation.newMap());
         renderer.with(domainTypeSpecifications).includesSelf();
 
@@ -131,13 +131,13 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
 
         val resourceContext = createResourceContext(
                 RepresentationType.LAYOUT, Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE);
-        
+
         val serializationStrategy = resourceContext.getSerializationStrategy();
 
         val gridFacet = getSpecificationLoader().specForLogicalTypeName(domainType)
                 .map(spec->spec.getFacet(GridFacet.class))
                 .orElse(null);
-        
+
         final Response.ResponseBuilder builder;
         if(gridFacet == null) {
             builder = Responses.ofNotFound();
@@ -167,7 +167,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
 
         val objectMember = parentSpec.getAssociation(propertyId)
                 .orElseThrow(()->RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND));
-        
+
         if (objectMember.isOneToManyAssociation()) {
             throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
         }
@@ -184,7 +184,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
     @Path("/{domainType}/collections/{collectionId}")
     @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_COLLECTION_DESCRIPTION })
     public Response typeCollection(@PathParam("domainType") final String domainType, @PathParam("collectionId") final String collectionId) {
-        
+
         val resourceContext = createResourceContext(
                 RepresentationType.COLLECTION_DESCRIPTION, Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE);
 
@@ -195,7 +195,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
 
         val objectMember = parentSpec.getAssociation(collectionId)
                 .orElseThrow(()->RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND));
-        
+
         if (objectMember.isOneToOneAssociation()) {
             throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
         }
@@ -212,7 +212,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
     @Path("/{domainType}/actions/{actionId}")
     @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_ACTION_DESCRIPTION })
     public Response typeAction(@PathParam("domainType") final String domainType, @PathParam("actionId") final String actionId) {
-        
+
         val resourceContext = createResourceContext(
                 RepresentationType.ACTION_DESCRIPTION, Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE);
 
@@ -223,7 +223,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
 
         val action = parentSpec.getAction(actionId)
                 .orElseThrow(()->RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND));
-        
+
         final ActionDescriptionReprRenderer renderer = new ActionDescriptionReprRenderer(resourceContext, null, JsonRepresentation.newMap());
         renderer.with(new ParentSpecAndAction(parentSpec, action)).includesSelf();
 
@@ -246,7 +246,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
 
         val parentAction = parentSpec.getAction(actionId)
                 .orElseThrow(()->RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND));
-        
+
         final ObjectActionParameter actionParam = parentAction.getParameterByName(paramName);
 
         final ActionParameterDescriptionReprRenderer renderer = new ActionParameterDescriptionReprRenderer(resourceContext, null, JsonRepresentation.newMap());
@@ -268,7 +268,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
             @QueryParam("supertype") final String superTypeStr, // simple style
             @QueryParam("args") final String argsUrlEncoded // formal style
             ) {
-        
+
         val resourceContext = createResourceContext(
                 ResourceDescriptor.generic(Where.ANYWHERE, RepresentationService.Intent.NOT_APPLICABLE));
 
@@ -280,7 +280,7 @@ public class DomainTypeResourceServerside extends ResourceAbstract implements Do
                 || supertypeSpec == null) {
             throw RestfulObjectsApplicationException.create(HttpStatusCode.NOT_FOUND);
         }
-        
+
         final TypeActionResultReprRenderer renderer = new TypeActionResultReprRenderer(resourceContext, null, JsonRepresentation.newMap());
 
         final String url = "domain-types/" + domainType + "/type-actions/isSubtypeOf/invoke";