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 1075788f4f ISIS-2381: fail early on deficient navigable parent type
1075788f4f is described below

commit 1075788f4f635eb40afd9ef91a8f73f8b92351eb
Author: Andi Huber <[email protected]>
AuthorDate: Wed Oct 5 10:57:04 2022 +0200

    ISIS-2381: fail early on deficient navigable parent type
---
 .../progmodel/ProgrammingModelConstants.java       |  4 ++
 .../NavigableParentAnnotationFacetFactory.java     |  8 +--
 .../method/NavigableParentFacetViaMethod.java      | 75 ++++++++++++++++++++--
 .../navparent/NavigableParentFacetMethodTest.java  | 17 +++--
 .../NavigableParentAnnotationFacetFactoryTest.java | 66 +++++++++++--------
 .../annotation/NavigableParentTestSamples.java     | 53 +++++++--------
 6 files changed, 152 insertions(+), 71 deletions(-)

diff --git 
a/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
 
b/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
index 4cc5f7209a..22b2ac0b67 100644
--- 
a/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
+++ 
b/core/config/src/main/java/org/apache/isis/core/config/progmodel/ProgrammingModelConstants.java
@@ -481,6 +481,9 @@ public final class ProgrammingModelConstants {
         VIEWMODEL_MISSING_DESERIALIZING_CONSTRUCTOR(
                 "${type}: ViewModel contract violation: missing single 
(String) arg constructor "
                 + "(for de-serialization from memento string)."),
+        DOMAIN_OBJECT_INVALID_NAVIGABLE_PARENT("${type}: the object's 
navigable parent must no be void, "
+                + "plural, vetoed or a value-type; "
+                + "yet the parent type '${parentType}' as discovered was 
${parentTypeDeficiency}; "),
         DOMAIN_OBJECT_MISSING_A_NAMESPACE("${type}: the object type must 
declare a namespace, "
                 + "yet there was none found in '${logicalTypeName}'; "
                 + "eg. @Named(\"Customer\") is considered invalid, "
@@ -504,6 +507,7 @@ public final class ProgrammingModelConstants {
                 + "mapped to multiple non-abstract classes:\n"
                 + "${csv}"),
         ;
+
         private final String template;
         public String getMessage(final Identifier featureIdentifier) {
             return getMessageForTypeAndMemberId(
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
index 24966b06ac..3ca415cd38 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -103,12 +103,8 @@ implements MetaModelRefiner {
             return; // no parent resolvable
         }
 
-        try {
-            addFacet(new NavigableParentFacetViaMethod(method, facetHolder));
-        } catch (IllegalAccessException e) {
-            log.warn("failed to create NavigableParentFacetMethod method:{} 
holder:{}",
-                    method, facetHolder, e);
-        }
+        addFacetIfPresent(
+                NavigableParentFacetViaMethod.create(cls, method, 
facetHolder));
     }
 
     private static boolean isNavigableParentFlagSet(final AnnotatedElement 
annotatedElement){
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaMethod.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaMethod.java
index 5ea3809ed6..b732ea0d06 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaMethod.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaMethod.java
@@ -21,10 +21,19 @@ package 
org.apache.isis.core.metamodel.facets.object.navparent.method;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 
+import org.apache.isis.commons.functional.Either;
+import org.apache.isis.core.config.progmodel.ProgrammingModelConstants;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
 import 
org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacetAbstract;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailure;
+
+import lombok.NonNull;
+import lombok.val;
 
 /**
  * @since 2.0
@@ -34,11 +43,35 @@ extends NavigableParentFacetAbstract {
 
     private final MethodHandle methodHandle;
 
-    public NavigableParentFacetViaMethod(
-            final Method method,
-            final FacetHolder holder) throws IllegalAccessException {
+    public static Optional<NavigableParentFacet> create(
+            final @NonNull Class<?> processedClass,
+            final @NonNull Method method,
+            final @NonNull FacetHolder facetHolder) {
+
+
+        return validateNavigableParentType(processedClass, method, facetHolder)
+        .fold(
+            // success
+            methodHandle->
+                Optional.of(new NavigableParentFacetViaMethod(methodHandle, 
facetHolder)),
+            // failure
+            deficiency->{
+                val validationResponse =
+                        
ProgrammingModelConstants.Validation.DOMAIN_OBJECT_INVALID_NAVIGABLE_PARENT;
+                ValidationFailure.raiseFormatted(facetHolder,
+                        validationResponse.getMessage(Map.of(
+                                "type", processedClass.getName(),
+                                "parentType", method.getReturnType().getName(),
+                                "parentTypeDeficiency", deficiency)));
+                return Optional.empty();
+            });
+    }
+
+    protected NavigableParentFacetViaMethod(
+            final MethodHandle methodHandle,
+            final FacetHolder holder) {
         super(holder);
-        this.methodHandle = MethodHandles.lookup().unreflect(method);
+        this.methodHandle = methodHandle;
     }
 
     @Override
@@ -57,4 +90,38 @@ extends NavigableParentFacetAbstract {
         visitor.accept("methodHandle", methodHandle);
     }
 
+    // -- HELPER
+
+    /** Returns either the MethodHandle to use or a deficiency message. */
+    private static Either<MethodHandle, String> validateNavigableParentType(
+            final @NonNull Class<?> processedClass,
+            final @NonNull Method method,
+            final @NonNull FacetHolder holder) {
+
+        val navigableParentSpec = 
holder.getSpecificationLoader().loadSpecification(method.getReturnType());
+        if(navigableParentSpec==null) {
+            return Either.right("vetoed");
+        }
+        if(navigableParentSpec.isPlural()) {
+            return Either.right("plural");
+        }
+        if(navigableParentSpec.isVoid()) {
+            return Either.right("void");
+        }
+        if(navigableParentSpec.isValue()) {
+            return Either.right("value-type");
+        }
+
+        try {
+            val methodHandle = MethodHandles.lookup().unreflect(method);
+            return Either.left(methodHandle);
+        } catch (IllegalAccessException e) {
+            return Either.right(
+                    String.format("'reflection exception while trying to 
create a method handle for %s'\n"
+                            + "(%s)",
+                            method,
+                            e.getMessage()));
+        }
+    }
+
 }
diff --git 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
index 238d638b2d..9f68518866 100644
--- 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
@@ -29,20 +29,23 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
+import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetViaMethod;
 import org.apache.isis.core.metamodel.object.ManagedObject;
 
+import lombok.val;
+
 class NavigableParentFacetMethodTest {
 
-    private NavigableParentFacetViaMethod facet;
-    private FacetHolder mockFacetHolder;
+    private NavigableParentFacet facet;
+    private FacetHolder simpleFacetHolder;
     private ManagedObject mockOwningAdapter;
 
     private DomainObjectWithProblemInNavigableParentMethod pojo;
 
     public static class DomainObjectWithProblemInNavigableParentMethod {
-        public String parent() {
+        public Object parent() {
             throw new NullPointerException();
         }
     }
@@ -50,11 +53,15 @@ class NavigableParentFacetMethodTest {
     @BeforeEach
     public void setUp() throws Exception {
 
+        val mmc = MetaModelContext_forTesting.buildDefault();
+        simpleFacetHolder = FacetHolder.forTesting(mmc);
+
         pojo = new DomainObjectWithProblemInNavigableParentMethod();
-        mockFacetHolder = Mockito.mock(FacetHolder.class);
+
         mockOwningAdapter = Mockito.mock(ManagedObject.class);
         final Method navigableParentMethod = 
DomainObjectWithProblemInNavigableParentMethod.class.getMethod("parent");
-        facet = new NavigableParentFacetViaMethod(navigableParentMethod, 
mockFacetHolder);
+        facet = NavigableParentFacetViaMethod.create(pojo.getClass(), 
navigableParentMethod, simpleFacetHolder)
+                .orElse(null);
 
         Mockito.when(mockOwningAdapter.getPojo()).thenReturn(pojo);
     }
diff --git 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
index b04de60599..59df92741f 100644
--- 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
@@ -19,24 +19,28 @@
 package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
 
 import java.lang.reflect.Method;
+import java.util.stream.Stream;
 
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
-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.lang.Nullable;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import org.apache.isis.commons.internal._Constants;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import 
org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJupiterTestCase;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
 import 
org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
-import 
org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectA;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectInvalidParentAnnot;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectProperAnnot;
 import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetViaMethod;
 
-import lombok.SneakyThrows;
 import lombok.val;
 
 class NavigableParentAnnotationFacetFactoryTest
@@ -47,13 +51,7 @@ extends AbstractFacetFactoryJupiterTestCase {
     @BeforeEach
     void setUp() throws Exception {
         super.setUpMmc();
-        facetFactory = new 
NavigableParentAnnotationFacetFactory(getMetaModelContext()) {
-            @Override
-            public void process(final ProcessClassContext processClassContext) 
{
-                super.process(processClassContext);
-                
assertHasNavigableParentFacet(processClassContext.getFacetHolder());
-            }
-        };
+        super.setUpFacetedMethodAndParameter();
     }
 
     @AfterEach
@@ -63,33 +61,47 @@ extends AbstractFacetFactoryJupiterTestCase {
         super.tearDown();
     }
 
-    @Test @SneakyThrows
-    protected void testParentAnnotatedMethod() throws Exception {
 
-        val domainObject = new DomainObjectA();
-        val parentMethodName = "root";
+    static Stream<Arguments> navigableTypeArgs() {
+        return Stream.of(
+                Arguments.of(new DomainObjectProperAnnot(), "root", null),
+                Arguments.of(new DomainObjectInvalidParentAnnot(), "root",
+                        "the object's navigable parent must no be void, 
plural, vetoed or a value-type; "
+                        + "yet the parent type 'java.math.BigInteger' as 
discovered was value-type")); }
+
+    @ParameterizedTest
+    @MethodSource("navigableTypeArgs")
+    protected void navigableType(
+            final Object domainObject,
+            final String parentMethodName,
+            final @Nullable String expectedValidationMessage) throws Exception 
{
+
         val domainClass = domainObject.getClass();
         val facetedMethod = facetedAction(domainClass, parentMethodName);
 
+        facetFactory = new 
NavigableParentAnnotationFacetFactory(getMetaModelContext());
         facetFactory.process(ProcessClassContext
                 .forTesting(domainClass, defaultMethodRemover(), 
facetedMethod));
 
-        val navigableParentFacet = 
assertHasNavigableParentFacet(facetedMethod);
-        assertTrue(navigableParentFacet instanceof 
NavigableParentFacetViaMethod);
+        if(expectedValidationMessage==null) {
 
-        final Method parentMethod = domainClass.getMethod(parentMethodName);
+            val navigableParentFacet = 
facetedMethod.getFacet(NavigableParentFacet.class);
+            assertNotNull(navigableParentFacet, ()->"NavigableParentFacet 
required");
+            assertTrue(navigableParentFacet instanceof 
NavigableParentFacetViaMethod);
 
-        assertEquals(
-                navigableParentFacet.navigableParent(domainObject),
-                parentMethod.invoke(domainObject, _Constants.emptyObjects));
-    }
+            final Method parentMethod = 
domainClass.getMethod(parentMethodName);
+
+            assertEquals(
+                    navigableParentFacet.navigableParent(domainObject),
+                    parentMethod.invoke(domainObject, 
_Constants.emptyObjects));
+        } else {
 
-    // -- HELPER
+            assertNull(facetedMethod.getFacet(NavigableParentFacet.class));
 
-    NavigableParentFacet assertHasNavigableParentFacet(final FacetHolder 
facetHolder) {
-        val navigableParentFacet = 
facetHolder.getFacet(NavigableParentFacet.class);
-        assertNotNull(navigableParentFacet, ()->"NavigableParentFacet 
required");
-        return navigableParentFacet;
+            val validation = 
getSpecificationLoader().getOrAssessValidationResult();
+            assertTrue(validation.getMessages().stream()
+                    .anyMatch(msg->msg.contains(expectedValidationMessage)));
+        }
     }
 
 }
diff --git 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
index 7632ff5c26..d48f3d56b9 100644
--- 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
@@ -18,6 +18,8 @@
  */
 package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
 
+import java.math.BigInteger;
+
 import org.apache.isis.applib.annotation.Navigable;
 import org.apache.isis.applib.annotation.PropertyLayout;
 
@@ -25,44 +27,37 @@ class NavigableParentTestSamples {
 
     // has no navigable parent
     protected static class DomainObjectRoot {
-
-        @Override
-        public String toString() {
-            return "Root";
-        }
-
+        @Override public String toString() { return "Root"; }
     }
 
     // has navigable parent 'Root' specified via Annotation
-    protected static class DomainObjectA {
-
-        private static final Object myParent = new DomainObjectRoot();
-
-        @Override
-        public String toString() {
-            return "A";
-        }
+    protected static class DomainObjectProperAnnot {
+        private static final DomainObjectRoot myParent = new 
DomainObjectRoot();
+        @Override public String toString() { return "properAnnot"; }
 
         @PropertyLayout(navigable=Navigable.PARENT)
-        public Object root() {
-            return myParent;
-        }
-
+        public DomainObjectRoot root() { return myParent; }
     }
 
-    // has navigable parent 'A' specified via method
-    protected static class DomainObjectB {
-
-        private static final Object myParent = new DomainObjectA();
+//    // has navigable parent 'A' specified via method
+//    protected static class DomainObjectProperMethod {
+//        private static final DomainObjectRoot myParent = new 
DomainObjectRoot();
+//        @Override public String toString() { return "properMethod"; }
+//        public DomainObjectRoot parent() { return myParent; }
+//    }
 
-        @Override
-        public String toString() {
-            return "B";
-        }
+    // has invalid (value-type) navigable parent specified via Annotation
+    protected static class DomainObjectInvalidParentAnnot {
+        @Override public String toString() { return "invalidAnnot"; }
 
-        public Object parent() {
-            return myParent;
-        }
+        @PropertyLayout(navigable=Navigable.PARENT)
+        public BigInteger root() { return BigInteger.ONE; }
 
     }
+
+//    // has invalid (value-type) navigable parent specified via method
+//    protected static class DomainObjectInvalidParentMethod {
+//        @Override public String toString() { return "invalidMethod"; }
+//        public BigInteger parent() { return BigInteger.ONE; }
+//    }
 }

Reply via email to