You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2018/01/12 15:12:21 UTC

[isis] branch ISIS-1740-where-am-i updated: ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate with Isis' meta-model

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

ahuber pushed a commit to branch ISIS-1740-where-am-i
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/ISIS-1740-where-am-i by this push:
     new f5d87ab  ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate with Isis' meta-model
f5d87ab is described below

commit f5d87abdc412f9b1e70bc915cf134cd0919f8c7f
Author: Andi Huber <ah...@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      |  11 +-
 .../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   |  19 ++-
 .../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, 806 insertions(+), 289 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 ahuber@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
index 82cc7e5..59bc1c8 100644
--- 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
@@ -93,8 +93,8 @@ public class Reflect {
 			visitor.accept(interf);
 	}
 	
-	public static Method getGetter(Object bean, String propertyName) throws IntrospectionException {
-		final BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
+	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;
@@ -103,6 +103,13 @@ public class Reflect {
 		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(
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 ahuber@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 ahuber@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 ahuber@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 ahuber@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 dd405ea..c8c03b9 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
@@ -23,23 +23,22 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 
-import com.google.common.base.Function;
-
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
 import org.apache.isis.core.metamodel.consent.InteractionResult;
-import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
-import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.object.domainobject.DomainObjectAnnotationFacetFactoryTest.ObjectType;
 import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
 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.objectspecid.ObjectSpecIdFacet;
+import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
 import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
@@ -52,6 +51,8 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationContainer;
 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.
@@ -187,6 +188,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);
 
     /**
      *
@@ -351,4 +360,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 21dcac5..90021e6 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;
@@ -348,6 +352,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
 
         titleFacet = getFacet(TitleFacet.class);
         iconFacet = getFacet(IconFacet.class);
+        navigableParentFacet = getFacet(NavigableParentFacet.class);
         cssClassFacet = getFacet(CssClassFacet.class);
     }
 
@@ -379,6 +384,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 ahuber@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 b786d42..0552f7c 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
@@ -79,6 +79,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;
@@ -319,6 +321,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 dc767b3..81481c5 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
@@ -175,6 +175,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.huber@corax.at
+ * @author ahuber@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
['"commits@isis.apache.org" <co...@isis.apache.org>'].