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

[isis] 01/13: ISIS-1740 initial commit of prototype

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 e92b4db2846cce03959b7bf7e7f5c31c705e1476
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Oct 27 15:34:55 2017 +0200

    ISIS-1740 initial commit of prototype
---
 .../org/apache/isis/applib/annotation/Parent.java  |  34 ++++++
 .../metamodel/util/pchain/CachingParentChain.java  |  66 +++++++++++
 .../core/metamodel/util/pchain/ParentChain.java    |  99 ++++++++++++++++
 .../metamodel/util/pchain/SimpleParentChain.java   |  75 +++++++++++++
 .../isis/core/metamodel/util/pchain/SoftCache.java | 124 +++++++++++++++++++++
 .../model/models/whereami/WhereAmIModel.java       |  38 +++++++
 .../models/whereami/WhereAmIModelDefault.java      |  68 +++++++++++
 .../viewer/wicket/ui/pages/entity/EntityPage.css   |  29 +++++
 .../viewer/wicket/ui/pages/entity/EntityPage.html  |   7 +-
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |  53 ++++++++-
 10 files changed, 587 insertions(+), 6 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Parent.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Parent.java
new file mode 100644
index 0000000..6e4a7d8
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Parent.java
@@ -0,0 +1,34 @@
+/* 
+ * 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.applib.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tells the framework which method or field to use in order to construct a navigable chain of
+ * parent domain object instances. This annotation can only be used once per class declaration. 
+ * 
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface Parent {
+}
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/CachingParentChain.java
new file mode 100644
index 0000000..630e928
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
@@ -0,0 +1,66 @@
+/*
+ * 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.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+
+class CachingParentChain extends SimpleParentChain {
+
+	private final SoftCache<Class<?>, MethodHandle> cache = new SoftCache<>();
+
+	@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;
+					}
+				});
+		
+		if(mh==null)
+			return null;
+		
+		try {
+			return mh.invoke(node);
+		} catch (Throwable e) {
+			e.printStackTrace();
+			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);
+	}
+	
+}
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
new file mode 100644
index 0000000..31ae507
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
@@ -0,0 +1,99 @@
+/*
+ * 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.Method;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.isis.applib.annotation.Parent;
+import org.apache.isis.core.commons.reflection.Reflect;
+
+/**
+ * Represents a unidirectional 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/>
+ * 
+ * startNode --@Parent--&gt; node2 --@Parent--&gt; node3 ...
+ * 
+ * @author ahuber@apache.org
+ *
+ */
+public interface ParentChain {
+	
+	static ParentChain simple() {
+		return new SimpleParentChain();
+	}
+	
+	static ParentChain caching() {
+		return new CachingParentChain();
+	}
+	
+	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){
+		final Set<Object> chain = new LinkedHashSet<>();
+		
+		chain.add(startNode);
+		
+		Object next = startNode;
+		
+		while((next = parentOf(next))!=null) {
+			final boolean doContinue = chain.add(next);
+			if(!doContinue)
+				break;
+		}
+		
+		return chain.stream().skip(1);
+	}
+	
+	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/SimpleParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
new file mode 100644
index 0000000..b30e427
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
@@ -0,0 +1,75 @@
+/*
+ * 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
new file mode 100644
index 0000000..34c1ff6
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
@@ -0,0 +1,124 @@
+/*
+ * 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/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
new file mode 100644
index 0000000..7ecfba0
--- /dev/null
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
@@ -0,0 +1,38 @@
+/*
+ * 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.viewer.wicket.model.models.whereami;
+
+import java.util.stream.Stream;
+
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+
+public interface WhereAmIModel {
+
+	public static WhereAmIModel of(EntityModel endOfChain) {
+		return new WhereAmIModelDefault(endOfChain);
+	}
+
+	public boolean isShowWhereAmI();
+	
+	public Stream<EntityModel> streamParentChain();
+	
+	public EntityModel getEndOfChain();
+	
+}
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
new file mode 100644
index 0000000..b3f6679
--- /dev/null
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.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.viewer.wicket.model.models.whereami;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.apache.isis.core.metamodel.util.pchain.ParentChain;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+
+class WhereAmIModelDefault implements WhereAmIModel {
+
+	private final List<Object> chainOfParents = new ArrayList<>();
+	private final EntityModel endOfChain;
+	
+	public WhereAmIModelDefault(EntityModel endOfChain) {
+		this.endOfChain = endOfChain;
+		
+		final Object startPojo = endOfChain.getObject().getObject();
+
+		ParentChain.caching()
+		.streamReversedParentChainOf(startPojo)
+		.forEach(chainOfParents::add);
+	}
+	
+	@Override
+	public EntityModel getEndOfChain() {
+		return endOfChain;
+	}
+	
+	@Override
+	public boolean isShowWhereAmI() {
+		return !chainOfParents.isEmpty();
+	}
+
+	@Override
+	public Stream<EntityModel> streamParentChain() {
+		return chainOfParents.stream()
+		.map(this::toEntityModel);
+	}
+	
+	// -- HELPER
+
+	private EntityModel toEntityModel(Object domainObject) {
+		return new EntityModel(
+				endOfChain.getPersistenceSession()
+				.adapterFor(domainObject)	);
+	}
+	
+}
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.css b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.css
index eaeea17..b3618df 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.css
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.css
@@ -16,3 +16,32 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
+ 
+ /* === whereAmI feature === */
+
+.whereAmI {
+    color: #555;
+}
+.whereAmI img {
+    width: 16px !important;
+    height: 16px !important;
+}
+ul.whereAmI {
+    padding: 8px 12px;
+    list-style: none;
+    background-color: #eee;
+}
+
+/* Display list items side by side */
+ul.whereAmI li {
+    display: inline;
+}
+
+/* Add a slash symbol (/) before/behind each list item */
+ul.whereAmI li+li:before {
+    padding: 4px;
+    color: black;
+    content: "/\00a0";
+}
+
+/* -------------------------- */
\ No newline at end of file
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.html
index 1caad01..39aa437 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.html
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.html
@@ -26,8 +26,13 @@
         <wicket:extend>
             <div class="entityPage" wicket:id="entityPageContainer">
                 <div wicket:id="bookmarks"></div>
+                 <div wicket:id="whereAmI-container">
+	                <ul class="whereAmI">
+	    				<li wicket:id="whereAmI-items"></li>
+					</ul>
+                </div>
                 <div wicket:id="entity"></div>
             </div>
         </wicket:extend>
     </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
index f824e1d..e9e04dc 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
@@ -22,12 +22,16 @@ package org.apache.isis.viewer.wicket.ui.pages.entity;
 import org.apache.wicket.Application;
 import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.RepeatingView;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.CssResourceReference;
 import org.apache.wicket.util.string.Strings;
 
-import org.apache.isis.applib.layout.grid.Grid;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
 import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
@@ -39,7 +43,9 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.viewer.wicket.model.common.PageParametersUtils;
 import org.apache.isis.viewer.wicket.model.hints.UiHintContainer;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.whereami.WhereAmIModel;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModelProvider;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
@@ -51,8 +57,10 @@ import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
 @AuthorizeInstantiation("org.apache.isis.viewer.wicket.roles.USER")
 public class EntityPage extends PageAbstract {
 
-    private static final long serialVersionUID = 1L;
-    
+	private static final long serialVersionUID = 144368606134796079L;
+	private static final CssResourceReference WHERE_AM_I_CSS = 
+			new CssResourceReference(EntityPage.class, "EntityPage.css");
+
     private final EntityModel model;
     private final String titleString;
 
@@ -63,6 +71,12 @@ public class EntityPage extends PageAbstract {
     public EntityPage(final PageParameters pageParameters) {
         this(pageParameters, createEntityModel(pageParameters));
     }
+    
+    @Override
+    public void renderHead(IHeaderResponse response) {
+    	super.renderHead(response);
+    	response.render(CssHeaderItem.forReference(WHERE_AM_I_CSS));
+    }
 
     /**
      * Creates an EntityModel from the given page parameters.
@@ -159,7 +173,7 @@ public class EntityPage extends PageAbstract {
             // the facet should always exist, in fact
             // just enough to ask for the metadata.
             // This will cause the current ObjectSpec to be updated as a side effect.
-            final Grid unused = gridFacet.getGrid();
+            gridFacet.getGrid();
         }
 
         if(titleString == null) {
@@ -179,6 +193,8 @@ public class EntityPage extends PageAbstract {
 
         themeDiv.addOrReplace(entityPageContainer);
 
+        addWhereAmIIfShown(entityPageContainer, WhereAmIModel.of(model));
+        
         addChildComponents(entityPageContainer, model);
 
         // bookmarks and breadcrumbs
@@ -186,10 +202,37 @@ public class EntityPage extends PageAbstract {
         addBreadcrumbIfShown(model);
 
         addBookmarkedPages(entityPageContainer);
+        
+        
     }
 
     protected DeploymentCategory getDeploymentCategory() {
         return getIsisSessionFactory().getDeploymentCategory();
     }
-
+    
+    protected void addWhereAmIIfShown(
+    		WebMarkupContainer entityPageContainer, 
+    		WhereAmIModel whereAmIModel) 
+    {
+    	
+    	final WebMarkupContainer whereAmIContainer = 
+    			new WebMarkupContainer("whereAmI-container");
+    	entityPageContainer.addOrReplace(whereAmIContainer);
+    	
+    	if(!whereAmIModel.isShowWhereAmI()) {
+    		whereAmIContainer.setVisible(false);
+    		return;
+    	}
+    	
+    	final RepeatingView listItems = new RepeatingView("whereAmI-items");
+    	
+    	whereAmIModel.streamParentChain().forEach(entityModel->
+    		listItems.add(new EntityIconAndTitlePanel(listItems.newChildId(), entityModel))	
+		);
+    	
+    	listItems.add(new Label(listItems.newChildId(), whereAmIModel.getEndOfChain().getTitle()));
+    	
+    	whereAmIContainer.addOrReplace(listItems);
+    	
+	}
 }

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