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"));
}