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/04/12 18:46:05 UTC

[isis] branch master updated: ISIS-2604: implement new mm validators and provide tests

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 45cf097  ISIS-2604: implement new mm validators and provide tests
45cf097 is described below

commit 45cf097b464b1630f48661660cc6ca61bc872d79
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Apr 12 20:45:49 2021 +0200

    ISIS-2604: implement new mm validators and provide tests
---
 .../isis/core/metamodel/facets/FacetFactory.java   |  41 ++++----
 .../action/ActionAnnotationFacetFactory.java       |  24 ++++-
 .../actions/layout/ActionLayoutFacetFactory.java   |  22 +++-
 .../CollectionAnnotationFacetFactory.java          |  39 ++++---
 .../layout/CollectionLayoutFacetFactory.java       |  21 +++-
 .../property/PropertyAnnotationFacetFactory.java   |  23 +++--
 .../propertylayout/PropertyLayoutFacetFactory.java |  21 +++-
 ...ModelValidatorForAmbiguousMixinAnnotations.java |  47 +++++++++
 ...etaModelValidatorForConflictingOptionality.java |   2 +
 .../model/bad/AmbiguousMixinAnnotations.java       | 109 +++++++++++++++++++
 .../DomainModelTest_usingBadDomain.java            | 115 +++++++++++++--------
 ...nModelTest_usingBadDomain_noActionEnforced.java |   4 +-
 .../applib/validate/DomainModelValidator.java      |  18 ++--
 13 files changed, 385 insertions(+), 101 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java
index ea558f4..9eaa73b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactory.java
@@ -25,7 +25,6 @@ import java.lang.reflect.Parameter;
 import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
-import java.util.function.Supplier;
 
 import org.apache.isis.commons.collections.ImmutableEnumSet;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
@@ -36,6 +35,8 @@ import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MethodRemover;
 
 import lombok.Getter;
+import lombok.NonNull;
+import lombok.val;
 
 public interface FacetFactory {
 
@@ -239,22 +240,28 @@ public interface FacetFactory {
         /** 
          * Annotation lookup on this context's method, if not found, extends search to type in case 
          * the predicate {@link #isMixinMain} evaluates {@code true}.
+         * <p>
+         * As of [ISIS-2604] we also make sure the annotation type does not appear in both places 
+         * (method and type). Hence the 2nd parameter is a callback that fires if the annotation 
+         * is found in both places. 
+         * 
          * @since 2.0
          */
-        public <A extends Annotation> Optional<A> synthesizeOnMethodOrMixinType(Class<A> annotationType) {
-            return computeIfAbsent(synthesizeOnMethod(annotationType),
-                    ()-> isMixinMain()
-                        ? synthesizeOnType(annotationType)
-                                : Optional.empty() ) ;
-        }
-        
-        private static <T> Optional<T> computeIfAbsent(
-                Optional<T> optional,
-                Supplier<Optional<T>> supplier) {
+        public <A extends Annotation> Optional<A> synthesizeOnMethodOrMixinType(
+                final @NonNull Class<A> annotationType,
+                final @NonNull Runnable onAmbiguity) {
+            
+            
+            val onMethod = synthesizeOnMethod(annotationType);
+            val onType = synthesizeOnType(annotationType);
             
-            return optional.isPresent() ? 
-                    optional
-                        : supplier.get();
+            if(onMethod.isPresent()) {
+                if(onType.isPresent()) {
+                    onAmbiguity.run();    
+                }
+                return onMethod;
+            }
+            return onType;
         }
         
     }
@@ -265,11 +272,7 @@ public interface FacetFactory {
      */
     void process(ProcessMethodContext processMethodContext);
 
-
-
-    // //////////////////////////////////////
-    // process param
-    // //////////////////////////////////////
+    // -- PROCESS PARAM
 
     public static class ProcessParameterContext 
     extends AbstractProcessWithMethodContext<FacetedMethodParameter> {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
index 5b5dbba..0950722 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/ActionAnnotationFacetFactory.java
@@ -27,6 +27,7 @@ import org.apache.isis.applib.mixins.system.HasInteractionId;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Collections;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
 import org.apache.isis.core.metamodel.facets.actions.action.associateWith.AssociatedWithFacetForActionAnnotation;
@@ -46,13 +47,21 @@ import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFac
 import org.apache.isis.core.metamodel.facets.members.publish.command.CommandPublishingFacetForActionAnnotation;
 import org.apache.isis.core.metamodel.facets.members.publish.execution.ExecutionPublishingActionFacetForActionAnnotation;
 import org.apache.isis.core.metamodel.facets.object.domainobject.domainevents.ActionDomainEventDefaultFacetForDomainObjectAnnotation;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 import org.apache.isis.core.metamodel.util.EventUtil;
 
 import lombok.val;
 
-public class ActionAnnotationFacetFactory extends FacetFactoryAbstract {
+public class ActionAnnotationFacetFactory
+extends FacetFactoryAbstract
+implements MetaModelRefiner {
 
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+            = new MetaModelValidatorForAmbiguousMixinAnnotations();
+
+    
     public ActionAnnotationFacetFactory() {
         super(FeatureType.ACTIONS_ONLY);
     }
@@ -60,7 +69,11 @@ public class ActionAnnotationFacetFactory extends FacetFactoryAbstract {
     @Override
     public void process(final ProcessMethodContext processMethodContext) {
 
-        val actionIfAny = processMethodContext.synthesizeOnMethodOrMixinType(Action.class);
+        val actionIfAny = processMethodContext
+                .synthesizeOnMethodOrMixinType(
+                        Action.class, 
+                        () -> ambiguousMixinAnnotationsValidator
+                        .addValidationFailure(processMethodContext.getFacetHolder(), Action.class));
 
         processExplicit(processMethodContext, actionIfAny);
         processInvocation(processMethodContext, actionIfAny);
@@ -285,4 +298,11 @@ public class ActionAnnotationFacetFactory extends FacetFactoryAbstract {
         super.addFacet(facet);
     }
 
+    // -- METAMODEL REFINER
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
+    }
+
 }
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 0e978ff..1b38452 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
@@ -20,6 +20,7 @@ package org.apache.isis.core.metamodel.facets.actions.layout;
 
 import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacetFallback;
@@ -36,11 +37,17 @@ import org.apache.isis.core.metamodel.facets.members.layout.order.LayoutOrderFac
 import org.apache.isis.core.metamodel.facets.members.layout.order.LayoutOrderFacetFromActionLayoutAnnotation;
 import org.apache.isis.core.metamodel.facets.object.bookmarkpolicy.BookmarkPolicyFacet;
 import org.apache.isis.core.metamodel.facets.object.promptStyle.PromptStyleFacet;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 
 import lombok.val;
 
 public class ActionLayoutFacetFactory 
-extends FacetFactoryAbstract {
+extends FacetFactoryAbstract
+implements MetaModelRefiner {
+    
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+            = new MetaModelValidatorForAmbiguousMixinAnnotations();
 
     public ActionLayoutFacetFactory() {
         super(FeatureType.ACTIONS_ONLY);
@@ -50,7 +57,11 @@ extends FacetFactoryAbstract {
     public void process(final ProcessMethodContext processMethodContext) {
 
         val facetHolder = processMethodContext.getFacetHolder();
-        val actionLayoutIfAny = processMethodContext.synthesizeOnMethodOrMixinType(ActionLayout.class);
+        val actionLayoutIfAny = processMethodContext
+                .synthesizeOnMethodOrMixinType(
+                        ActionLayout.class,
+                        () -> ambiguousMixinAnnotationsValidator
+                        .addValidationFailure(processMethodContext.getFacetHolder(), ActionLayout.class));
         
         // bookmarkable
         BookmarkPolicyFacet bookmarkableFacet = BookmarkPolicyFacetForActionLayoutAnnotation
@@ -108,4 +119,11 @@ extends FacetFactoryAbstract {
 
     }
 
+    // -- METAMODEL REFINER
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
+    }
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
index 568dfec..dfc31f1 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/collection/CollectionAnnotationFacetFactory.java
@@ -30,6 +30,7 @@ import org.apache.isis.applib.events.domain.CollectionDomainEvent;
 import org.apache.isis.commons.internal.collections._Collections;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet;
 import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacetInferredFromArray;
@@ -44,12 +45,19 @@ import org.apache.isis.core.metamodel.facets.collections.collection.modify.Colle
 import org.apache.isis.core.metamodel.facets.collections.collection.typeof.TypeOfFacetOnCollectionFromCollectionAnnotation;
 import org.apache.isis.core.metamodel.facets.object.domainobject.domainevents.CollectionDomainEventDefaultFacetForDomainObjectAnnotation;
 import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 import org.apache.isis.core.metamodel.util.EventUtil;
 
 import lombok.val;
 
-public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract {
+public class CollectionAnnotationFacetFactory
+extends FacetFactoryAbstract
+implements MetaModelRefiner {
+    
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+        = new MetaModelValidatorForAmbiguousMixinAnnotations();
 
     public CollectionAnnotationFacetFactory() {
         super(FeatureType.COLLECTIONS_AND_ACTIONS);
@@ -58,7 +66,11 @@ public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract {
     @Override
     public void process(final ProcessMethodContext processMethodContext) {
 
-        val collectionIfAny = processMethodContext.synthesizeOnMethodOrMixinType(Collection.class);
+        val collectionIfAny = processMethodContext
+                .synthesizeOnMethodOrMixinType(
+                        Collection.class, 
+                        () -> ambiguousMixinAnnotationsValidator
+                        .addValidationFailure(processMethodContext.getFacetHolder(), Collection.class));
 
         inferIntentWhenOnTypeLevel(processMethodContext, collectionIfAny);
 
@@ -96,8 +108,6 @@ public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract {
             return;
         }
 
-
-
         // following only runs for regular collections, not for mixins.
         // those are tackled in the post-processing, when more of the metamodel is available to us
 
@@ -105,19 +115,17 @@ public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract {
         // Set up CollectionDomainEventFacet, which will act as the hiding/disabling/validating advisor
         //
 
-
         // search for @Collection(domainEvent=...)
         val collectionDomainEventFacet = collectionIfAny
-                .map(Collection::domainEvent)
-                .filter(domainEvent -> domainEvent != CollectionDomainEvent.Default.class)
-                .map(domainEvent ->
+        .map(Collection::domainEvent)
+        .filter(domainEvent -> domainEvent != CollectionDomainEvent.Default.class)
+        .map(domainEvent ->
                 (CollectionDomainEventFacetAbstract)
                 new CollectionDomainEventFacetForCollectionAnnotation(
                         defaultFromDomainObjectIfRequired(typeSpec, domainEvent), holder))
-                .orElse(
-                        new CollectionDomainEventFacetDefault(
-                                defaultFromDomainObjectIfRequired(typeSpec, CollectionDomainEvent.Default.class), holder)
-                        );
+        .orElse(
+                new CollectionDomainEventFacetDefault(
+                        defaultFromDomainObjectIfRequired(typeSpec, CollectionDomainEvent.Default.class), holder));
         if(!CollectionDomainEvent.Noop.class.isAssignableFrom(collectionDomainEventFacet.getEventType())) {
             super.addFacet(collectionDomainEventFacet);
         }
@@ -219,5 +227,12 @@ public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract {
         return null;
     }
 
+    // -- METAMODEL REFINER
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
+    }
+
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/CollectionLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/CollectionLayoutFacetFactory.java
index 2fdcef2..fba354c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/CollectionLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/collections/layout/CollectionLayoutFacetFactory.java
@@ -20,14 +20,21 @@ package org.apache.isis.core.metamodel.facets.collections.layout;
 
 import org.apache.isis.applib.annotation.CollectionLayout;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.members.layout.order.LayoutOrderFacetFromCollectionLayoutAnnotation;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 
 import lombok.val;
 
 public class CollectionLayoutFacetFactory 
-extends FacetFactoryAbstract {
+extends FacetFactoryAbstract
+implements MetaModelRefiner {
 
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+        = new MetaModelValidatorForAmbiguousMixinAnnotations();
+    
     public CollectionLayoutFacetFactory() {
         super(FeatureType.COLLECTIONS_AND_ACTIONS);
     }
@@ -36,7 +43,11 @@ extends FacetFactoryAbstract {
     public void process(final ProcessMethodContext processMethodContext) {
 
         val facetHolder = processMethodContext.getFacetHolder();
-        val collectionLayoutIfAny = processMethodContext.synthesizeOnMethodOrMixinType(CollectionLayout.class);
+        val collectionLayoutIfAny = processMethodContext
+                .synthesizeOnMethodOrMixinType(
+                        CollectionLayout.class, 
+                        () -> ambiguousMixinAnnotationsValidator
+                        .addValidationFailure(processMethodContext.getFacetHolder(), CollectionLayout.class));
 
         val cssClassFacet = CssClassFacetForCollectionLayoutAnnotation
                 .create(collectionLayoutIfAny, facetHolder);
@@ -71,6 +82,12 @@ extends FacetFactoryAbstract {
         super.addFacet(sortedByFacet);
     }
 
+    // -- METAMODEL REFINER
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
+    }
 
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index fe2ded9..c2a9579 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -33,8 +33,8 @@ import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
-import org.apache.isis.core.metamodel.facets.actions.contributing.ContributingFacetAbstract;
 import org.apache.isis.core.metamodel.facets.actions.contributing.ContributingFacet.Contributing;
+import org.apache.isis.core.metamodel.facets.actions.contributing.ContributingFacetAbstract;
 import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFacetAbstract;
 import org.apache.isis.core.metamodel.facets.members.publish.command.CommandPublishingFacetForPropertyAnnotation;
 import org.apache.isis.core.metamodel.facets.members.publish.execution.ExecutionPublishingPropertyFacetForPropertyAnnotation;
@@ -62,16 +62,21 @@ import org.apache.isis.core.metamodel.facets.properties.update.clear.PropertyCle
 import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForConflictingOptionality;
 import org.apache.isis.core.metamodel.util.EventUtil;
 
 import lombok.val;
 
-public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract
+public class PropertyAnnotationFacetFactory 
+extends FacetFactoryAbstract
 implements MetaModelRefiner {
 
-    private final MetaModelValidatorForConflictingOptionality conflictingOptionalityValidator =
-            new MetaModelValidatorForConflictingOptionality();
+    private final MetaModelValidatorForConflictingOptionality conflictingOptionalityValidator 
+        = new MetaModelValidatorForConflictingOptionality();
+    
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+        = new MetaModelValidatorForAmbiguousMixinAnnotations();
 
     public PropertyAnnotationFacetFactory() {
         super(FeatureType.PROPERTIES_AND_ACTIONS);
@@ -86,7 +91,11 @@ implements MetaModelRefiner {
     @Override
     public void process(final ProcessMethodContext processMethodContext) {
 
-        val propertyIfAny = processMethodContext.synthesizeOnMethodOrMixinType(Property.class);
+        val propertyIfAny = processMethodContext
+                .synthesizeOnMethodOrMixinType(
+                        Property.class, 
+                        () -> ambiguousMixinAnnotationsValidator
+                            .addValidationFailure(processMethodContext.getFacetHolder(), Property.class));
 
         inferIntentWhenOnTypeLevel(processMethodContext, propertyIfAny);
 
@@ -104,7 +113,6 @@ implements MetaModelRefiner {
         processFileAccept(processMethodContext, propertyIfAny);
     }
 
-
     void inferIntentWhenOnTypeLevel(ProcessMethodContext processMethodContext, Optional<Property> propertyIfAny) {
         if(!processMethodContext.isMixinMain() || !propertyIfAny.isPresent()) {
             return; // no @Property found neither type nor method
@@ -371,11 +379,12 @@ implements MetaModelRefiner {
         super.addFacet(facet);
     }
 
-    // //////////////////////////////////////
+    // -- METAMODEL REFINER
 
     @Override
     public void refineProgrammingModel(ProgrammingModel programmingModel) {
         programmingModel.addValidator(conflictingOptionalityValidator);
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/PropertyLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/PropertyLayoutFacetFactory.java
index 609b6ae..1706647 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/PropertyLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/properties/propertylayout/PropertyLayoutFacetFactory.java
@@ -21,14 +21,21 @@ package org.apache.isis.core.metamodel.facets.properties.propertylayout;
 
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.members.layout.group.LayoutGroupFacetFromPropertyLayoutAnnotation;
 import org.apache.isis.core.metamodel.facets.members.layout.order.LayoutOrderFacetFromPropertyLayoutAnnotation;
+import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForAmbiguousMixinAnnotations;
 
 import lombok.val;
 
 public class PropertyLayoutFacetFactory 
-extends FacetFactoryAbstract {
+extends FacetFactoryAbstract
+implements MetaModelRefiner {
+    
+    private final MetaModelValidatorForAmbiguousMixinAnnotations ambiguousMixinAnnotationsValidator
+        = new MetaModelValidatorForAmbiguousMixinAnnotations();
 
     public PropertyLayoutFacetFactory() {
         super(FeatureType.PROPERTIES_AND_ACTIONS);
@@ -39,7 +46,10 @@ extends FacetFactoryAbstract {
 
         val facetHolder = processMethodContext.getFacetHolder();
         val propertyLayoutIfAny = processMethodContext
-                .synthesizeOnMethodOrMixinType(PropertyLayout.class);
+                .synthesizeOnMethodOrMixinType(
+                        PropertyLayout.class, 
+                        () -> ambiguousMixinAnnotationsValidator
+                        .addValidationFailure(processMethodContext.getFacetHolder(), PropertyLayout.class));
 
         val cssClassFacet = CssClassFacetForPropertyLayoutAnnotation
                 .create(propertyLayoutIfAny, facetHolder);
@@ -90,5 +100,12 @@ extends FacetFactoryAbstract {
         super.addFacet(unchangingFacet);
         
     }
+    
+    // -- METAMODEL REFINER
+
+    @Override
+    public void refineProgrammingModel(ProgrammingModel programmingModel) {
+        programmingModel.addValidator(ambiguousMixinAnnotationsValidator);
+    }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForAmbiguousMixinAnnotations.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForAmbiguousMixinAnnotations.java
new file mode 100644
index 0000000..ce26fad
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForAmbiguousMixinAnnotations.java
@@ -0,0 +1,47 @@
+/*
+ *  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.validator;
+
+import java.lang.annotation.Annotation;
+
+import org.apache.isis.core.metamodel.facets.FacetedMethod;
+
+import lombok.val;
+
+public class MetaModelValidatorForAmbiguousMixinAnnotations 
+extends MetaModelValidatorAbstract {
+
+    public <A extends Annotation> void addValidationFailure(
+            final FacetedMethod holder,
+            final Class<A> annotationType) {
+        
+        final String annotationLiteral = "@" + annotationType.getSimpleName();
+        val identifier = holder.getIdentifier();
+        
+        super.onFailure(
+                holder, 
+                identifier, 
+                "Annotation %s on both method and type level is not allowed, "
+                + "it must be one or the other. Found with mixin: %s", 
+                annotationLiteral, 
+                identifier.getFullIdentityString());
+    }
+
+    
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForConflictingOptionality.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForConflictingOptionality.java
index d449c57..35d0604 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForConflictingOptionality.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/validator/MetaModelValidatorForConflictingOptionality.java
@@ -33,6 +33,8 @@ public class MetaModelValidatorForConflictingOptionality extends MetaModelValida
         }
         return facet;
     }
+    
+    // -- HELPER
 
     private Facet addFailure(final Facet facet, final String message) {
         if(facet != null) {
diff --git a/regressiontests/stable/src/main/java/org/apache/isis/testdomain/model/bad/AmbiguousMixinAnnotations.java b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/model/bad/AmbiguousMixinAnnotations.java
new file mode 100644
index 0000000..5f40bcf
--- /dev/null
+++ b/regressiontests/stable/src/main/java/org/apache/isis/testdomain/model/bad/AmbiguousMixinAnnotations.java
@@ -0,0 +1,109 @@
+/*
+ *  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.testdomain.model.bad;
+
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Collection;
+import org.apache.isis.applib.annotation.CollectionLayout;
+import org.apache.isis.applib.annotation.DomainObject;
+import org.apache.isis.applib.annotation.Nature;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.annotation.PropertyLayout;
+
+import lombok.RequiredArgsConstructor;
+
+@SuppressWarnings("unused")
+public class AmbiguousMixinAnnotations {
+
+    @DomainObject(nature = Nature.VIEW_MODEL)
+    public static class Mixee {
+        
+    }
+
+    // -- SHOULD FAIL VALIDATION 
+    
+    @Property
+    @RequiredArgsConstructor
+    public static class InvalidMixinP {
+        private final Mixee mixee;
+        
+        @Property
+        public String prop() {
+            return null;
+        }
+    }
+    
+    @PropertyLayout
+    @RequiredArgsConstructor
+    public static class InvalidMixinPL {
+        private final Mixee mixee;
+        
+        @PropertyLayout
+        public String prop() {
+            return null;
+        }
+    }
+    
+    @Collection
+    @RequiredArgsConstructor
+    public static class InvalidMixinC {
+        private final Mixee mixee;
+        
+        @Collection
+        public String coll() {
+            return null;
+        }
+    }
+    
+    @CollectionLayout
+    @RequiredArgsConstructor
+    public static class InvalidMixinCL {
+        private final Mixee mixee;
+        
+        @CollectionLayout
+        public String coll() {
+            return null;
+        }
+    }
+    
+    @Action
+    @RequiredArgsConstructor
+    public static class InvalidMixinA {
+        private final Mixee mixee;
+        
+        @Action
+        public String act() {
+            return null;
+        }
+    }
+    
+    @ActionLayout
+    @RequiredArgsConstructor
+    public static class InvalidMixinAL {
+        private final Mixee mixee;
+        
+        @ActionLayout
+        public String act() {
+            return null;
+        }
+    }
+
+
+}
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
index 7c5cec6..083a140 100644
--- a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
@@ -18,23 +18,39 @@
  */
 package org.apache.isis.testdomain.domainmodel;
 
+import java.lang.annotation.Annotation;
+import java.util.stream.Stream;
+
 import javax.inject.Inject;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.test.context.TestPropertySource;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.annotation.Action;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Collection;
+import org.apache.isis.applib.annotation.CollectionLayout;
+import org.apache.isis.applib.annotation.Property;
+import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.exceptions.unrecoverable.DomainModelException;
+import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.config.metamodel.specloader.IntrospectionMode;
 import org.apache.isis.core.config.presets.IsisPresets;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.testdomain.conf.Configuration_headless;
+import org.apache.isis.testdomain.model.bad.AmbiguousMixinAnnotations;
 import org.apache.isis.testdomain.model.bad.AmbiguousTitle;
 import org.apache.isis.testdomain.model.bad.Configuration_usingInvalidDomain;
 import org.apache.isis.testdomain.model.bad.InvalidOrphanedActionSupport;
@@ -43,8 +59,6 @@ import org.apache.isis.testdomain.model.bad.InvalidOrphanedPropertySupport;
 import org.apache.isis.testdomain.model.bad.InvalidPropertyAnnotationOnAction;
 import org.apache.isis.testing.integtestsupport.applib.validate.DomainModelValidator;
 
-import lombok.val;
-
 @SpringBootTest(
         classes = { 
                 Configuration_headless.class,
@@ -65,6 +79,15 @@ class DomainModelTest_usingBadDomain {
     @Inject private IsisSystemEnvironment isisSystemEnvironment;
     @Inject private SpecificationLoader specificationLoader;
     
+    private DomainModelValidator validator;
+    
+    @BeforeEach
+    void setup() {
+        validator = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
+        assertThrows(DomainModelException.class, validator::throwIfInvalid);
+    }
+    
+    
     @Test
     void fullIntrospection_shouldBeEnabledByThisTestClass() {
         assertTrue(IntrospectionMode.isFullIntrospect(configuration, isisSystemEnvironment));
@@ -72,71 +95,73 @@ class DomainModelTest_usingBadDomain {
     
     @Test
     void ambiguousTitle_shouldFail() {
-           
-        val validator = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        
-        assertThrows(DomainModelException.class, validator::throwIfInvalid);
         assertTrue(validator.anyMatchesContaining(
-                AmbiguousTitle.class, 
+                Identifier.classIdentifier(LogicalType.fqcn(AmbiguousTitle.class)),
                 "conflict for determining a strategy for retrieval of title"));
     }
     
     @Test
     void orphanedActionSupport_shouldFail() {
-           
-        val validateDomainModel = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        
-        assertThrows(DomainModelException.class, validateDomainModel::throwIfInvalid);
-        assertTrue(validateDomainModel.anyMatchesContaining(
-                InvalidOrphanedActionSupport.class, 
+        assertTrue(validator.anyMatchesContaining(
+                Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedActionSupport.class)), 
                 "is assumed to support"));
     }
-    
-//    @Test
-//    void orphanedActionSupportNotEnforced_shouldFail() {
-//           
-//        val validateDomainModel = new DomainModelValidator();
-//        
-//        assertThrows(DomainModelException.class, validateDomainModel::run);
-//        assertTrue(validateDomainModel.anyMatchesContaining(
-//                OrphanedPrefixedAction.class, 
-//                "is assumed to support"));
-//    }
 
     @Test
     void orphanedPropertySupport_shouldFail() {
-           
-        val validateDomainModel = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        
-        assertThrows(DomainModelException.class, validateDomainModel::throwIfInvalid);
-        assertTrue(validateDomainModel.anyMatchesContaining(
-                InvalidOrphanedPropertySupport.class, 
+        assertTrue(validator.anyMatchesContaining(
+                Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedPropertySupport.class)), 
                 "is assumed to support"));
     }
     
     @Test
     void orphanedCollectionSupport_shouldFail() {
-           
-        val validateDomainModel = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        
-        assertThrows(DomainModelException.class, validateDomainModel::throwIfInvalid);
-        assertTrue(validateDomainModel.anyMatchesContaining(
-                InvalidOrphanedCollectionSupport.class, 
+        assertTrue(validator.anyMatchesContaining(
+                Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedCollectionSupport.class)), 
                 "is assumed to support"));
     }
     
+    @ParameterizedTest
+    @MethodSource("provideAmbiguousMixins")
+    void ambiguousMixinAnnotions_shouldFailValidation(
+            final Class<?> mixinClass, 
+            final Class<? extends Annotation> annotationType, 
+            final String mixinMethodName) {
+        
+        final String annotationLiteral = "@" + annotationType.getSimpleName();
+        assertTrue(validator.anyMatchesContaining(
+                Identifier.propertyOrCollectionIdentifier(LogicalType.fqcn(mixinClass), mixinMethodName), 
+                String.format("Annotation %s on both method and type level is not allowed", annotationLiteral)));
+    }
+    
+    private static Stream<Arguments> provideAmbiguousMixins() {
+        return Stream.of(
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinA.class, Action.class, "act"),
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinAL.class, ActionLayout.class, "act"),
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinP.class, Property.class, "prop"),
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinPL.class, PropertyLayout.class, "prop"),
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinC.class, Collection.class, "coll"),
+          Arguments.of(AmbiguousMixinAnnotations.InvalidMixinCL.class, CollectionLayout.class, "coll")
+        );
+    }
+    
+    // -- INCUBATING
+    
     @Test @Disabled("this case has no vaildation refiner yet")
     void invalidPropertyAnnotationOnAction_shouldFail() {
-        
-        val validateDomainModel = new DomainModelValidator(specificationLoader, configuration, isisSystemEnvironment);
-        
-        assertThrows(DomainModelException.class, validateDomainModel::throwIfInvalid);
-        assertTrue(validateDomainModel.anyMatchesContaining(
-                InvalidPropertyAnnotationOnAction.class, 
+        assertTrue(validator.anyMatchesContaining(
+                Identifier.classIdentifier(LogicalType.fqcn(InvalidPropertyAnnotationOnAction.class)), 
                 "TODO"));
     }
     
-
-    
-
+//    @Test
+//    void orphanedActionSupportNotEnforced_shouldFail() {
+//           
+//        val validateDomainModel = new DomainModelValidator();
+//        
+//        assertThrows(DomainModelException.class, validateDomainModel::run);
+//        assertTrue(validateDomainModel.anyMatchesContaining(
+//                OrphanedPrefixedAction.class, 
+//                "is assumed to support"));
+//    }
 }
diff --git a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain_noActionEnforced.java b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain_noActionEnforced.java
index fe170c7..63ec852 100644
--- a/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain_noActionEnforced.java
+++ b/regressiontests/stable/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain_noActionEnforced.java
@@ -28,7 +28,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.exceptions.unrecoverable.DomainModelException;
+import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.core.config.IsisConfiguration;
 import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.config.metamodel.specloader.IntrospectionMode;
@@ -80,7 +82,7 @@ class DomainModelTest_usingBadDomain_noActionEnforced {
         
         assertThrows(DomainModelException.class, validateDomainModel::throwIfInvalid);
         assertTrue(validateDomainModel.anyMatchesContaining(
-                InvalidOrphanedActionSupportNoActionEnforced.class, 
+                Identifier.classIdentifier(LogicalType.fqcn(InvalidOrphanedActionSupportNoActionEnforced.class)), 
                 "is assumed to support"));
     }
     
diff --git a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/validate/DomainModelValidator.java b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/validate/DomainModelValidator.java
index 854fd00..7da2d20 100644
--- a/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/validate/DomainModelValidator.java
+++ b/testing/integtestsupport/applib/src/main/java/org/apache/isis/testing/integtestsupport/applib/validate/DomainModelValidator.java
@@ -32,17 +32,15 @@ import org.junit.jupiter.api.Assertions;
 
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.exceptions.unrecoverable.DomainModelException;
-import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.applib.services.registry.ServiceRegistry;
-import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.config.IsisConfiguration;
+import org.apache.isis.core.config.environment.IsisSystemEnvironment;
 import org.apache.isis.core.config.metamodel.specloader.IntrospectionMode;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailure;
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
 
-import static org.apache.isis.commons.internal.base._With.requires;
-
+import lombok.NonNull;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -133,9 +131,9 @@ public class DomainModelValidator {
                 .filter(failure->filter.test(failure.getOrigin()));
     }
 
-    public Stream<ValidationFailure> streamFailuresMatchingOriginatingClass(Class<?> cls) {
-        requires(cls, "cls");
-        return streamFailures(origin->origin.getClassName().equals(cls.getName()));
+    public Stream<ValidationFailure> streamFailuresMatchingOriginatingIdentifier(
+            final @NonNull Identifier identifier) {
+        return streamFailures(id->id.equals(identifier));
     }
 
 
@@ -144,8 +142,10 @@ public class DomainModelValidator {
     /**
      * primarily used for testing
      */
-    public boolean anyMatchesContaining(Class<?> cls, String messageSnippet) {
-        return streamFailuresMatchingOriginatingClass(cls)
+    public boolean anyMatchesContaining(
+            final @NonNull Identifier identifier, 
+            final @NonNull String messageSnippet) {
+        return streamFailuresMatchingOriginatingIdentifier(identifier)
                 .anyMatch(failure->
                     failure.getMessage().contains(messageSnippet));
     }