You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2023/06/22 10:53:03 UTC

[causeway] branch master updated: CAUSEWAY-3437: resurrect mm validation for conflicting optionality

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/causeway.git


The following commit(s) were added to refs/heads/master by this push:
     new f9de37c32a CAUSEWAY-3437: resurrect mm validation for conflicting optionality
f9de37c32a is described below

commit f9de37c32a1417a64bad87a4938343135b238a12
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Jun 22 12:52:59 2023 +0200

    CAUSEWAY-3437: resurrect mm validation for conflicting optionality
---
 .../progmodel/ProgrammingModelConstants.java       |  3 +
 .../objectvalue/mandatory/MandatoryFacet.java      | 11 ++-
 .../mandatory/MandatoryFacetAbstract.java          |  8 +-
 .../mandatory/MandatoryFacetDefault.java           |  2 +-
 .../parameter/ParameterAnnotationFacetFactory.java |  4 +-
 .../MandatoryFacetForParameterAnnotation.java      | 24 ++++--
 ...cetInvertedByNullableAnnotationOnParameter.java |  2 +-
 .../property/PropertyAnnotationFacetFactory.java   |  4 +-
 .../MandatoryFacetForPropertyAnnotation.java       |  9 ++-
 ...acetInvertedByNullableAnnotationOnProperty.java |  2 +-
 .../MandatoryFacetFromXmlElementAnnotation.java    |  2 +-
 ...datoryFacetFromXmlElementAnnotationFactory.java |  7 +-
 .../validator/ValidationFailureUtils.java          | 91 ++++++++++++++--------
 .../applib/tenancy/dom/ApplicationTenancy.java     |  4 +-
 .../metamodel/facets/entity/JdoEntityFacet.java    |  1 -
 ...andatoryFacetFromAbsenceOfColumnAnnotation.java |  4 +-
 .../column/MandatoryFacetFromColumnAnnotation.java |  4 +-
 .../MandatoryFromColumnAnnotationFacetFactory.java |  4 +-
 .../MandatoryFacetFromJdoPrimaryKeyAnnotation.java |  2 +-
 .../MandatoryFacetFromJpaColumnAnnotation.java     |  7 +-
 ...ndatoryFromJpaColumnAnnotationFacetFactory.java |  7 +-
 21 files changed, 132 insertions(+), 70 deletions(-)

diff --git a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
index a62e28d35d..b11572401e 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/progmodel/ProgrammingModelConstants.java
@@ -475,6 +475,9 @@ public final class ProgrammingModelConstants {
         CONFLICTING_TITLE_STRATEGIES(
                 "${type} has title() method with @Title annotation, which is not allowed; "
                 + "consider either removing the @Title annotation or renaming the method"),
+        CONFLICTING_OPTIONALITY(
+                "${member} has conflicting optionality semantics; facets involved are:\n"
+                + "${conflictingFacets}"),
         ORPHANED_METHOD("${type}#${member}: is public, but orphaned (was not picked up by the framework); "
                 + "reporting orphans, because the class is setup for member introspection, "
                 + "without enforcing annotations"),
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacet.java
index b52bc52e9d..6df032013f 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacet.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacet.java
@@ -70,7 +70,16 @@ extends Facet, ValidatingInteractionAdvisor {
      * other words that the {@link FacetHolder} to which this {@link Facet} is
      * attached is <i>not</i> mandatory.
      */
-    public Semantics getSemantics();
+    Semantics getSemantics();
+
+    /**
+     * Used to generate validation message, when there are conflicting mandatory semantics.
+     */
+    default String summarize() {
+        return String.format("%s->%s", getClass().getSimpleName(), getSemantics().name());
+    }
+
+    // -- UTILITY
 
     static boolean isMandatory(final @NonNull FacetHolder facetHolder) {
         val mandatoryFacet = facetHolder.getFacet(MandatoryFacet.class);
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
index d147618368..96b20d7b0d 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetAbstract.java
@@ -48,19 +48,19 @@ implements MandatoryFacet {
     @Getter(onMethod_ = {@Override})
     private Semantics semantics;
 
-    public MandatoryFacetAbstract(final FacetHolder holder, final Semantics semantics) {
+    public MandatoryFacetAbstract(final Semantics semantics, final FacetHolder holder) {
         super(type(), holder);
         this.semantics = semantics;
     }
 
     public MandatoryFacetAbstract(
-            final FacetHolder holder, final Semantics semantics, final Facet.Precedence precedence) {
+            final Semantics semantics, final FacetHolder holder, final Facet.Precedence precedence) {
         super(type(), holder, precedence);
         this.semantics = semantics;
     }
 
     @Override
-    public boolean semanticEquals(final @NonNull Facet other) {
+    public final boolean semanticEquals(final @NonNull Facet other) {
         return other instanceof MandatoryFacetAbstract
                 ? this.getSemantics() == ((MandatoryFacetAbstract)other).getSemantics()
                 : false;
@@ -107,7 +107,7 @@ implements MandatoryFacet {
     }
 
     @Override
-    public void visitAttributes(final BiConsumer<String, Object> visitor) {
+    public final void visitAttributes(final BiConsumer<String, Object> visitor) {
         super.visitAttributes(visitor);
         visitor.accept("semantics", semantics);
     }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetDefault.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetDefault.java
index 12dba0c107..a6fbe797aa 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetDefault.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/objectvalue/mandatory/MandatoryFacetDefault.java
@@ -37,7 +37,7 @@ public class MandatoryFacetDefault extends MandatoryFacetAbstract {
 
     private MandatoryFacetDefault(final FacetHolder holder, final Semantics semantics) {
         // unconditionally created, hence acting as a fallback
-        super(holder, semantics, Facet.Precedence.FALLBACK);
+        super(semantics, holder, Facet.Precedence.FALLBACK);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
index 463eaedfcc..7ff26c52d6 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/ParameterAnnotationFacetFactory.java
@@ -127,14 +127,14 @@ extends FacetFactoryAbstract {
                 .create(hasNullable, parameterType, holder))
         .ifPresent(mandatoryFacet->
                 ValidationFailureUtils.raiseIfConflictingOptionality(
-                    mandatoryFacet, "Conflicting @Nullable with other optionality annotation"));
+                    mandatoryFacet, ()->"Conflicting @Nullable with other optionality annotation"));
 
         addFacetIfPresent(
                 MandatoryFacetForParameterAnnotation
                 .create(parameterIfAny, parameterType, holder))
         .ifPresent(mandatoryFacet->
                 ValidationFailureUtils.raiseIfConflictingOptionality(
-                    mandatoryFacet, "Conflicting @Parameter#optionality with other optionality annotation"));
+                    mandatoryFacet, ()->"Conflicting @Parameter#optionality with other optionality annotation"));
     }
 
     void processParamsFileAccept(final ProcessParameterContext processParameterContext) {
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetForParameterAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetForParameterAnnotation.java
index 80ac302f23..ab6fd796fe 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetForParameterAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetForParameterAnnotation.java
@@ -24,11 +24,8 @@ import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
 import org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.MandatoryFacetAbstract;
 
-public abstract class MandatoryFacetForParameterAnnotation extends MandatoryFacetAbstract {
-
-    public MandatoryFacetForParameterAnnotation(final FacetHolder holder, final Semantics semantics) {
-        super(holder, semantics);
-    }
+public abstract class MandatoryFacetForParameterAnnotation
+extends MandatoryFacetAbstract {
 
     public static java.util.Optional<MandatoryFacet> create(
             final java.util.Optional<Parameter> parameterIfAny,
@@ -57,21 +54,32 @@ public abstract class MandatoryFacetForParameterAnnotation extends MandatoryFace
         });
     }
 
+    protected MandatoryFacetForParameterAnnotation(final Semantics semantics, final FacetHolder holder) {
+        super(semantics, holder);
+    }
+
+    @Override
+    public final String summarize() {
+        return MandatoryFacetForParameterAnnotation.class.getSimpleName() + "." + super.summarize();
+    }
+
+    // -- IMPLEMENTATIONS
+
     public static class Primitive extends MandatoryFacetForParameterAnnotation {
         public Primitive(final FacetHolder holder) {
-            super(holder, Semantics.REQUIRED);
+            super(Semantics.REQUIRED, holder);
         }
     }
 
     public static class Required extends MandatoryFacetForParameterAnnotation {
         public Required(final FacetHolder holder) {
-            super(holder, Semantics.REQUIRED);
+            super(Semantics.REQUIRED, holder);
         }
     }
 
     public static class Optional extends MandatoryFacetForParameterAnnotation {
         public Optional(final FacetHolder holder) {
-            super(holder, Semantics.OPTIONAL);
+            super(Semantics.OPTIONAL, holder);
         }
     }
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetInvertedByNullableAnnotationOnParameter.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetInvertedByNullableAnnotationOnParameter.java
index 1c57969553..8e0af4bc57 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetInvertedByNullableAnnotationOnParameter.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/param/parameter/mandatory/MandatoryFacetInvertedByNullableAnnotationOnParameter.java
@@ -52,7 +52,7 @@ extends MandatoryFacetAbstract {
     }
 
     private MandatoryFacetInvertedByNullableAnnotationOnParameter(final FacetHolder holder) {
-        super(holder, Semantics.OPTIONAL);
+        super(Semantics.OPTIONAL, holder);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
index bed15f00d3..276b516279 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/PropertyAnnotationFacetFactory.java
@@ -288,7 +288,7 @@ extends FacetFactoryAbstract {
         .ifPresent(mandatoryFacet->
                 ValidationFailureUtils.raiseIfConflictingOptionality(
                         mandatoryFacet,
-                        "Conflicting @Nullable with other optionality annotation"));
+                        ()->"Conflicting @Nullable with other optionality annotation"));
 
         // search for @Property(optional=...)
         addFacetIfPresent(
@@ -297,7 +297,7 @@ extends FacetFactoryAbstract {
         .ifPresent(mandatoryFacet->
                 ValidationFailureUtils.raiseIfConflictingOptionality(
                         mandatoryFacet,
-                        "Conflicting Property#optionality with other optionality annotation"));
+                        ()->"Conflicting Property#optionality with other optionality annotation"));
     }
 
     void processRegEx(final ProcessMethodContext processMethodContext, final Optional<Property> propertyIfAny) {
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetForPropertyAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetForPropertyAnnotation.java
index 863c14988c..4a0c3808fc 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetForPropertyAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetForPropertyAnnotation.java
@@ -64,9 +64,16 @@ extends MandatoryFacetAbstract {
     }
 
     public MandatoryFacetForPropertyAnnotation(final FacetHolder holder, final Semantics semantics) {
-        super(holder, semantics);
+        super(semantics, holder);
     }
 
+    @Override
+    public final String summarize() {
+        return MandatoryFacetForPropertyAnnotation.class.getSimpleName() + "." + super.summarize();
+    }
+
+    // -- IMPLEMENTATIONS
+
     public static class Primitive extends MandatoryFacetForPropertyAnnotation {
         public Primitive(final FacetHolder holder) {
             super(holder, Semantics.REQUIRED);
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetInvertedByNullableAnnotationOnProperty.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetInvertedByNullableAnnotationOnProperty.java
index 74a2914237..9f9b266501 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetInvertedByNullableAnnotationOnProperty.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/MandatoryFacetInvertedByNullableAnnotationOnProperty.java
@@ -56,7 +56,7 @@ extends MandatoryFacetAbstract {
     }
 
     private MandatoryFacetInvertedByNullableAnnotationOnProperty(final FacetHolder holder) {
-        super(holder, Semantics.OPTIONAL);
+        super(Semantics.OPTIONAL, holder);
     }
 
 
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotation.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotation.java
index dd0f9a90f4..6d9540ff38 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotation.java
@@ -51,7 +51,7 @@ extends MandatoryFacetAbstract {
 
     private MandatoryFacetFromXmlElementAnnotation(
             final Semantics semantics, final FacetHolder holder) {
-        super(holder, semantics);
+        super(semantics, holder);
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotationFactory.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotationFactory.java
index 48c24812a3..ca04900499 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotationFactory.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/properties/property/mandatory/jaxb/MandatoryFacetFromXmlElementAnnotationFactory.java
@@ -24,6 +24,7 @@ import javax.xml.bind.annotation.XmlElement;
 import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.facetapi.FeatureType;
 import org.apache.causeway.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.causeway.core.metamodel.specloader.validator.ValidationFailureUtils;
 
 import lombok.val;
 
@@ -46,7 +47,11 @@ extends FacetFactoryAbstract {
 
         // search for @XmlElement(required=...)
         addFacetIfPresent(
-                MandatoryFacetFromXmlElementAnnotation.create(xmlElementIfAny, facetHolder));
+                MandatoryFacetFromXmlElementAnnotation.create(xmlElementIfAny, facetHolder))
+        .ifPresent(mandatoryFacet->
+            ValidationFailureUtils.raiseIfConflictingOptionality(
+                    mandatoryFacet,
+                    ()->"Conflicting @XmlElement(required=...) with other optionality annotation"));
     }
 
 }
diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/ValidationFailureUtils.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/ValidationFailureUtils.java
index b8fd417904..5234f60659 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/ValidationFailureUtils.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/specloader/validator/ValidationFailureUtils.java
@@ -19,14 +19,24 @@
 package org.apache.causeway.core.metamodel.specloader.validator;
 
 import java.lang.annotation.Annotation;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
+import org.springframework.lang.Nullable;
+
+import org.apache.causeway.commons.collections.Can;
+import org.apache.causeway.commons.internal.functions._Predicates;
+import org.apache.causeway.commons.internal.primitives._Ints;
+import org.apache.causeway.commons.internal.primitives._Ints.Range;
 import org.apache.causeway.core.config.progmodel.ProgrammingModelConstants;
+import org.apache.causeway.core.metamodel.facetapi.Facet.Precedence;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.FacetedMethod;
 import org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
 import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
 import org.apache.causeway.core.metamodel.spec.feature.ObjectMember;
 
+import lombok.val;
 import lombok.experimental.UtilityClass;
 
 @UtilityClass
@@ -73,57 +83,76 @@ public final class ValidationFailureUtils {
     }
 
 
-    //XXX assumes that given mandatoryFacet is one of the top ranking
-    @Deprecated // marked deprecated, because not implemented
-    public void raiseIfConflictingOptionality(final MandatoryFacet mandatoryFacet, final String message) {
+    private static final Range PRECEDENCE_ORDINALS_CONSIDERED_FOR_CONFLICTING_OPTIONALITY =
+            _Ints.rangeClosed(Precedence.LOW.ordinal(), Precedence.HIGH.ordinal());
+
+    /**
+     * As called on the fly during {@link MandatoryFacet} creation,
+     * not only checks the final top rank but also intermediate top ranks.
+     * However, only considering {LOW, DEFAULT and HIGH}. Others are ignored.
+     *
+     * @apiNote as an alternative we could do a check only on the top rank once the MM is fully populated
+     */
+    public void raiseIfConflictingOptionality(
+            final @Nullable MandatoryFacet mandatoryFacet, final Supplier<String> messageSupplier) {
 
-        /* XXX yet has false positives
-        if(isConflictingOptionality(mandatoryFacet)) {
-            addFailure(mandatoryFacet, message);
+        if(mandatoryFacet == null
+                || !PRECEDENCE_ORDINALS_CONSIDERED_FOR_CONFLICTING_OPTIONALITY
+                    .contains(mandatoryFacet.getPrecedence().ordinal())) {
+            return; // ignore
         }
-        */
-    }
 
-    // -- HELPER
+        final Can<MandatoryFacet> conflictingFacets = conflictingMandatoryFacets(mandatoryFacet);
 
-    /*
-    private void addFailure(final MandatoryFacet mandatoryFacet, final String message) {
-        if(mandatoryFacet != null) {
+
+        if(conflictingFacets.isNotEmpty()) {
             val holder = mandatoryFacet.getFacetHolder();
-            ValidationFailure.raiseFormatted(
-                    holder,
-                    "%s : %s",
-                    message,
-                    holder.getFeatureIdentifier().getFullIdentityString());
+
+            ValidationFailure.raiseFormatted(holder,
+                    ProgrammingModelConstants.Violation.CONFLICTING_OPTIONALITY.builder()
+                        .addVariable("member", holder.getFeatureIdentifier().getFullIdentityString())
+                        .addVariable("conflictingFacets", conflictingFacets.stream()
+                                .map(MandatoryFacet::summarize)
+                                .collect(Collectors.joining(", \n")))
+                        .buildMessage());
         }
     }
 
-    private boolean isConflictingOptionality(final MandatoryFacet mandatoryFacet) {
-        if (mandatoryFacet == null) {
-            return false;
-        }
+    // -- HELPER
+
+    private Can<MandatoryFacet> conflictingMandatoryFacets(final MandatoryFacet mandatoryFacet) {
 
         //TODO maybe move this kind of logic to FacetRanking
 
         val facetRanking = mandatoryFacet.getSharedFacetRankingElseFail();
 
         // assumes that given mandatoryFacet is one of the top ranking
-        _Assert.assertEquals(
-                mandatoryFacet.getPrecedence(),
-                facetRanking.getTopPrecedence().orElse(null));
+
+        val isTopRanking = mandatoryFacet.getPrecedence()
+                .equals(facetRanking.getTopPrecedence().orElse(null));
+        if(!isTopRanking) {
+            // ignore validation of lower than top-rank
+            return Can.empty();
+        }
 
         val topRankingFacets = facetRanking.getTopRank(mandatoryFacet.facetType());
-        val firstOfTopRanking = topRankingFacets.getFirstElseFail();
+        val firstOfTopRanking = (MandatoryFacet)topRankingFacets.getFirstElseFail();
 
         // the top ranking mandatory facets should semantically agree
-
-        return topRankingFacets.isCardinalityMultiple()
+        final Can<MandatoryFacet> conflictingWithFirst = topRankingFacets.isCardinalityMultiple()
                 ? topRankingFacets
                         .stream()
                         .skip(1)
-                        .anyMatch(firstOfTopRanking::semanticEquals)
-                : false; // not conflicting
-
-    }*/
+                        .map(MandatoryFacet.class::cast) // upcast
+                        .filter(_Predicates.not(firstOfTopRanking::semanticEquals))
+                        .collect(Can.toCan())
+                : Can.empty(); // not conflicting
+
+        // if there are any conflicts, prepend first facet
+        return conflictingWithFirst.isEmpty()
+                ? conflictingWithFirst
+                : conflictingWithFirst
+                    .add(0, firstOfTopRanking);
+    }
 
 }
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/tenancy/dom/ApplicationTenancy.java b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/tenancy/dom/ApplicationTenancy.java
index b3405e1e88..b8ba283129 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/tenancy/dom/ApplicationTenancy.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/causeway/extensions/secman/applib/tenancy/dom/ApplicationTenancy.java
@@ -32,6 +32,7 @@ import org.apache.causeway.applib.annotation.DomainObject;
 import org.apache.causeway.applib.annotation.DomainObjectLayout;
 import org.apache.causeway.applib.annotation.Editing;
 import org.apache.causeway.applib.annotation.ObjectSupport;
+import org.apache.causeway.applib.annotation.Optionality;
 import org.apache.causeway.applib.annotation.Parameter;
 import org.apache.causeway.applib.annotation.ParameterLayout;
 import org.apache.causeway.applib.annotation.Programmatic;
@@ -129,7 +130,8 @@ public abstract class ApplicationTenancy implements Comparable<ApplicationTenanc
     // -- PATH
 
     @Property(
-            domainEvent = Parent.DomainEvent.class
+            domainEvent = Parent.DomainEvent.class,
+            optionality = Optionality.MANDATORY
     )
     @PropertyLayout(
             fieldSetId = "identity"
diff --git a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
index 289d63c361..1d49e9a782 100644
--- a/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
+++ b/persistence/jdo/datanucleus/src/main/java/org/apache/causeway/persistence/jdo/datanucleus/metamodel/facets/entity/JdoEntityFacet.java
@@ -53,7 +53,6 @@ import org.apache.causeway.core.config.beans.PersistenceStack;
 import org.apache.causeway.core.metamodel.facetapi.FacetAbstract;
 import org.apache.causeway.core.metamodel.facetapi.FacetHolder;
 import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet;
-import org.apache.causeway.core.metamodel.facets.object.entity.EntityFacet.PrimaryKeyType;
 import org.apache.causeway.core.metamodel.object.ManagedObject;
 import org.apache.causeway.core.metamodel.objectmanager.ObjectManager;
 import org.apache.causeway.core.metamodel.services.idstringifier.IdStringifierLookupService;
diff --git a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromAbsenceOfColumnAnnotation.java b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromAbsenceOfColumnAnnotation.java
index 966b708938..985adbec7f 100644
--- a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromAbsenceOfColumnAnnotation.java
+++ b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromAbsenceOfColumnAnnotation.java
@@ -28,8 +28,8 @@ public class MandatoryFacetFromAbsenceOfColumnAnnotation
 extends MandatoryFacetAbstract {
 
     public MandatoryFacetFromAbsenceOfColumnAnnotation(
-            final FacetHolder holder, final Semantics semantics, final Precedence precedence) {
-        super(holder, semantics, precedence);
+            final Semantics semantics, final FacetHolder holder, final Precedence precedence) {
+        super(semantics, holder, precedence);
     }
 
 
diff --git a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromColumnAnnotation.java b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromColumnAnnotation.java
index 28a8af4c5f..5e159b19db 100644
--- a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromColumnAnnotation.java
+++ b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFacetFromColumnAnnotation.java
@@ -28,8 +28,8 @@ public class MandatoryFacetFromColumnAnnotation
 extends MandatoryFacetAbstract {
 
     public MandatoryFacetFromColumnAnnotation(
-            final FacetHolder holder, final Semantics semantics) {
-        super(holder, semantics);
+            final Semantics semantics, final FacetHolder holder) {
+        super(semantics, holder);
     }
 
 }
diff --git a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFromColumnAnnotationFacetFactory.java b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFromColumnAnnotationFacetFactory.java
index 4f85230d9c..8bbdd0b0e8 100644
--- a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFromColumnAnnotationFacetFactory.java
+++ b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/column/MandatoryFromColumnAnnotationFacetFactory.java
@@ -85,13 +85,13 @@ implements MetaModelRefiner {
         _ColumnUtil.inferSemantics(processMethodContext,
                 semanticsWhileColumnPresent->{
                     FacetUtil.addFacet(
-                            new MandatoryFacetFromColumnAnnotation(holder, semanticsWhileColumnPresent));
+                            new MandatoryFacetFromColumnAnnotation(semanticsWhileColumnPresent, holder));
                 },
                 semanticsWhileColumnAbsent->{
                     FacetUtil.addFacet(
                             new MandatoryFacetFromAbsenceOfColumnAnnotation(
-                                    holder,
                                     semanticsWhileColumnAbsent,
+                                    holder,
                                     semanticsWhileColumnAbsent.isRequired()
                                         ? Precedence.DEFAULT
                                         : Precedence.INFERRED));
diff --git a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/MandatoryFacetFromJdoPrimaryKeyAnnotation.java b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/MandatoryFacetFromJdoPrimaryKeyAnnotation.java
index 7d80a57386..2fd6557651 100644
--- a/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/MandatoryFacetFromJdoPrimaryKeyAnnotation.java
+++ b/persistence/jdo/metamodel/src/main/java/org/apache/causeway/persistence/jdo/metamodel/facets/prop/primarykey/MandatoryFacetFromJdoPrimaryKeyAnnotation.java
@@ -37,7 +37,7 @@ public class MandatoryFacetFromJdoPrimaryKeyAnnotation
 extends MandatoryFacetAbstract {
 
     public MandatoryFacetFromJdoPrimaryKeyAnnotation(final FacetHolder holder) {
-        super(holder, Semantics.OPTIONAL);
+        super(Semantics.OPTIONAL, holder, Precedence.INFERRED);
     }
 
 }
diff --git a/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFacetFromJpaColumnAnnotation.java b/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFacetFromJpaColumnAnnotation.java
index f6d1aaefac..bda4b32148 100644
--- a/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFacetFromJpaColumnAnnotation.java
+++ b/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFacetFromJpaColumnAnnotation.java
@@ -27,10 +27,9 @@ import org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.Mandatory
 public class MandatoryFacetFromJpaColumnAnnotation
 extends MandatoryFacetAbstract {
 
-
-    public MandatoryFacetFromJpaColumnAnnotation(final FacetHolder holder, final boolean required) {
-        super(holder, Semantics.required(required));
+    public MandatoryFacetFromJpaColumnAnnotation(
+            final Semantics semantics, final FacetHolder holder) {
+        super(semantics, holder);
     }
 
-
 }
diff --git a/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFromJpaColumnAnnotationFacetFactory.java b/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFromJpaColumnAnnotationFacetFactory.java
index 5a486c93a0..e24ef5951a 100644
--- a/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFromJpaColumnAnnotationFacetFactory.java
+++ b/persistence/jpa/metamodel/src/main/java/org/apache/causeway/persistence/jpa/metamodel/facets/prop/column/MandatoryFromJpaColumnAnnotationFacetFactory.java
@@ -28,6 +28,7 @@ import org.apache.causeway.core.metamodel.context.MetaModelContext;
 import org.apache.causeway.core.metamodel.facetapi.FacetUtil;
 import org.apache.causeway.core.metamodel.facetapi.FeatureType;
 import org.apache.causeway.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.causeway.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet.Semantics;
 
 import lombok.val;
 
@@ -56,11 +57,11 @@ extends FacetFactoryAbstract {
         }
 
         val nullable = nullable1.orElseGet(nullable2::get);
+        val semantics = Semantics.required(!nullable);
 
         val facetHolder = processMethodContext.getFacetHolder();
-        FacetUtil.addFacet(new MandatoryFacetFromJpaColumnAnnotation(
-                facetHolder,
-                !nullable));
+        FacetUtil.addFacet(
+                new MandatoryFacetFromJpaColumnAnnotation(semantics, facetHolder));
     }