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; }
+// }
}