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 2015/10/14 02:28:57 UTC

[12/12] isis git commit: ISIS-1213: mix-ins for associations.

ISIS-1213: mix-ins for associations.


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

Branch: refs/heads/ISIS-1213
Commit: 3a299c2000812dc039709895d37bbe6b2e23028d
Parents: 6c88813
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Wed Oct 14 01:28:04 2015 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Wed Oct 14 01:28:04 2015 +0100

----------------------------------------------------------------------
 .../layout/ActionLayoutFacetFactory.java        |  17 +-
 ...otContributedFacetDerivedFromMixinFacet.java |  32 +++
 ...ibutedFacetDerivedFromMixinFacetFactory.java |  52 +++++
 .../MixinFacetForDomainObjectAnnotation.java    |   9 +
 .../mixin/MixinFacetForMixinAnnotation.java     |   5 +
 .../specloader/ObjectReflectorDefault.java      |   8 +
 .../specloader/specimpl/ContributeeMember.java  |  13 +-
 .../specimpl/ObjectActionContributee.java       |   3 -
 .../specimpl/ObjectActionMixedIn.java           | 115 +++--------
 .../specimpl/ObjectMemberAbstract.java          |  55 +++++-
 .../specimpl/ObjectSpecificationAbstract.java   | 152 +++++++++++++--
 .../specimpl/OneToManyAssociationMixedIn.java   | 193 +++++++++++++++++++
 .../specimpl/OneToOneAssociationMixedIn.java    | 174 +++++++++++++++++
 .../dflt/ProgrammingModelFacetsJava5.java       |   4 +
 .../specimpl/ObjectActionMixedInTest.java       |  12 +-
 15 files changed, 711 insertions(+), 133 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
index 7e3becf..6219faa 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
@@ -18,9 +18,9 @@
  */
 package org.apache.isis.core.metamodel.facets.actions.layout;
 
-
 import java.lang.reflect.Method;
 import java.util.Properties;
+
 import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
@@ -39,6 +39,7 @@ import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet;
 import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
 import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
 import org.apache.isis.core.metamodel.facets.object.domainservice.DomainServiceFacet;
+import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 
@@ -122,7 +123,7 @@ public class ActionLayoutFacetFactory extends FacetFactoryAbstract implements Co
 
         // contributing
 
-        if (isContributingService(processMethodContext)) {
+        if (isContributingServiceOrMixinObject(processMethodContext)) {
             NotContributedFacet notContributedFacet = NotContributedFacetForLayoutProperties.create(properties, holder);
             if(notContributedFacet == null) {
                 notContributedFacet = NotContributedFacetForActionLayoutAnnotation.create(actionLayout, holder);
@@ -131,18 +132,28 @@ public class ActionLayoutFacetFactory extends FacetFactoryAbstract implements Co
         }
     }
 
-    protected boolean isContributingService(final ProcessMethodContext processMethodContext) {
+    protected boolean isContributingServiceOrMixinObject(final ProcessMethodContext processMethodContext) {
         final Method method = processMethodContext.getMethod();
         final Class<?> declaringClass = method.getDeclaringClass();
         final ObjectSpecification spec = getSpecificationLoader().loadSpecification(declaringClass);
 
+        return isContributingService(spec) || isMixinObject(spec);
+    }
+
+    private static boolean isContributingService(final ObjectSpecification spec) {
         final DomainServiceFacet domainServiceFacet = spec.getFacet(DomainServiceFacet.class);
         return domainServiceFacet != null && !domainServiceFacet.isNoop() &&
                 domainServiceFacet.getNatureOfService() != NatureOfService.VIEW_MENU_ONLY &&
                 domainServiceFacet.getNatureOfService() != NatureOfService.DOMAIN;
     }
 
+    private static boolean isMixinObject(final ObjectSpecification spec) {
+        final MixinFacet mixinFacet = spec.getFacet(MixinFacet.class);
+        final boolean b = mixinFacet != null && !mixinFacet.isNoop();
+        return b;
+    }
 
+    // UNUSED
     protected boolean skipContributing(final DomainServiceFacet domainServiceFacet) {
         return domainServiceFacet == null || domainServiceFacet.isNoop() || domainServiceFacet.getNatureOfService() == NatureOfService.VIEW_MENU_ONLY || domainServiceFacet.getNatureOfService() == NatureOfService.DOMAIN;
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacet.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacet.java
new file mode 100644
index 0000000..ba6f38f
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacet.java
@@ -0,0 +1,32 @@
+/*
+ *  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.core.metamodel.facets.actions.notcontributed.derived;
+
+import org.apache.isis.applib.annotation.NotContributed;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacetAbstract;
+
+public class NotContributedFacetDerivedFromMixinFacet extends NotContributedFacetAbstract {
+
+    public NotContributedFacetDerivedFromMixinFacet(
+            final FacetHolder holder) {
+        super(NotContributed.As.EITHER, holder, Derivation.DERIVED);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacetFactory.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacetFactory.java
new file mode 100644
index 0000000..1e7a052
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/notcontributed/derived/NotContributedFacetDerivedFromMixinFacetFactory.java
@@ -0,0 +1,52 @@
+/*
+ *  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.core.metamodel.facets.actions.notcontributed.derived;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+
+public class NotContributedFacetDerivedFromMixinFacetFactory extends FacetFactoryAbstract {
+
+    public NotContributedFacetDerivedFromMixinFacetFactory() {
+        super(FeatureType.ACTIONS_ONLY);
+    }
+
+    @Override
+    public void process(final ProcessMethodContext processMethodContext) {
+        final Method method = processMethodContext.getMethod();
+        final Class<?> declaringClass = method.getDeclaringClass();
+        final ObjectSpecification spec = getSpecificationLoader().loadSpecification(declaringClass);
+
+        final MixinFacet mixinFacet = spec.getFacet(MixinFacet.class);
+        if(mixinFacet == null || mixinFacet.isNoop()) {
+            return;
+        }
+
+        final FacetedMethod facetHolder = processMethodContext.getFacetHolder();
+        FacetUtil.addFacet(new NotContributedFacetDerivedFromMixinFacet(facetHolder));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
index 3493fcc..cdd0c2a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForDomainObjectAnnotation.java
@@ -21,6 +21,8 @@ package org.apache.isis.core.metamodel.facets.object.mixin;
 
 import java.lang.reflect.Constructor;
 
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
@@ -42,6 +44,13 @@ public class MixinFacetForDomainObjectAnnotation extends MixinFacetAbstract {
     public static MixinFacet create(
             final Class<?> candidateMixinType, final FacetHolder facetHolder,
             final ServicesInjector servicesInjector) {
+        final DomainObject domainObject = candidateMixinType.getAnnotation(DomainObject.class);
+        if(domainObject == null) {
+            return null;
+        }
+        if(domainObject.nature() != Nature.MIXIN) {
+            return null;
+        }
         final Constructor<?>[] constructors = candidateMixinType.getConstructors();
         for (Constructor<?> constructor : constructors) {
             final Class<?>[] constructorTypes = constructor.getParameterTypes();

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForMixinAnnotation.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForMixinAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForMixinAnnotation.java
index ed369c5..7aedf55 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForMixinAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/mixin/MixinFacetForMixinAnnotation.java
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.object.mixin;
 
 import java.lang.reflect.Constructor;
 
+import org.apache.isis.applib.annotation.Mixin;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
@@ -42,6 +43,10 @@ public class MixinFacetForMixinAnnotation extends MixinFacetAbstract {
     public static MixinFacet create(
             final Class<?> candidateMixinType, final FacetHolder facetHolder,
             final ServicesInjector servicesInjector) {
+        final Mixin mixin = candidateMixinType.getAnnotation(Mixin.class);
+        if(mixin == null) {
+            return null;
+        }
         final Constructor<?>[] constructors = candidateMixinType.getConstructors();
         for (Constructor<?> constructor : constructors) {
             final Class<?>[] constructorTypes = constructor.getParameterTypes();

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
index cdea01b..4bb6a47 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/ObjectReflectorDefault.java
@@ -32,6 +32,7 @@ import com.google.common.collect.Maps;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.isis.applib.AppManifest;
 import org.apache.isis.core.commons.components.ApplicationScopedComponent;
 import org.apache.isis.core.commons.config.IsisConfiguration;
 import org.apache.isis.core.commons.debug.DebugBuilder;
@@ -250,6 +251,7 @@ public final class ObjectReflectorDefault
         metaModelValidator.init();
 
         loadSpecificationsForServices();
+        loadSpecificationsForMixins();
     }
 
     private void loadSpecificationsForServices() {
@@ -258,6 +260,12 @@ public final class ObjectReflectorDefault
         }
     }
 
+    private void loadSpecificationsForMixins() {
+        for (final Class<?> mixinType : AppManifest.Registry.instance().getMixinTypes()) {
+            internalLoadSpecification(mixinType);
+        }
+    }
+
     private void cacheBySpecId() {
         final Map<ObjectSpecId, ObjectSpecification> specById = Maps.newHashMap();
         for (final ObjectSpecification objSpec : allSpecifications()) {

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ContributeeMember.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ContributeeMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ContributeeMember.java
index 97dd48d..9aca015 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ContributeeMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ContributeeMember.java
@@ -26,15 +26,14 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
  * Interface indicating an a contributed association or action.
  */
 public interface ContributeeMember extends ObjectMember {
-    
+
+
     public static class Predicates {
         
         private Predicates(){}
-        
+
         /**
-         * Evaluates the supplied {@link ObjectMember} and includes either if it is not a {@link ContributeeMember}
-         * (ie is a regular member) or is a {@link ContributeeMember} and contributed are to be
-         * {@link Contributed#isIncluded() included}.  
+         * Now generalized to handle {@link MixedInMember}s as well as {@link ContributeeMember}s.
          */
         public static <T extends ObjectMember> Predicate<T> regularElse(final Contributed contributed) {
             return com.google.common.base.Predicates.or(regular(), is(contributed));
@@ -44,7 +43,7 @@ public interface ContributeeMember extends ObjectMember {
             return new Predicate<T>() {
                 @Override
                 public boolean apply(ObjectMember input) {
-                    return !(input instanceof ContributeeMember);
+                    return !(input instanceof ContributeeMember) && !(input instanceof MixedInMember);
                 }
             };
         }
@@ -53,7 +52,7 @@ public interface ContributeeMember extends ObjectMember {
             return new Predicate<T>() {
                 @Override
                 public boolean apply(ObjectMember input) {
-                    return input instanceof ContributeeMember && contributed.isIncluded();
+                    return contributed.isIncluded();
                 }
             };
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
index 117d2e4..5aa6444 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionContributee.java
@@ -72,9 +72,6 @@ public class ObjectActionContributee extends ObjectActionDefault implements Cont
     
     private final Identifier identifier;
 
-    /**
-     * @param contributeeParam - the parameter number which corresponds to the contributee, and so should be suppressed.
-     */
     public ObjectActionContributee(
             final Object servicePojo,
             final ObjectActionDefault serviceAction,

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedIn.java
index 3f68d8e..067be24 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
@@ -25,27 +25,25 @@ import com.google.common.collect.Lists;
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.Bulk;
 import org.apache.isis.applib.annotation.InvokedOn;
+import org.apache.isis.applib.annotation.NotContributed;
 import org.apache.isis.applib.annotation.Where;
-import org.apache.isis.applib.filter.Filter;
 import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
 import org.apache.isis.applib.services.bookmark.Bookmark;
 import org.apache.isis.applib.services.command.Command;
 import org.apache.isis.applib.services.command.Command.Executor;
 import org.apache.isis.applib.services.command.CommandContext;
 import org.apache.isis.core.commons.lang.ObjectExtensions;
-import org.apache.isis.core.commons.lang.StringExtensions;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
-import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
-import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet;
 import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
 import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacet;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacetAbstract;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred;
-import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.core.metamodel.interactions.InteractionUtils;
 import org.apache.isis.core.metamodel.interactions.UsabilityContext;
 import org.apache.isis.core.metamodel.interactions.VisibilityContext;
@@ -99,39 +97,20 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
         // adjust name if necessary
         final String name = getName();
 
-        String memberName = null;
         if(Objects.equal(name, "_")) {
-            memberName = determineNameFrom(mixinAction);
+            String memberName = determineNameFrom(mixinAction);
             FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
         }
 
         // calculate the identifier
         final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
-        memberName = memberName != null? memberName : mixinIdentifier.getMemberName();
         List<String> memberParameterNames = mixinIdentifier.getMemberParameterNames();
-        identifier = Identifier.actionIdentifier(getOnType().getCorrespondingClass().getName(), memberName, memberParameterNames);
+        identifier = Identifier.actionIdentifier(getOnType().getCorrespondingClass().getName(), getId(), memberParameterNames);
     }
 
-    private static String determineNameFrom(final ObjectActionDefault mixinAction) {
-        return suffixAfterUnderscore(mixinAction.getOnType().getSingularName());
-    }
-
-    static String suffixAfterUnderscore(final String singularName) {
-        return StringExtensions.asCapitalizedName(suffix(singularName));
-    }
-
-    private static String suffix(final String singularName) {
-        if (singularName.endsWith("_")) {
-            if (Objects.equal(singularName, "_")) {
-                return singularName;
-            }
-            return singularName;
-        }
-        final int indexOfUnderscore = singularName.lastIndexOf('_');
-        if (indexOfUnderscore == -1) {
-            return singularName;
-        }
-        return singularName.substring(indexOfUnderscore + 1);
+    @Override
+    public String getId() {
+        return determineIdFrom(this.mixinAction);
     }
 
     @Override
@@ -166,9 +145,10 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
     public Consent isVisible(
             final ObjectAdapter mixedInAdapter,
             final InteractionInitiatedBy interactionInitiatedBy,
-            Where where) {
-        final VisibilityContext<?> ic = mixinAction.createVisibleInteractionContext(mixinAdapterFor(mixedInAdapter),
-                interactionInitiatedBy, where);
+            final Where where) {
+        final VisibilityContext<?> ic =
+                mixinAction.createVisibleInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
         return InteractionUtils.isVisibleResult(this, ic).createConsent();
     }
 
@@ -176,28 +156,29 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
     public Consent isUsable(
             final ObjectAdapter mixedInAdapter,
             final InteractionInitiatedBy interactionInitiatedBy, final Where where) {
-        final UsabilityContext<?> ic = mixinAction.createUsableInteractionContext(mixinAdapterFor(mixedInAdapter),
-                interactionInitiatedBy, where);
+        final UsabilityContext<?> ic =
+                mixinAction.createUsableInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
         return InteractionUtils.isUsableResult(this, ic).createConsent();
     }
 
     @Override
     public ObjectAdapter[] getDefaults(final ObjectAdapter mixedInAdapter) {
-        return mixinAction.getDefaults(mixinAdapterFor(mixedInAdapter));
+        return mixinAction.getDefaults(mixinAdapterFor(mixinType, mixedInAdapter));
     }
 
     @Override
     public ObjectAdapter[][] getChoices(
             final ObjectAdapter mixedInAdapter,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        return mixinAction.getChoices(mixinAdapterFor(mixedInAdapter), interactionInitiatedBy);
+        return mixinAction.getChoices(mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy);
     }
 
     public Consent isProposedArgumentSetValid(
             final ObjectAdapter mixedInAdapter,
             final ObjectAdapter[] proposedArguments,
             final InteractionInitiatedBy interactionInitiatedBy) {
-        return mixinAction.isProposedArgumentSetValid(mixinAdapterFor(mixedInAdapter), proposedArguments, interactionInitiatedBy);
+        return mixinAction.isProposedArgumentSetValid(mixinAdapterFor(mixinType, mixedInAdapter), proposedArguments, interactionInitiatedBy);
     }
 
     @Override
@@ -248,61 +229,22 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
             }
         }
 
-        return mixinAction.execute(mixinAdapterFor(mixedInAdapter), arguments, interactionInitiatedBy);
-    }
-
-    // //////////////////////////////////////
-    // FacetHolder
-    // //////////////////////////////////////
-
-    @Override
-    public Class<? extends Facet>[] getFacetTypes() {
-        return facetHolder.getFacetTypes();
-    }
-
-    @Override
-    public <T extends Facet> T getFacet(Class<T> cls) {
-        return facetHolder.getFacet(cls);
-    }
-
-    @Override
-    public boolean containsFacet(Class<? extends Facet> facetType) {
-        return facetHolder.containsFacet(facetType);
+        return mixinAction.execute(mixinAdapterFor(mixinType, mixedInAdapter), arguments, interactionInitiatedBy);
     }
 
-    @Override
-    public boolean containsDoOpFacet(Class<? extends Facet> facetType) {
-        return facetHolder.containsDoOpFacet(facetType);
-    }
 
+    //region > facetHolder
     @Override
-    public List<Facet> getFacets(Filter<Facet> filter) {
-        return facetHolder.getFacets(filter);
+    protected FacetHolder getFacetHolder() {
+        return facetHolder;
     }
+    //endregion
 
-    @Override
-    public void addFacet(Facet facet) {
-        facetHolder.addFacet(facet);
-    }
 
-    @Override
-    public void addFacet(MultiTypedFacet facet) {
-        facetHolder.addFacet(facet);
-    }
-    
-    @Override
-    public void removeFacet(Facet facet) {
-        facetHolder.removeFacet(facet);
-    }
-
-    @Override
-    public void removeFacet(Class<? extends Facet> facetType) {
-        facetHolder.removeFacet(facetType);
+    ObjectAdapter mixinAdapterFor(final ObjectAdapter mixedInAdapter) {
+        return mixinAdapterFor(mixinType, mixedInAdapter);
     }
 
-    
-    // //////////////////////////////////////
-    
     /* (non-Javadoc)
      * @see org.apache.isis.core.metamodel.specloader.specimpl.ObjectMemberAbstract#getIdentifier()
      */
@@ -311,12 +253,5 @@ public class ObjectActionMixedIn extends ObjectActionDefault implements MixedInM
         return identifier;
     }
     
-    // //////////////////////////////////////
 
-    ObjectAdapter mixinAdapterFor(final ObjectAdapter mixedInAdapter) {
-        final ObjectSpecification objectSpecification = getSpecificationLoader().loadSpecification(mixinType);
-        final MixinFacet mixinFacet = objectSpecification.getFacet(MixinFacet.class);
-        final Object mixinPojo = mixinFacet.instantiate(mixedInAdapter.getObject());
-        return getPersistenceSessionService().adapterFor(mixinPojo);
-    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
----------------------------------------------------------------------
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 85ba2f3..2fc4fc3 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
@@ -21,6 +21,8 @@ package org.apache.isis.core.metamodel.specloader.specimpl;
 
 import java.util.List;
 
+import com.google.common.base.Objects;
+
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.annotation.When;
 import org.apache.isis.applib.annotation.Where;
@@ -39,6 +41,7 @@ import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
+import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
 import org.apache.isis.core.metamodel.interactions.AccessContext;
 import org.apache.isis.core.metamodel.interactions.DisablingInteractionAdvisor;
 import org.apache.isis.core.metamodel.interactions.HidingInteractionAdvisor;
@@ -63,7 +66,6 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
     //region > fields
     private final CollectionTypeRegistry collectionTypeRegistry = new CollectionTypeRegistry();
 
-    protected final String defaultName;
     private final String id;
     private final FacetedMethod facetedMethod;
     private final FeatureType featureType;
@@ -78,12 +80,11 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
             final ObjectMemberDependencies objectMemberDependencies) {
         final String id = facetedMethod.getIdentifier().getMemberName();
         if (id == null) {
-            throw new IllegalArgumentException("Name must always be set");
+            throw new IllegalArgumentException("Id must always be set");
         }
         this.facetedMethod = facetedMethod;
         this.featureType = featureType;
         this.id = id;
-        this.defaultName = StringExtensions.asNaturalName2(this.id);
 
         this.specificationLookup = objectMemberDependencies.getSpecificationLoader();
         this.servicesInjector = objectMemberDependencies.getServicesInjector();
@@ -182,7 +183,7 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
         }
         else {
             // this should now be redundant, see NamedFacetDefault
-            return defaultName;
+            return StringExtensions.asNaturalName2(getId());
         }
     }
 
@@ -310,6 +311,52 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
     }
     //endregion
 
+    //region > mixinAdapterFor
+    /**
+     * For mixins
+     */
+    protected ObjectAdapter mixinAdapterFor(
+            final Class<?> mixinType,
+            final ObjectAdapter mixedInAdapter) {
+        final ObjectSpecification objectSpecification = getSpecificationLoader().loadSpecification(mixinType);
+        final MixinFacet mixinFacet = objectSpecification.getFacet(MixinFacet.class);
+        final Object mixinPojo = mixinFacet.instantiate(mixedInAdapter.getObject());
+        return getPersistenceSessionService().adapterFor(mixinPojo);
+    }
+
+    static String determineNameFrom(final ObjectActionDefault mixinAction) {
+        return StringExtensions.asCapitalizedName(suffix(mixinAction));
+    }
+
+    static String determineIdFrom(final ObjectActionDefault mixinAction) {
+        final String id = compress(suffix(mixinAction));
+        return id;
+    }
+
+    private static String compress(final String suffix) {
+        return suffix.replaceAll(" ","");
+    }
+
+    static String suffix(final ObjectActionDefault mixinAction) {
+        return suffix(mixinAction.getOnType().getSingularName());
+    }
+
+    static String suffix(final String singularName) {
+        if (singularName.endsWith("_")) {
+            if (Objects.equal(singularName, "_")) {
+                return singularName;
+            }
+            return singularName;
+        }
+        final int indexOfUnderscore = singularName.lastIndexOf('_');
+        if (indexOfUnderscore == -1) {
+            return singularName;
+        }
+        return singularName.substring(indexOfUnderscore + 1);
+    }
+
+    //endregion
+
     //region > toString
 
     @Override

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
----------------------------------------------------------------------
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 f7968a3..fd6bf5c 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
@@ -27,6 +27,7 @@ import java.util.Properties;
 import java.util.Set;
 
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -148,8 +149,8 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
         return map;
     }
 
-    private boolean contributeeAssociationsAdded;
-    private boolean contributeeActionsAdded;
+    private boolean contributeeAndMixedInAssociationsAdded;
+    private boolean contributeeAndMixedInActionsAdded;
 
 
     private final List<ObjectSpecification> interfaces = Lists.newArrayList();
@@ -623,12 +624,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
     public List<ObjectAssociation> getAssociations(final Contributed contributed) {
         // the "contributed.isIncluded()" guard is required because we cannot do this too early;
         // there must be a session available
-        if(contributed.isIncluded() && !contributeeAssociationsAdded) {
+        if(contributed.isIncluded() && !contributeeAndMixedInAssociationsAdded) {
             synchronized (this.associations) {
                 List<ObjectAssociation> associations = Lists.newArrayList(this.associations);
                 associations.addAll(createContributeeAssociations());
+                associations.addAll(createMixedInAssociations());
                 sortAndUpdateAssociations(associations);
-                contributeeAssociationsAdded = true;
+                contributeeAndMixedInAssociationsAdded = true;
             }
         }
         final List<ObjectAssociation> associations = Lists.newArrayList(this.associations);
@@ -736,13 +738,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
         // and they have not yet been added
         // the "contributed.isIncluded()" guard is required because we cannot do this too early;
         // there must be a session available
-        if(contributed.isIncluded() && !contributeeActionsAdded) {
+        if(contributed.isIncluded() && !contributeeAndMixedInActionsAdded) {
             synchronized (this.objectActions) {
                 final List<ObjectAction> actions = Lists.newArrayList(this.objectActions);
                 actions.addAll(createContributeeActions());
-                actions.addAll(createMixinActions());
+                actions.addAll(createMixedInActions());
                 sortCacheAndUpdateActions(actions);
-                contributeeActionsAdded = true;
+                contributeeAndMixedInActionsAdded = true;
             }
         }
 
@@ -894,8 +896,11 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
             contributedActions.add((ObjectActionDefault) serviceAction);
         }
 
-        return Lists.newArrayList(Iterables.transform(contributedActions, createContributeeAssociationFunctor(
-                servicePojo, this)));
+        return Lists.newArrayList(
+                Iterables.transform(
+                    contributedActions,
+                    createContributeeAssociationFunctor(servicePojo, this)
+                ));
     }
 
     private Function<ObjectActionDefault, ObjectAssociation> createContributeeAssociationFunctor(
@@ -905,14 +910,114 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
             @Override
             public ObjectAssociation apply(ObjectActionDefault input) {
                 final ObjectSpecification returnType = input.getReturnType();
-                final ObjectAssociationAbstract association = returnType.isNotCollection()
-                        ? new OneToOneAssociationContributee(servicePojo, input, contributeeType,
-                        objectMemberDependencies)
-                        : new OneToManyAssociationContributee(servicePojo, input, contributeeType,
-                        objectMemberDependencies);
+                final ObjectAssociationAbstract association = createObjectAssociation(input, returnType);
                 facetProcessor.processMemberOrder(metadataProperties, association);
                 return association;
             }
+
+            ObjectAssociationAbstract createObjectAssociation(
+                    final ObjectActionDefault input,
+                    final ObjectSpecification returnType) {
+                if (returnType.isNotCollection()) {
+                    return new OneToOneAssociationContributee(servicePojo, input, contributeeType,
+                            objectMemberDependencies);
+                } else {
+                    return new OneToManyAssociationContributee(servicePojo, input, contributeeType,
+                            objectMemberDependencies);
+                }
+            }
+        };
+    }
+
+    //endregion
+
+    //region > mixin associations (properties and collections)
+
+    private List<ObjectAssociation> createMixedInAssociations() {
+        if (isService() || isValue()) {
+            return Collections.emptyList();
+        }
+
+        final Set<Class<?>> mixinTypes = AppManifest.Registry.instance().getMixinTypes();
+        if(mixinTypes == null) {
+            return Collections.emptyList();
+        }
+
+        final List<ObjectAssociation> mixedInAssociations = Lists.newArrayList();
+
+        for (final Class<?> mixinType : mixinTypes) {
+            addMixedInAssociationsIfAny(mixinType, mixedInAssociations);
+        }
+        return mixedInAssociations;
+    }
+
+    private void addMixedInAssociationsIfAny(
+            final Class<?> mixinType, final List<ObjectAssociation> toAppendTo) {
+
+        final ObjectSpecification specification = getSpecificationLoader().loadSpecification(mixinType);
+        if (specification == this) {
+            return;
+        }
+        final MixinFacet mixinFacet = specification.getFacet(MixinFacet.class);
+        if(mixinFacet == null) {
+            // this shouldn't happen; perhaps it would be more correct to throw an exception?
+            return;
+        }
+        if(!mixinFacet.isMixinFor(getCorrespondingClass())) {
+            return;
+        }
+
+        final List<ObjectActionDefault> mixinActions = getObjectActions(specification);
+
+        final List<ObjectAssociation> mixedInAssociations = Lists.newArrayList(
+                Iterables.transform(
+                        Iterables.filter(mixinActions, new Predicate<ObjectActionDefault>() {
+                            @Override public boolean apply(final ObjectActionDefault input) {
+                                final NotContributedFacet notContributedFacet = input.getFacet(NotContributedFacet.class);
+                                if (notContributedFacet == null || !notContributedFacet.toActions()) {
+                                    return false;
+                                }
+                                if(input.getParameterCount() != 0) {
+                                    return false;
+                                }
+                                if(!input.getSemantics().isSafeInNature()) {
+                                    return false;
+                                }
+                                return true;
+                            }
+                        }),
+                        createMixedInAssociationFunctor(this, mixinType)
+                ));
+
+        toAppendTo.addAll(mixedInAssociations);
+    }
+
+    private List getObjectActions(final ObjectSpecification specification) {
+        return specification.getObjectActions(ActionType.ALL, Contributed.INCLUDED, Filters.<ObjectAction>any());
+    }
+
+    private Function<ObjectActionDefault, ObjectAssociation> createMixedInAssociationFunctor(
+            final ObjectSpecification mixedInType,
+            final Class<?> mixinType) {
+        return new Function<ObjectActionDefault, ObjectAssociation>(){
+            @Override
+            public ObjectAssociation apply(final ObjectActionDefault mixinAction) {
+                final ObjectAssociationAbstract association = createObjectAssociation(mixinAction);
+                facetProcessor.processMemberOrder(metadataProperties, association);
+                return association;
+            }
+
+            ObjectAssociationAbstract createObjectAssociation(
+                    final ObjectActionDefault mixinAction) {
+                final ObjectSpecification returnType = mixinAction.getReturnType();
+                if (returnType.isNotCollection()) {
+                    return new OneToOneAssociationMixedIn(
+                            mixinAction, mixedInType, mixinType, objectMemberDependencies);
+                } else {
+                    return new OneToManyAssociationMixedIn(
+                            mixinAction, mixedInType, mixinType, objectMemberDependencies);
+                }
+            }
         };
     }
 
@@ -1004,7 +1109,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
      * If this specification {@link #isService() is actually for} a service,
      * then returns an empty list.
      */
-    protected List<ObjectAction> createMixinActions() {
+    protected List<ObjectAction> createMixedInActions() {
         if (isService() || isValue()) {
             return Collections.emptyList();
         }
@@ -1013,17 +1118,17 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
             return Collections.emptyList();
         }
 
-        final List<ObjectAction> mixinActions = Lists.newArrayList();
+        final List<ObjectAction> mixedInActions = Lists.newArrayList();
 
         for (final Class<?> mixinType : mixinTypes) {
-            addMixinActionsIfAny(mixinType, mixinActions);
+            addMixedInActionsIfAny(mixinType, mixedInActions);
         }
-        return mixinActions;
+        return mixedInActions;
     }
 
-    private void addMixinActionsIfAny(
+    private void addMixedInActionsIfAny(
             final Class<?> mixinType,
-            final List<ObjectAction> mixinActionsToAppendTo) {
+            final List<ObjectAction> mixedInActionsToAppendTo) {
         final ObjectSpecification specification = getSpecificationLoader().loadSpecification(mixinType);
         if (specification == this) {
             return;
@@ -1048,16 +1153,21 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
                 continue;
             }
             final ObjectActionDefault mixinAction = (ObjectActionDefault) mixinTypeAction;
+            final NotContributedFacet notContributedFacet = mixinAction.getFacet(NotContributedFacet.class);
+            if(notContributedFacet.toActions()) {
+                continue;
+            }
 
             ObjectActionMixedIn mixedInAction =
                     new ObjectActionMixedIn(mixinType, mixinAction, this, objectMemberDependencies);
             facetProcessor.processMemberOrder(metadataProperties, mixedInAction);
             actions.add(mixedInAction);
         }
-        mixinActionsToAppendTo.addAll(actions);
+        mixedInActionsToAppendTo.addAll(actions);
     }
 
     //endregion
+
     //region > validity
     @Override
     public Consent isValid(final ObjectAdapter targetAdapter, final InteractionInitiatedBy interactionInitiatedBy) {

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..1890b3d
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToManyAssociationMixedIn.java
@@ -0,0 +1,193 @@
+/**
+ *  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.core.metamodel.specloader.specimpl;
+
+import java.util.List;
+
+import com.google.common.base.Objects;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.When;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
+import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred;
+import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
+import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacetForContributee;
+import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacet;
+import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacetAbstract;
+import org.apache.isis.core.metamodel.interactions.InteractionUtils;
+import org.apache.isis.core.metamodel.interactions.UsabilityContext;
+import org.apache.isis.core.metamodel.interactions.VisibilityContext;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.SpecificationLoader;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMemberDependencies;
+
+public class OneToManyAssociationMixedIn extends OneToManyAssociationDefault implements MixedInMember {
+
+    /**
+     * The type of the mixin (providing the action), eg annotated with {@link org.apache.isis.applib.annotation.Mixin}.
+     */
+    private final Class<?> mixinType;
+
+    /**
+     * The {@link ObjectActionDefault} for the action being mixed in (ie on the {@link #mixinType}.
+     */
+    private final ObjectActionDefault mixinAction;
+
+    /**
+     * The domain object type being mixed in to (being supplemented).
+     */
+    private final ObjectSpecification mixedInType;
+
+
+    /**
+     * Hold facets rather than delegate to the mixin action (different types might use layout metadata to position
+     * the mixin in different ways)
+     */
+    private final FacetHolder facetHolder = new FacetHolderImpl();
+
+    private final Identifier identifier;
+
+    private static ObjectSpecification typeOfSpec(
+            final ObjectActionDefault objectAction,
+            final ObjectMemberDependencies objectMemberDependencies) {
+
+        final TypeOfFacet actionTypeOfFacet = objectAction.getFacet(TypeOfFacet.class);
+        final SpecificationLoader specificationLookup = objectMemberDependencies.getSpecificationLoader();
+        // TODO: a bit of a hack; ought really to set up a fallback TypeOfFacetDefault which ensures that there is always
+        // a TypeOfFacet for any mixedIn associations created from mixin actions.
+        Class<? extends Object> cls = actionTypeOfFacet != null? actionTypeOfFacet.value(): Object.class;
+        return specificationLookup.loadSpecification(cls);
+    }
+
+    public OneToManyAssociationMixedIn(
+            final ObjectActionDefault mixinAction,
+            final ObjectSpecification mixedInType,
+            final Class<?> mixinType,
+            final ObjectMemberDependencies objectMemberDependencies) {
+        super(mixinAction.getFacetedMethod(),
+                typeOfSpec(mixinAction, objectMemberDependencies),
+                objectMemberDependencies);
+
+        this.mixinType = mixinType;
+        this.mixinAction = mixinAction;
+        this.mixedInType = mixedInType;
+
+        //
+        // ensure the mixedIn collection cannot be modified, and derive its TypeOfFaccet
+        //
+        final NotPersistedFacet notPersistedFacet = new NotPersistedFacetAbstract(this) {};
+        final DisabledFacet disabledFacet = disabledFacet();
+        final TypeOfFacet typeOfFacet = new TypeOfFacetAbstract(getSpecification().getCorrespondingClass(), this, objectMemberDependencies
+                .getSpecificationLoader()) {};
+
+        FacetUtil.addFacet(notPersistedFacet);
+        FacetUtil.addFacet(disabledFacet);
+        FacetUtil.addFacet(typeOfFacet);
+
+
+        //
+        // in addition, copy over facets from contributed to own.
+        //
+        // These could include everything under @Collection(...) because the
+        // CollectionAnnotationFacetFactory is also run against actions.
+        //
+        FacetUtil.copyFacets(mixinAction.getFacetedMethod(), facetHolder);
+
+        // adjust name if necessary
+        final String name = getName();
+
+        if(Objects.equal(name, "_")) {
+            String memberName = ObjectActionMixedIn.determineNameFrom(mixinAction);
+            FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
+        }
+
+        // calculate the identifier
+        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
+        List<String> memberParameterNames = mixinIdentifier.getMemberParameterNames();
+
+        identifier = Identifier.actionIdentifier(mixedInType.getCorrespondingClass().getName(), getId(), memberParameterNames);
+    }
+
+    private DisabledFacet disabledFacet() {
+        final DisabledFacet originalFacet = facetHolder.getFacet(DisabledFacet.class);
+        if( originalFacet != null && 
+            originalFacet.when() == When.ALWAYS && 
+            originalFacet.where() == Where.ANYWHERE) {
+            return originalFacet;
+        }
+        // ensure that the contributed association is always disabled
+        return new DisabledFacetForContributee("Contributed collection", this);
+    }
+
+    @Override
+    public ObjectAdapter get(final ObjectAdapter mixedInAdapter, final InteractionInitiatedBy interactionInitiatedBy) {
+        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixinType, mixedInAdapter);
+        return mixinAction.execute(mixinAdapter, new ObjectAdapter[0], interactionInitiatedBy);
+    }
+
+    @Override
+    public Identifier getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public String getId() {
+        return determineIdFrom(this.mixinAction);
+    }
+
+    @Override
+    public Consent isVisible(
+            final ObjectAdapter mixedInAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy,
+            final Where where) {
+        final VisibilityContext<?> ic =
+                mixinAction.createVisibleInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+        return InteractionUtils.isVisibleResult(this, ic).createConsent();
+    }
+
+    @Override
+    public Consent isUsable(
+            final ObjectAdapter mixedInAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy,
+            final Where where) {
+        final UsabilityContext<?> ic =
+                mixinAction.createUsableInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+        return InteractionUtils.isUsableResult(this, ic).createConsent();
+    }
+
+    //region > FacetHolder
+
+    @Override
+    protected FacetHolder getFacetHolder() {
+        return facetHolder;
+    }
+
+    //endregion
+
+
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..71daf26
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/OneToOneAssociationMixedIn.java
@@ -0,0 +1,174 @@
+/**
+ *  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.core.metamodel.specloader.specimpl;
+
+import java.util.List;
+
+import com.google.common.base.Objects;
+
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.NotContributed;
+import org.apache.isis.applib.annotation.When;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacet;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacetAbstract;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacetInferred;
+import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
+import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacetForContributee;
+import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacet;
+import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacetAbstract;
+import org.apache.isis.core.metamodel.interactions.InteractionUtils;
+import org.apache.isis.core.metamodel.interactions.UsabilityContext;
+import org.apache.isis.core.metamodel.interactions.VisibilityContext;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMemberDependencies;
+
+public class OneToOneAssociationMixedIn extends OneToOneAssociationDefault implements MixedInMember {
+
+    /**
+     * The type of the mixin (providing the action), eg annotated with {@link org.apache.isis.applib.annotation.Mixin}.
+     */
+    private final Class<?> mixinType;
+
+    /**
+     * The {@link ObjectActionDefault} for the action being mixed in (ie on the {@link #mixinType}.
+     */
+    private final ObjectActionDefault mixinAction;
+
+    /**
+     * The domain object type being mixed in to (being supplemented).
+     */
+    private final ObjectSpecification mixedInType;
+
+
+    /**
+     * Hold facets rather than delegate to the contributed action (different types might
+     * use layout metadata to position the contributee in different ways)
+     */
+    private final FacetHolder facetHolder = new FacetHolderImpl();
+
+    private final Identifier identifier;
+
+    public OneToOneAssociationMixedIn(
+            final ObjectActionDefault mixinAction,
+            final ObjectSpecification mixedInType,
+            final Class<?> mixinType,
+            final ObjectMemberDependencies objectMemberDependencies) {
+        super(mixinAction.getFacetedMethod(), mixinAction.getReturnType(), objectMemberDependencies);
+
+        this.mixinType = mixinType;
+        this.mixinAction = mixinAction;
+        this.mixedInType = mixedInType;
+
+        //
+        // ensure the contributed property cannot be modified
+        //
+        final NotPersistedFacet notPersistedFacet = new NotPersistedFacetAbstract(this) {};
+        final DisabledFacet disabledFacet = disabledFacet();
+
+        FacetUtil.addFacet(notPersistedFacet);
+        FacetUtil.addFacet(disabledFacet);
+
+        //
+        // in addition, copy over facets from contributed to own.
+        //
+        // These could include everything under @Property(...) because the
+        // PropertyAnnotationFacetFactory is also run against actions.
+        //
+        FacetUtil.copyFacets(mixinAction.getFacetedMethod(), facetHolder);
+
+
+
+        // adjust name if necessary
+        final String name = getName();
+
+        if(Objects.equal(name, "_")) {
+            String memberName = ObjectActionMixedIn.determineNameFrom(mixinAction);
+            FacetUtil.addFacet(new NamedFacetInferred(memberName, facetHolder));
+        }
+
+
+        // calculate the identifier
+        final Identifier mixinIdentifier = mixinAction.getFacetedMethod().getIdentifier();
+        List<String> memberParameterNames = mixinIdentifier.getMemberParameterNames();
+
+        identifier = Identifier.actionIdentifier(mixedInType.getCorrespondingClass().getName(), getId(), memberParameterNames);
+    }
+
+    private DisabledFacet disabledFacet() {
+        final DisabledFacet originalFacet = facetHolder.getFacet(DisabledFacet.class);
+        if( originalFacet != null && 
+            originalFacet.when() == When.ALWAYS && 
+            originalFacet.where() == Where.ANYWHERE) {
+            return originalFacet;
+        }
+        // ensure that the contributed association is always disabled
+        return new DisabledFacetForContributee("Contributed property", this);
+    }
+
+    @Override
+    public ObjectAdapter get(
+            final ObjectAdapter mixedInAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy) {
+
+        final ObjectAdapter mixinAdapter = mixinAdapterFor(mixinType, mixedInAdapter);
+        return mixinAction.execute(mixinAdapter, new ObjectAdapter[0], interactionInitiatedBy);
+    }
+
+    @Override
+    public Identifier getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public String getId() {
+        return determineIdFrom(this.mixinAction);
+    }
+
+    @Override
+    public Consent isVisible(
+            final ObjectAdapter mixedInAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy,
+            Where where) {
+        final VisibilityContext<?> ic =
+                mixinAction.createVisibleInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+        return InteractionUtils.isVisibleResult(this, ic).createConsent();
+    }
+
+    @Override
+    public Consent isUsable(
+            final ObjectAdapter mixedInAdapter,
+            final InteractionInitiatedBy interactionInitiatedBy, final Where where) {
+        final UsabilityContext<?> ic =
+                mixinAction.createUsableInteractionContext(
+                        mixinAdapterFor(mixinType, mixedInAdapter), interactionInitiatedBy, where);
+        return InteractionUtils.isUsableResult(this, ic).createConsent();
+    }
+
+    @Override
+    protected FacetHolder getFacetHolder() {
+        return facetHolder;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
index f21e958..4b9c4b7 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -28,6 +28,7 @@ import org.apache.isis.core.metamodel.facets.actions.interaction.ActionNamedDebu
 import org.apache.isis.core.metamodel.facets.actions.layout.ActionLayoutFacetFactory;
 import org.apache.isis.core.metamodel.facets.actions.notcontributed.annotation.NotContributedFacetAnnotationFactory;
 import org.apache.isis.core.metamodel.facets.actions.notcontributed.derived.NotContributedFacetDerivedFromDomainServiceFacetFactory;
+import org.apache.isis.core.metamodel.facets.actions.notcontributed.derived.NotContributedFacetDerivedFromMixinFacetFactory;
 import org.apache.isis.core.metamodel.facets.actions.notinservicemenu.annotation.NotInServiceMenuFacetAnnotationFactory;
 import org.apache.isis.core.metamodel.facets.actions.notinservicemenu.derived.NotInServiceMenuFacetDerivedFromDomainServiceFacetFactory;
 import org.apache.isis.core.metamodel.facets.actions.notinservicemenu.method.NotInServiceMenuFacetViaMethodFactory;
@@ -365,6 +366,7 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(RecreatableObjectFacetFactory.class);
         addFactory(MixinFacetForMixinAnnotationFactory.class);
 
+
         addFactory(MaxLengthFacetOnTypeAnnotationFactory.class);
         addFactory(MaxLengthFacetOnActionAnnotationFactory.class);
 
@@ -389,6 +391,8 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         // must come after DomainObjectAnnotationFacetFactory
         addFactory(DisabledFacetOnPropertyDerivedFromRecreatableObjectFacetFactory.class);
         addFactory(DisabledFacetOnCollectionDerivedFromViewModelFacetFactory.class);
+        // must come after DomainObjectAnnotationFacetFactory & MixinFacetFactory
+        addFactory(NotContributedFacetDerivedFromMixinFacetFactory.class);
 
         addFactory(DomainServiceLayoutFactory.class);
         addFactory(DomainObjectLayoutFacetFactory.class);

http://git-wip-us.apache.org/repos/asf/isis/blob/3a299c20/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedInTest.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedInTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedInTest.java
index fdb3eaf..9707af6 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedInTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionMixedInTest.java
@@ -2,6 +2,8 @@ package org.apache.isis.core.metamodel.specloader.specimpl;
 
 import org.junit.Test;
 
+import org.apache.isis.core.commons.lang.StringExtensions;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.junit.Assert.assertThat;
 
@@ -12,31 +14,31 @@ public class ObjectActionMixedInTest {
 
         @Test
         public void exactly_underscore() throws Exception {
-            final String s = ObjectActionMixedIn.suffixAfterUnderscore("_");
+            final String s = StringExtensions.asCapitalizedName(ObjectMemberAbstract.suffix("_"));
             assertThat(s, is("_"));
         }
 
         @Test
         public void ends_with_underscore() throws Exception {
-            final String s = ObjectActionMixedIn.suffixAfterUnderscore("abc_");
+            final String s = StringExtensions.asCapitalizedName(ObjectMemberAbstract.suffix("abc_"));
             assertThat(s, is("Abc_"));
         }
 
         @Test
         public void has_no_underscore() throws Exception {
-            final String s = ObjectActionMixedIn.suffixAfterUnderscore("defghij");
+            final String s = StringExtensions.asCapitalizedName(ObjectMemberAbstract.suffix("defghij"));
             assertThat(s, is("Defghij"));
         }
 
         @Test
         public void contains_one_underscore() throws Exception {
-            final String s = ObjectActionMixedIn.suffixAfterUnderscore("abc_def");
+            final String s = StringExtensions.asCapitalizedName(ObjectMemberAbstract.suffix("abc_def"));
             assertThat(s, is("Def"));
         }
 
         @Test
         public void contains_more_than_one_underscore() throws Exception {
-            final String s = ObjectActionMixedIn.suffixAfterUnderscore("abc_def_ghi");
+            final String s = StringExtensions.asCapitalizedName(ObjectMemberAbstract.suffix("abc_def_ghi"));
             assertThat(s, is("Ghi"));
         }
     }