This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 266a4e9964e5483b53456895641f49c1a580fcab
Author: Andi Huber <ahu...@apache.org>
AuthorDate: Fri Jan 12 16:11:31 2018 +0100

    ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate
    with Isis' meta-model
---
 .../services/navparent/NavigableParentService.java |  20 +++
 .../isis/core/commons/reflection/Reflect.java      | 174 +++++++++++++++++++++
 .../isis/core/metamodel/facets/Annotations.java    |   2 +-
 .../object/navparent/NavigableParentFacet.java     |  44 ++++++
 .../navparent/NavigableParentFacetAbstract.java    |  36 +++++
 .../NavigableParentAnnotationFacetFactory.java     | 163 +++++++++++++++++++
 .../method/NavigableParentFacetMethod.java         |  68 ++++++++
 .../method/NavigableParentFacetMethodFactory.java  |  64 ++++++++
 .../core/metamodel/spec/ObjectSpecification.java   |  12 ++
 .../specimpl/ObjectSpecificationAbstract.java      |  12 ++
 .../core/metamodel/util/pchain/ParentChain.java    |  63 +++-----
 ...ingParentChain.java => ParentChainDefault.java} |  43 ++---
 .../metamodel/util/pchain/SimpleParentChain.java   |  75 ---------
 .../isis/core/metamodel/util/pchain/SoftCache.java | 124 ---------------
 .../dflt/ProgrammingModelFacetsJava5.java          |   4 +
 .../NavigableParentFacetMethodFactoryTest.java     |  70 +++++++++
 .../navparent/NavigableParentFacetMethodTest.java  |  83 ++++++++++
 .../NavigableParentAnnotationFacetFactoryTest.java | 120 ++++++++++++++
 .../annotation/NavigableParentTestSamples.java     |  49 ++++++
 .../testspec/ObjectSpecificationStub.java          |   5 +
 .../model/models/whereami/WhereAmIModel.java       |   2 +-
 .../models/whereami/WhereAmIModelDefault.java      |  18 ++-
 22 files changed, 968 insertions(+), 283 deletions(-)

diff --git 
a/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
 
b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
new file mode 100644
index 0000000..129122a
--- /dev/null
+++ 
b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
@@ -0,0 +1,20 @@
+package org.apache.isis.applib.services.navparent;
+
+import org.apache.isis.applib.annotation.Programmatic;
+
+/**
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ */
+public interface NavigableParentService {
+
+    /**
+     * Return the navigable parent (a domain-object or a domain-view-model) of 
the object, 
+     * used to build a navigable parent chain as required by the 'where-am-I' 
feature.
+     * 
+     */
+    @Programmatic
+    public Object navigableParentOf(Object domainObject);
+       
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
new file mode 100644
index 0000000..59bc1c8
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.core.commons.reflection;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+/**
+ * 
+ * Provides shortcuts for common java.lang.reflect idioms.
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ *
+ */
+public class Reflect {
+
+       public static Object[] emptyObjects = {};
+       public static Class<?>[] emptyClasses = {};
+       
+       // -- CLASS REFLECTION
+       
+       /**
+        * Returns declared methods of this class/interface and all super 
classes/interfaces.
+        * @param type
+        * @return
+        */
+       public static List<Method> getAllDeclaredMethods(Class<?> type) {
+               final List<Method> methods = new ArrayList<>();
+
+               Stream.of(type.getDeclaredMethods()).forEach(methods::add);
+               
visitInterfaces(type,c->Stream.of(c.getDeclaredMethods()).forEach(methods::add));
+               
visitSuperclassesOf(type,c->Stream.of(c.getDeclaredMethods()).forEach(methods::add));
+               return methods;
+       }
+
+       /**
+        * Returns declared fields of this class/interface and all super 
classes/interfaces.
+        * @param type
+        * @return
+        */
+       public static List<Field> getAllDeclaredFields(Class<?> type) {
+               final List<Field> fields = new ArrayList<>();
+
+               Stream.of(type.getDeclaredFields()).forEach(fields::add);
+               
visitInterfaces(type,c->Stream.of(c.getDeclaredFields()).forEach(fields::add));
+               
visitSuperclassesOf(type,c->Stream.of(c.getDeclaredFields()).forEach(fields::add));
+               return fields;
+       }
+       
+       public static void visitSuperclassesOf(final Class<?> clazz, final 
Consumer<Class<?>> visitor){
+               final Class<?> superclass = clazz.getSuperclass();
+               if(superclass!=null){
+                       visitor.accept(superclass);
+                       visitSuperclassesOf(superclass, visitor);
+               }
+       }
+
+       public static void visitInterfaces(final Class<?> clazz, final 
Consumer<Class<?>> visitor){
+               if(clazz.isInterface())
+                       visitor.accept(clazz);
+
+               for(Class<?> interf : clazz.getInterfaces())
+                       visitor.accept(interf);
+       }
+       
+       public static Method getGetter(Class<?> cls, String propertyName) 
throws IntrospectionException {
+               final BeanInfo beanInfo = Introspector.getBeanInfo(cls);
+               for(PropertyDescriptor pd:beanInfo.getPropertyDescriptors()){
+                       if(!pd.getName().equals(propertyName))
+                               continue;
+                       return pd.getReadMethod();
+               }
+               return null;    
+       }
+       
+       public static Method getGetter(Object bean, String propertyName) throws 
IntrospectionException {
+               if(bean==null)
+                       return null;
+               return getGetter(bean, propertyName);   
+       }
+       
+       
+       // -- PRIMITIVE TYPES
+
+       private static final Set<Class<?>> primitives = new 
HashSet<>(Arrays.asList(
+                       boolean.class,
+                       byte.class,
+                       char.class,
+                       double.class, 
+                       float.class,
+                       int.class,
+                       long.class,
+                       short.class
+                       //void.class //separated out into its own predicate: 
isVoid(...)
+                       ));
+
+       private static final Set<Class<?>> primitiveWrappers = new 
HashSet<>(Arrays.asList(
+                       Boolean.class,
+                       Byte.class,
+                       Character.class,
+                       Double.class,
+                       Float.class,
+                       Integer.class,
+                       Long.class,
+                       Short.class
+                       //Void.class //separated out into its own predicate: 
isVoid(...)
+                       ));
+       
+       // -- TYPE PREDICATES
+
+       public static boolean isVoid(Class<?> c) {
+               Objects.requireNonNull(c);
+               return c == void.class || c == Void.class;
+       }
+
+       public static boolean isPrimitive(Class<?> c) {
+               Objects.requireNonNull(c);
+               return primitives.contains(c);
+       }
+
+       public static boolean isPrimitiveWrapper(Class<?> c) {
+               Objects.requireNonNull(c);
+               return primitiveWrappers.contains(c);
+       }
+
+
+       // -- METHOD PREDICATES
+
+       public static boolean isNoArg(Method m) {
+               Objects.requireNonNull(m);
+               return m.getParameterTypes().length==0;
+       }
+
+       public static boolean isPublic(Method m) {
+               Objects.requireNonNull(m);
+               return Modifier.isPublic(m.getModifiers());
+       }
+
+       public static boolean isVoid(Method m) {
+               Objects.requireNonNull(m);
+               return isVoid(m.getReturnType());
+       }
+
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
index b2aaeda..5a4a45e 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
@@ -435,7 +435,7 @@ public final class Annotations  {
         }
     }
 
-    static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
+    public static class FieldEvaluator<T extends Annotation> extends 
Evaluator<T> {
         private final Field field;
 
         FieldEvaluator(final Field field, final T annotation) {
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
new file mode 100644
index 0000000..252eff3
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+
+/**
+ * 
+ * Mechanism for obtaining the navigable parent (a domain-object or a 
domain-view-model) 
+ * of an instance of a class, used to build a navigable parent chain as 
required by the 
+ * 'where-am-I' feature.
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ *
+ */
+public interface NavigableParentFacet extends Facet {
+
+       /**
+        * Returns the navigable parent (a domain-object or a 
domain-view-model) for the target object
+        * or null if there is no parent.
+        * @param object
+        * @return 
+        */
+    Object navigableParent(final Object object);
+       
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
new file mode 100644
index 0000000..b3468ac
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+
+public abstract class NavigableParentFacetAbstract extends FacetAbstract 
implements NavigableParentFacet {
+
+    public static Class<? extends Facet> type() {
+        return NavigableParentFacet.class;
+    }
+
+    public NavigableParentFacetAbstract(final FacetHolder holder) {
+        super(type(), holder, Derivation.NOT_DERIVED);
+    }
+
+}
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
new file mode 100644
index 0000000..f3b2102
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -0,0 +1,163 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Parent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.MethodFinderUtils;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.methodutils.MethodScope;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
+import 
org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import 
org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
+import 
org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
+
+/**
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentAnnotationFacetFactory extends 
FacetFactoryAbstract implements MetaModelValidatorRefiner {
+
+    private static final String NAVIGABLE_PARENT_METHOD_NAME = "parent";
+
+
+    public NavigableParentAnnotationFacetFactory() {
+        super(FeatureType.OBJECTS_ONLY);
+    }
+
+    @Override
+    public void process(final ProcessClassContext processClassContext) {
+        final Class<?> cls = processClassContext.getCls();
+        final FacetHolder facetHolder = processClassContext.getFacetHolder();
+
+        final List<Annotations.Evaluator<Parent>> evaluators = 
Annotations.getEvaluators(cls, Parent.class);
+        if (evaluators.isEmpty()) {
+            return;
+        } else if (evaluators.size()>1) {
+               throw new RuntimeException("unable to determine navigable 
parent due to ambiguity");
+        }
+        
+        final Annotations.Evaluator<Parent> parentEvaluator = 
evaluators.get(0);
+        
+        final Method method;
+
+        // find method that provides the parent ...
+        if(parentEvaluator instanceof Annotations.MethodEvaluator) {
+               // we have a @Parent annotated method
+               method = ((Annotations.MethodEvaluator<Parent>) 
parentEvaluator).getMethod();
+        } else if(parentEvaluator instanceof Annotations.FieldEvaluator) {
+               // we have a @Parent annotated field (occurs if one uses 
lombok's @Getter on a field)
+               final Field field = ((Annotations.FieldEvaluator<Parent>) 
parentEvaluator).getField();
+               try {
+                               method = Reflect.getGetter(cls, 
field.getName());
+                       } catch (IntrospectionException e) {
+                               return;
+                       }
+        } else {
+               return;
+        }
+        
+        try {
+                       FacetUtil.addFacet(new 
NavigableParentFacetMethod(method, facetHolder));
+               } catch (IllegalAccessException e) {
+                       e.printStackTrace();
+               }
+    }
+
+
+    /**
+     * Violation if there is a class that has both a <tt>parent()</tt> method 
and also 
+     * any non-inherited method annotated with <tt>@Parent</tt>.
+     * <p>
+     * If there are only inherited methods annotated with <tt>@Parent</tt> 
then this is 
+     * <i>not</i> a violation; but the imperative <tt>parent()</tt> method 
will take precedence.
+     * </p>
+     */
+    @Override
+    public void refineMetaModelValidator(MetaModelValidatorComposite 
metaModelValidator, IsisConfiguration configuration) {
+        metaModelValidator.add(new MetaModelValidatorVisiting(new 
MetaModelValidatorVisiting.Visitor() {
+
+               //TODO [ahuber] code is a copy of the 
TitleAnnotationFacetFactory, not sure ...
+               // 1) what the wanted behavior should be (what about 
annotations in interfaces, ambiguity, etc.)
+               // 2) what this code fragment does
+               
+            @Override
+            public boolean visit(ObjectSpecification objectSpec, 
ValidationFailures validationFailures) {
+                final Class<?> cls = objectSpec.getCorrespondingClass();
+
+                final Method parentMethod =
+                               MethodFinderUtils.findMethod(cls, 
MethodScope.OBJECT, NAVIGABLE_PARENT_METHOD_NAME, Object.class, null);
+                if (parentMethod == null) {
+                    return true; // no conflict
+                }
+                
+                // determine if cls contains a @Parent annotated method, not 
inherited from superclass
+                final Class<?> supClass = cls.getSuperclass();
+                if (supClass == null) {
+                    return true; // no conflict
+                }
+                
+                final List<Method> methods = methodsWithParentAnnotation(cls);
+                final List<Method> superClassMethods = 
methodsWithParentAnnotation(supClass);
+                if (methods.size() > superClassMethods.size()) {
+                    validationFailures.add(
+                            "%s: conflict for determining a strategy for 
retrieval of (navigable) parent for class, "
+                            + "contains a method '%s' and an annotation '@%s'",
+                            objectSpec.getIdentifier().getClassName(),
+                            NAVIGABLE_PARENT_METHOD_NAME,
+                            Parent.class.getName());
+                }
+
+                return true;
+            }
+
+            private List<Method> methodsWithParentAnnotation(final Class<?> 
cls) {
+                return MethodFinderUtils.findMethodsWithAnnotation(cls, 
MethodScope.OBJECT, Parent.class);
+            }
+
+        }));
+    }
+
+
+    @Override
+    public void setServicesInjector(final ServicesInjector servicesInjector) {
+        super.setServicesInjector(servicesInjector);
+        adapterManager = 
servicesInjector.getPersistenceSessionServiceInternal();
+    }
+
+    PersistenceSessionServiceInternal adapterManager;
+
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
new file mode 100644
index 0000000..167a048
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
@@ -0,0 +1,68 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+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 org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacetAbstract;
+
+/**
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentFacetMethod extends NavigableParentFacetAbstract {
+
+       private final MethodHandle methodHandle;
+       
+       public NavigableParentFacetMethod(final Method method, final 
FacetHolder holder) throws IllegalAccessException {
+               super(holder);
+               this.methodHandle = handleOf(method);
+       }
+
+       @Override
+       public Object navigableParent(Object object) {
+               try {
+                       return methodHandle.invoke(object);
+               } catch (final Throwable ex) {
+                       return null;
+               }
+       }
+       
+       // -- HELPER
+       
+       private static MethodHandle handleOf(Method m) throws 
IllegalAccessException {
+               
+               if(!m.isAccessible()) {
+                       m.setAccessible(true);
+                       MethodHandle mh = 
MethodHandles.publicLookup().unreflect(m);
+                       m.setAccessible(false);
+                       return mh;      
+               }
+               
+               return MethodHandles.publicLookup().unreflect(m);
+
+       }
+
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java
new file mode 100644
index 0000000..ad0db2a
--- /dev/null
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java
@@ -0,0 +1,64 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent.method;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.methodutils.MethodScope;
+import org.apache.isis.core.metamodel.facets.MethodFinderUtils;
+import 
org.apache.isis.core.metamodel.facets.MethodPrefixBasedFacetFactoryAbstract;
+
+/**
+ * 
+ * @author ahu...@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentFacetMethodFactory extends 
MethodPrefixBasedFacetFactoryAbstract {
+
+    private static final String NAVIGABLE_PARENT_PREFIX = "parent";
+
+    private static final String[] PREFIXES = { NAVIGABLE_PARENT_PREFIX, };
+
+    public NavigableParentFacetMethodFactory() {
+        super(FeatureType.OBJECTS_ONLY, OrphanValidation.VALIDATE, PREFIXES);
+    }
+
+    @Override
+    public void process(final ProcessClassContext processClassContext) {
+        final Class<?> cls = processClassContext.getCls();
+        final FacetHolder facetHolder = processClassContext.getFacetHolder();
+
+        final Method method = 
+                       MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, 
NAVIGABLE_PARENT_PREFIX, Object.class, NO_PARAMETERS_TYPES);
+        if (method == null) {
+            return;
+        }
+        processClassContext.removeMethod(method);
+        try {
+                       FacetUtil.addFacet(new 
NavigableParentFacetMethod(method, facetHolder));
+               } catch (IllegalAccessException e) {
+                       e.printStackTrace();
+               }
+    }
+}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index 7d4e842..12833a5 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -54,6 +54,8 @@ import 
org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import 
org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
 
+import com.google.common.base.Function;
+
 /**
  * Represents an entity or value (cf {@link java.lang.Class}) within the
  * metamodel.
@@ -209,6 +211,14 @@ public interface ObjectSpecification extends 
Specification, ObjectActionContaine
      * returned by the {@link IconFacet}; is not necessarily immutable.
      */
     String getIconName(ObjectAdapter object);
+    
+    /**
+     * Returns this object's navigable parent, if any. 
+     * @param object
+     * @return
+     * @since 2.0.0
+     */
+    Object getNavigableParent(Object object);
 
     /**
      *
@@ -373,4 +383,6 @@ public interface ObjectSpecification extends Specification, 
ObjectActionContaine
 
     boolean isPersistenceCapable();
     boolean isPersistenceCapableOrViewModel();
+
+       
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index d939016..ab0faa9 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
@@ -66,6 +67,7 @@ import 
org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import 
org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
 import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
 import 
org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
 import 
org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
@@ -91,6 +93,7 @@ import 
org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
+import org.apache.isis.core.metamodel.util.pchain.ParentChain;
 import 
org.apache.isis.objectstore.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacet;
 
 public abstract class ObjectSpecificationAbstract extends FacetHolderImpl 
implements ObjectSpecification {
@@ -160,6 +163,7 @@ public abstract class ObjectSpecificationAbstract extends 
FacetHolderImpl implem
 
     private TitleFacet titleFacet;
     private IconFacet iconFacet;
+    private NavigableParentFacet navigableParentFacet;
     private CssClassFacet cssClassFacet;
 
     private IntrospectionState introspected = 
IntrospectionState.NOT_INTROSPECTED;
@@ -346,6 +350,7 @@ public abstract class ObjectSpecificationAbstract extends 
FacetHolderImpl implem
 
         titleFacet = getFacet(TitleFacet.class);
         iconFacet = getFacet(IconFacet.class);
+        navigableParentFacet = getFacet(NavigableParentFacet.class);
         cssClassFacet = getFacet(CssClassFacet.class);
     }
 
@@ -377,6 +382,13 @@ public abstract class ObjectSpecificationAbstract extends 
FacetHolderImpl implem
     public String getIconName(final ObjectAdapter reference) {
         return iconFacet == null ? null : iconFacet.iconName(reference);
     }
+    
+    @Override
+    public Object getNavigableParent(final Object object) {
+        return navigableParentFacet == null 
+                       ? null 
+                       : navigableParentFacet.navigableParent(object);
+    }
 
     @Deprecated
     @Override
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
index 31ae507..8f9471c 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
@@ -19,17 +19,15 @@
  
 package org.apache.isis.core.metamodel.util.pchain;
 
-import java.lang.reflect.Method;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Stream;
 
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 /**
- * Represents a unidirectional linked ordered set of Pojos (chain), where the 
chain 
+ * Represents a unidirectionally linked ordered set of POJOs (chain), where 
the chain 
  * starts at startNode. Each subsequent node is linked via de-referencing a 
  * singular field (or no-arg method) that is annotated with {@code @Parent}.
  * <br/>
@@ -41,40 +39,26 @@ import org.apache.isis.core.commons.reflection.Reflect;
  */
 public interface ParentChain {
        
-       static ParentChain simple() {
-               return new SimpleParentChain();
-       }
-       
-       static ParentChain caching() {
-               return new CachingParentChain();
+       public static ParentChain of(Function<Class<?>, ObjectSpecification> 
specificationLookup){
+               return new ParentChainDefault(specificationLookup);
        }
        
+       /**
+        * Returns the parent node of this {@code node} or {@code null} if 
{@code node} has no parent.
+        * @param node
+        * @return
+        */
        public Object parentOf(Object node);
        
-       static boolean providesParent(Method m) {
-               if(!Reflect.isNoArg(m))
-                       return false;
-               if(!Reflect.isPublic(m))
-                       return false;
-               if(Reflect.isVoid(m)) 
-                       return false;
-               if(Reflect.isPrimitive(m.getReturnType())) 
-                       return false;
-               
-               if(m.getName().equals("parent"))
-                       return true;
-               
-               if(m.isAnnotationPresent(Parent.class))
-                       return true;
-               
-               return false;
-       }
-
-       default Stream<Object> streamParentChainOf(Object startNode){
+       /**
+        * Returns a Stream of nodes that are chained together by parent 
references. 
+        * The startNode is excluded from the Stream.
+        * @param startNode
+        * @return
+        */
+       public default Stream<Object> streamParentChainOf(Object startNode){
                final Set<Object> chain = new LinkedHashSet<>();
                
-               chain.add(startNode);
-               
                Object next = startNode;
                
                while((next = parentOf(next))!=null) {
@@ -83,17 +67,8 @@ public interface ParentChain {
                                break;
                }
                
-               return chain.stream().skip(1);
+               return chain.stream();
        }
-       
-       default Stream<Object> streamReversedParentChainOf(Object startNode){
-               final LinkedList<Object> reverseChain = new 
LinkedList<Object>();
-               
-               streamParentChainOf(startNode)
-               .forEach(reverseChain::addFirst);
-               
-               return reverseChain.stream();
-       }
-       
+
        
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
similarity index 52%
rename from 
core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
rename to 
core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
index 630e928..2374634 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
@@ -19,48 +19,31 @@
 
 package org.apache.isis.core.metamodel.util.pchain;
 
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
+import java.util.function.Function;
 
-class CachingParentChain extends SimpleParentChain {
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
-       private final SoftCache<Class<?>, MethodHandle> cache = new 
SoftCache<>();
+class ParentChainDefault implements ParentChain {
+       
+       private final Function<Class<?>, ObjectSpecification> 
specificationLookup;
+       
+       ParentChainDefault(Function<Class<?>, ObjectSpecification> 
specificationLookup) {
+               this.specificationLookup = specificationLookup;
+       }
 
        @Override
        public Object parentOf(Object node) {
                if(node==null)
                        return null;
                
-               final MethodHandle mh = cache.computeIfAbsent(node.getClass(), 
-                               key->{
-                                       try {
-                                               return methodHandleOf(node);
-                                       } catch (IllegalAccessException e) {
-                                               e.printStackTrace();
-                                               return null;
-                                       }
-                               });
+               final Class<?> cls = node.getClass();
                
-               if(mh==null)
-                       return null;
+               final ObjectSpecification spec = specificationLookup.apply(cls);
                
-               try {
-                       return mh.invoke(node);
-               } catch (Throwable e) {
-                       e.printStackTrace();
+               if(spec==null)
                        return null;
-               }
                
-       }
-       
-       protected static MethodHandle methodHandleOf(Object node) throws 
IllegalAccessException{
-               final Method getter = parentGetterOf(node);
-               return getter!=null ? handleOf(getter) : null;
-       }
-
-       public static MethodHandle handleOf(Method m) throws 
IllegalAccessException {
-               return MethodHandles.publicLookup().unreflect(m);
+               return spec.getNavigableParent(node);
        }
        
 }
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
deleted file mode 100644
index b30e427..0000000
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.lang.NullSafe;
-import org.apache.isis.core.commons.reflection.Reflect;
-
-class SimpleParentChain implements ParentChain {
-
-       @Override
-       public Object parentOf(Object node) {
-               if(node==null)
-                       return null;
-               
-               final Method getter = parentGetterOf(node);
-               if(getter==null)
-                       return null;
-               
-               try {
-                       return getter.invoke(node, Reflect.emptyObjects);
-               } catch (IllegalAccessException | IllegalArgumentException | 
InvocationTargetException e) {
-                       e.printStackTrace();
-               }
-               
-               return null;
-       }
-       
-       protected static Method parentGetterOf(Object node) {
-               return
-               NullSafe.stream(Reflect.getAllDeclaredMethods(node.getClass()))
-               .filter(ParentChain::providesParent)
-               .findFirst()
-               .orElse(findGetterForAnnotatedField(node));
-       }
-       
-       protected static Method findGetterForAnnotatedField(Object node) {
-               return 
-               NullSafe.stream(Reflect.getAllDeclaredFields(node.getClass()))
-               .filter(f->f.isAnnotationPresent(Parent.class))
-               .findFirst()
-               .map(f->getterOf(node, f.getName()))
-               .orElse(null);
-       }
-       
-       private static Method getterOf(Object bean, String propertyName) {
-               try {
-                       return Reflect.getGetter(bean, propertyName);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       return null;
-               }
-       }
-
-}
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
 
b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
deleted file mode 100644
index 34c1ff6..0000000
--- 
a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-/**
- * Implements a caching {@code Map} where objects are stored referenced by 
- * a unique key, while {@codeMap.Entries} might be garbage collected any time. 
- * 
- * @author ahu...@apache.org
- *
- * @param <K>
- * @param <T>
- */
-class SoftCache<K,T> {
-       
-       private Map<K,SoftReference<T>> data;
-       
-       public SoftCache() {
-               data=newMap();
-       }
-       
-       public SoftCache(Supplier<Map<K,SoftReference<T>>> mapFactory) {
-               data=mapFactory.get();
-       }
-       
-       /**
-        * Note: might be overridden to use a different map implementation for 
storage
-        * @return
-        */
-       protected Map<K,SoftReference<T>> newMap(){
-               return new HashMap<>();
-       }
-       
-       /**
-        * Note: call to this method will fool the garbage collector, 
-        * so that last objects in the entry set will be kept longer, 
-        * due to latest access  
-        * @return number of currently usable SoftReferences 
-        */
-       public int computeSize(){
-               Map<K,SoftReference<T>> keep = newMap();
-               for(Map.Entry<K,SoftReference<T>> entry : data.entrySet()){
-                       if(entry.getValue()!=null) 
keep.put(entry.getKey(),entry.getValue()); 
-               }
-               data.clear();
-               data=keep;
-               return data.size();
-       }
-       
-       // keep private! (result is not guaranteed to be accurate, 
-       // since the garbage collector may change the soft references any time)
-       @SuppressWarnings("unused")
-       private boolean contains(K key){
-               return get(key)!=null;
-       }
-       
-       public void put(K key, T x){
-               data.put(key, new SoftReference<T>(x));
-       }
-       
-       public T get(K key){
-               SoftReference<T> ref = data.get(key); 
-               if(ref==null) {
-                       data.remove(key);
-                       return null;
-               }
-               return ref.get();
-       }
-
-       public void clear() {
-               data.clear();           
-       }
-
-       /**
-        * Tries to fetch a value from cache and returns it if it's a hit. 
-        * Otherwise stores and returns the value supplied by the 
mappingFunction.
-        * @param key
-        * @param mappingFunction
-        * @return either the value stored under key or (if there is no such 
key) the result from the factory 
-        */
-       public T computeIfAbsent(K key, Function<? super K,? extends T> 
mappingFunction){
-               return computeIfAbsent(key,()->mappingFunction.apply(key));
-       }
-       
-       /**
-        * Tries to fetch a value from cache and returns it if it's a hit. 
-        * Otherwise stores and returns the value supplied by the factory.
-        * @param key
-        * @param factory
-        * @return either the value stored under key or (if there is no such 
key) the result from the factory 
-        */
-       public T computeIfAbsent(K key, Supplier<T> factory) {
-               T res = get(key);
-               if(res!=null)
-                       return res;
-               res = factory.get();
-               put(key,res); 
-               return res;
-       }
-       
-}
-
diff --git 
a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
 
b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
index 96e54b9..293dafb 100644
--- 
a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ 
b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -83,6 +83,8 @@ import 
org.apache.isis.core.metamodel.facets.object.ignore.jdo.RemoveJdoPrefixed
 import 
org.apache.isis.core.metamodel.facets.object.immutable.immutableannot.CopyImmutableFacetOntoMembersFactory;
 import 
org.apache.isis.core.metamodel.facets.object.membergroups.annotprop.MemberGroupLayoutFacetFactory;
 import 
org.apache.isis.core.metamodel.facets.object.mixin.MixinFacetForMixinAnnotationFactory;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentAnnotationFacetFactory;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
 import 
org.apache.isis.core.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassNameFactory;
 import 
org.apache.isis.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImplFactory;
 import 
org.apache.isis.core.metamodel.facets.object.parseable.annotcfg.ParseableFacetAnnotationElseConfigurationFactory;
@@ -325,6 +327,8 @@ public final class ProgrammingModelFacetsJava5 extends 
ProgrammingModelAbstract
         addFactory(new TitleAnnotationFacetFactory());
         addFactory(new TitleFacetViaMethodsFactory());
         addFactory(new IconFacetMethodFactory());
+        addFactory(new NavigableParentAnnotationFacetFactory());
+        addFactory(new NavigableParentFacetMethodFactory());
         addFactory(new CssClassFacetMethodFactory());
 
 
diff --git 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
new file mode 100644
index 0000000..b962bbc
--- /dev/null
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
+
+public class NavigableParentFacetMethodFactoryTest extends 
AbstractFacetFactoryTest {
+
+    private NavigableParentFacetMethodFactory facetFactory;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        facetFactory = new NavigableParentFacetMethodFactory();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        facetFactory = null;
+        super.tearDown();
+    }
+
+    public void testNavigableParentMethodPickedUpOnClassAndMethodRemoved() {
+        class Customer {
+            @SuppressWarnings("unused")
+            public Object parent() {
+                return null;
+            }
+        }
+        final Method navigableParentMethod = findMethod(Customer.class, 
"parent");
+
+        facetFactory.process(new ProcessClassContext(Customer.class, 
methodRemover, facetedMethod));
+
+        final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+        assertThat(facet, is(notNullValue()));
+        assertThat(facet, is(instanceOf(NavigableParentFacetMethod.class)));
+
+        
assertTrue(methodRemover.getRemovedMethodMethodCalls().contains(navigableParentMethod));
+    }
+
+}
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
new file mode 100644
index 0000000..70a1dce
--- /dev/null
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import 
org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentFacetMethodTest {
+
+    private final Mockery mockery = new JUnit4Mockery();
+
+    private NavigableParentFacetMethod facet;
+    private FacetHolder mockFacetHolder;
+
+    private ObjectAdapter mockOwningAdapter;
+
+    private DomainObjectWithProblemInNavigableParentMethod pojo;
+
+    public static class DomainObjectWithProblemInNavigableParentMethod {
+        public String parent() {
+            throw new NullPointerException();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+
+        pojo = new DomainObjectWithProblemInNavigableParentMethod();
+        mockFacetHolder = mockery.mock(FacetHolder.class);
+        mockOwningAdapter = mockery.mock(ObjectAdapter.class);
+        final Method navigableParentMethod = 
DomainObjectWithProblemInNavigableParentMethod.class.getMethod("parent");
+        facet = new NavigableParentFacetMethod(navigableParentMethod, 
mockFacetHolder);
+
+        mockery.checking(new Expectations() {
+            {
+                allowing(mockOwningAdapter).getObject();
+                will(returnValue(pojo));
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        facet = null;
+    }
+
+    @Test
+    public void testNavigableParentThrowsException() {
+        final Object parent = 
facet.navigableParent(mockOwningAdapter.getObject());
+        assertThat(parent, is(nullValue()));
+    }
+
+}
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
new file mode 100644
index 0000000..fff951c
--- /dev/null
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
@@ -0,0 +1,120 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import 
org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import 
org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
+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.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.auto.Mock;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentAnnotationFacetFactoryTest extends 
AbstractFacetFactoryJUnit4TestCase {
+
+    private NavigableParentAnnotationFacetFactory facetFactory;
+
+    @Mock
+    private ObjectAdapter mockObjectAdapter;
+    @Mock
+    private AuthenticationSession mockAuthenticationSession;
+
+    @Before
+    public void setUp() throws Exception {
+
+        context.allowing(mockSpecificationLoader);
+
+        facetFactory = new NavigableParentAnnotationFacetFactory();
+        facetFactory.setServicesInjector(mockServicesInjector);
+
+        context.checking(new Expectations() {
+            {
+                
allowing(mockServicesInjector).lookupService(AuthenticationSessionProvider.class);
+                will(returnValue(mockAuthenticationSessionProvider));
+
+                
allowing(mockServicesInjector).lookupService(DeploymentCategoryProvider.class);
+                will(returnValue(mockDeploymentCategoryProvider));
+
+                
allowing(mockDeploymentCategoryProvider).getDeploymentCategory();
+                will(returnValue(DeploymentCategory.PRODUCTION));
+
+                
allowing(mockAuthenticationSessionProvider).getAuthenticationSession();
+                will(returnValue(mockAuthenticationSession));
+
+                allowing(mockServicesInjector).getSpecificationLoader();
+                will(returnValue(mockSpecificationLoader));
+
+                
allowing(mockServicesInjector).getPersistenceSessionServiceInternal();
+                will(returnValue(mockPersistenceSessionServiceInternal));
+            }
+        });
+
+        facetFactory.setServicesInjector(mockServicesInjector);
+
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        facetFactory = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testParentAnnotatedMethod() throws Exception {
+       testParentMethod(new DomainObjectA(), "root");
+    }
+
+    // -- HELPER
+    
+    private void testParentMethod(Object domainObject, String 
parentMethodName) throws Exception {
+       
+       final Class<?> domainClass = domainObject.getClass();
+       
+        facetFactory.process(new ProcessClassContext(domainClass, 
mockMethodRemover, facetedMethod));
+
+        final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+        Assert.assertNotNull(facet);
+        Assert.assertTrue(facet instanceof NavigableParentFacetMethod);
+        
+        final NavigableParentFacetMethod navigableParentFacetMethod = 
(NavigableParentFacetMethod) facet;
+        final Method parentMethod = domainClass.getMethod(parentMethodName);
+        
+        Assert.assertEquals(
+                       parentMethod.invoke(domainObject, 
Reflect.emptyObjects), 
+                       
navigableParentFacetMethod.navigableParent(domainObject)        );
+        
+    }
+    
+    
+    
+}
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
new file mode 100644
index 0000000..4326b72
--- /dev/null
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
@@ -0,0 +1,49 @@
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import org.apache.isis.applib.annotation.Parent;
+
+class NavigableParentTestSamples {
+
+       // has no navigable parent
+       protected static class DomainObjectRoot {
+
+               @Override
+               public String toString() {
+                       return "Root";
+               }
+
+       }
+
+       // has navigable parent 'Root' specified via Annotation
+       protected static class DomainObjectA {
+               
+               private final static Object myParent = new DomainObjectRoot();
+
+               @Override
+               public String toString() {
+                       return "A";
+               }
+
+               @Parent
+               public Object root() {
+                       return myParent;
+               }
+               
+       }
+       
+       // has navigable parent 'A' specified via method
+       protected static class DomainObjectB {
+               
+               private final static Object myParent = new DomainObjectA();
+
+               @Override
+               public String toString() {
+                       return "B";
+               }
+
+               public Object parent() {
+                       return myParent;
+               }
+               
+       }
+}
diff --git 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index 086bb4c..6eb9c84 100644
--- 
a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ 
b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -189,6 +189,11 @@ public class ObjectSpecificationStub extends 
FacetHolderImpl implements ObjectSp
         return null;
     }
 
+       @Override
+       public Object getNavigableParent(Object object) {
+               return null;
+       }
+    
     @Override
     public String getCssClass() {
         return null;
diff --git 
a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
 
b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
index fdc11f2..7d4a8ce 100644
--- 
a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
+++ 
b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
@@ -26,7 +26,7 @@ import org.apache.isis.viewer.wicket.model.models.EntityModel;
 /**
  * Represents a navigable chain of parent nodes starting at the current node. 
  * 
- * @author a.hu...@corax.at
+ * @author ahu...@apache.org
  * 
  * @since 2.0.0
  *
diff --git 
a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
 
b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
index 955c2db..042595f 100644
--- 
a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
+++ 
b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
@@ -19,26 +19,28 @@
 
 package org.apache.isis.viewer.wicket.model.models.whereami;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.LinkedList;
 import java.util.stream.Stream;
 
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.util.pchain.ParentChain;
+import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
 
 class WhereAmIModelDefault implements WhereAmIModel {
 
-       private final List<Object> reversedChainOfParents = new ArrayList<>();
+       private final LinkedList<Object> reversedChainOfParents = new 
LinkedList<>();
        private final EntityModel startOfChain;
        
        public WhereAmIModelDefault(EntityModel startOfChain) {
                this.startOfChain = startOfChain;
                
-               final Object startPojo = startOfChain.getObject().getObject();
-
-               ParentChain.caching()
-               .streamReversedParentChainOf(startPojo)
-               .forEach(reversedChainOfParents::add);
+               final ObjectAdapter adapter = startOfChain.getObject();
+               final Object startNode = adapter.getObject();
+               
+               
ParentChain.of(IsisContext.getSessionFactory().getSpecificationLoader()::loadSpecification)
+               .streamParentChainOf(startNode)
+               .forEach(reversedChainOfParents::addFirst);
        }
        
        @Override

-- 
To stop receiving notification emails like this one, please contact
danhayw...@apache.org.

Reply via email to