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 3c68f82  ISIS-2681: Allow members of a type hierarchy including 
interfaces to share the same objectType=...
3c68f82 is described below

commit 3c68f82dfb27a7f78b37b63d5d06eda21c9e5b59
Author: [email protected] <[email protected]@luna>
AuthorDate: Sat May 15 09:30:28 2021 +0200

    ISIS-2681: Allow members of a type hierarchy including interfaces to
    share the same objectType=...
---
 .../DomainObjectAnnotationFacetFactory.java        | 44 +++++++++++++++---
 .../metamodel/specloader/LogicalTypeResolver.java  | 23 ++++++++++
 .../specloader/LogicalTypeResolverDefault.java     | 52 ++++++++++++++--------
 .../DomainModelTest_usingBadDomain.java            |  6 +--
 4 files changed, 97 insertions(+), 28 deletions(-)

diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
index abcfa7f..80f2f8c 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/domainobject/DomainObjectAnnotationFacetFactory.java
@@ -494,26 +494,35 @@ implements
 
                     @Override
                     public void validate(ObjectSpecification objSpec) {
+
+                        // Allow members of a type hierarchy including 
interfaces to share the same
+                        // @DomainObject(objectType=...)
+                        // Eg. having an ApplicationUser interface and a 
concrete ApplicationUser (JDO)
+                        // that have the same @DomainObject(objectType=...) 
should be allowed.
+                        // The only constraint that applies, is that there 
cannot be multiple bookmark-able
+                        // types that share the same 
@DomainObject(objectType=...).
+                        // This must be guaranteed by MM validation.
+                        // - see also LogicalTypeResolver.register(...)
+
                         if(objSpec.isManagedBean()
-                                // || objSpec.isAbstract() // we allow 
abstract types now to have their own logical name
-                                ) {
+                                || objSpec.isAbstract()) {
                             return;
                         }
-                        
collidingSpecsByLogicalTypeName.putElement(objSpec.getLogicalTypeName() , 
objSpec);
+                        
collidingSpecsByLogicalTypeName.putElement(objSpec.getLogicalTypeName(), 
objSpec);
                     }
 
                     @Override
                     public void summarize() {
                         for (val logicalTypeName : 
collidingSpecsByLogicalTypeName.keySet()) {
                             val collidingSpecs = 
collidingSpecsByLogicalTypeName.get(logicalTypeName);
-                            val isCollision = collidingSpecs.size()>1;
-                            if(isCollision) {
+                            if(isObjectTypeCollision(collidingSpecs)) {
                                 val csv = asCsv(collidingSpecs);
 
                                 collidingSpecs.forEach(spec->{
                                     ValidationFailure.raiseFormatted(
                                             spec,
-                                            "Logical-type-name (aka. 
object-type) '%s' mapped to multiple classes: %s",
+                                            "Logical-type-name (aka. 
object-type) '%s' mapped to multiple classes,"
+                                            + " that do not all share the same 
type hierarchy:\n %s",
                                             logicalTypeName,
                                             csv);
                                 });
@@ -525,6 +534,29 @@ implements
                         collidingSpecsByLogicalTypeName.clear();
                     }
 
+                    // detect whether specs (of concrete type) belong to more 
than one type hierarchy
+                    private boolean isObjectTypeCollision(final 
List<ObjectSpecification> specs) {
+                        if(specs.size()<=1) {
+                            return false;
+                        }
+                        // algorithm: check all non-first against the first
+
+                        val first = specs.get(0);
+
+                        val shareSameTypeHierarchy = specs.stream()
+                                .skip(1)
+                                .allMatch(next->shareSameTypeHierarchy(first, 
next));
+
+                        return !shareSameTypeHierarchy;
+                    }
+
+                    private boolean shareSameTypeHierarchy(
+                            final @NonNull ObjectSpecification  a, final 
@NonNull ObjectSpecification b) {
+                        return a.equals(b)
+                                || 
a.getCorrespondingClass().isAssignableFrom(b.getCorrespondingClass())
+                                || 
b.getCorrespondingClass().isAssignableFrom(a.getCorrespondingClass());
+                    }
+
                     private String asCsv(final List<ObjectSpecification> 
specList) {
                         return stream(specList)
                                 .map(ObjectSpecification::getFullIdentifier)
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolver.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolver.java
index cd18141..60e603b 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolver.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolver.java
@@ -20,17 +20,40 @@ package org.apache.isis.core.metamodel.specloader;
 
 import java.util.Optional;
 
+import org.apache.isis.applib.annotation.DomainObject;
 import org.apache.isis.applib.id.LogicalType;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 import lombok.NonNull;
 
+/**
+ * Provides a lookup table for the purpose of recreating domain objects from 
bookmarks,
+ * in support of logical type names.
+ *
+ * @apiNote only bookmark-able types will be ever registered
+ * @see DomainObject#objectType()
+ *
+ * @since 2.0
+ */
 interface LogicalTypeResolver {
 
+    /**
+     * Optionally returns the bookmark-able concrete type as registered by 
given {@code logicalTypeName},
+     * based on whether there had been registered any.
+     * @param logicalTypeName
+     */
     Optional<LogicalType> lookup(@NonNull String logicalTypeName);
 
+    /**
+     * Collects concrete types, ignores abstract types and interfaces.
+     * Allows types to override their concrete super types.
+     * @param spec - type's ObjectSpecification
+     */
     void register(@NonNull ObjectSpecification spec);
 
+    /**
+     * Removes all entries from the lookup table.
+     */
     void clear();
 
 }
\ No newline at end of file
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolverDefault.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolverDefault.java
index 6f10747..580ae72 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolverDefault.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/LogicalTypeResolverDefault.java
@@ -48,26 +48,10 @@ class LogicalTypeResolverDefault implements 
LogicalTypeResolver {
 
     @Override
     public void register(final @NonNull ObjectSpecification spec) {
-        if(hasUsableSpecId(spec)) {
-
-            val key = spec.getLogicalTypeName();
-
-            val previousMapping = logicalTypeByName.put(key, 
spec.getLogicalType());
-
-            if(previousMapping!=null) {
-
-                val msg = String.format("failed to register mapping\n"
-                        + "%s -> %s,\n"
-                        + "because there was already a mapping\n "
-                        + "%s -> %s",
-                        key,
-                        spec.getCorrespondingClass(),
-                        key,
-                        previousMapping.getCorrespondingClass());
-
-                log.warn(msg);
-            }
+        if(!spec.isAbstract()
+                && hasUsableSpecId(spec)) {
 
+            logicalTypeByName.merge(spec.getLogicalTypeName(), 
spec.getLogicalType(), this::mostSpecializedOfConcrete);
         }
     }
 
@@ -79,4 +63,34 @@ class LogicalTypeResolverDefault implements 
LogicalTypeResolver {
         return spec.containsNonFallbackFacet(ObjectSpecIdFacet.class);
     }
 
+    private LogicalType mostSpecializedOfConcrete(final @NonNull LogicalType 
a, final @NonNull LogicalType b) {
+        if(a.equals(b)) {
+            return a;
+        }
+        
if(a.getCorrespondingClass().isAssignableFrom(b.getCorrespondingClass())) {
+            return b;
+        }
+        
if(b.getCorrespondingClass().isAssignableFrom(a.getCorrespondingClass())) {
+            return a;
+        }
+
+        val key = a.getLogicalTypeName();
+
+        val msg = String.format("Failed to register mapping\n"
+                + "%s -> %s,\n"
+                + "because there was already a mapping\n "
+                + "%s -> %s.\n"
+                + "Meta-model validation should detect this and fail, if not - 
that's a bug.",
+                key,
+                b.getCorrespondingClass(),
+                key,
+                a.getCorrespondingClass());
+
+        log.warn(msg);
+
+        // do not fail fast, but clear the entry and let MM validation fail 
later on
+        return null;
+    }
+
+
 }
diff --git 
a/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
 
b/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
index 3a91f57..29c88ee 100644
--- 
a/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
+++ 
b/regressiontests/stable-domainmodel/src/test/java/org/apache/isis/testdomain/domainmodel/DomainModelTest_usingBadDomain.java
@@ -155,17 +155,17 @@ class DomainModelTest_usingBadDomain {
                 "Action method overloading is not allowed"));
     }
 
-    @Test// @Disabled("there is a fail fast guard now in 
LogicalTypeResolverDefault.register(...) that breaks this test")
+    @Test
     void logicalTypeNameClash_shouldFail() {
         assertTrue(
             validator.anyMatchesContaining(
                     
Identifier.classIdentifier(LogicalType.fqcn(InvalidLogicalTypeNameClash.VariantA.class)),
                     "Logical-type-name (aka. object-type) 
'isis.testdomain.InvalidLogicalTypeNameClash' "
-                    + "mapped to multiple classes:")
+                    + "mapped to multiple classes")
             || validator.anyMatchesContaining(
                     
Identifier.classIdentifier(LogicalType.fqcn(InvalidLogicalTypeNameClash.VariantB.class)),
                     "Logical-type-name (aka. object-type) 
'isis.testdomain.InvalidLogicalTypeNameClash' "
-                    + "mapped to multiple classes:"));
+                    + "mapped to multiple classes"));
     }
 
 

Reply via email to