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 <ahu...@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--> node2 --@Parent--> node3 ... + * + * @author ahu...@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 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/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 danhayw...@apache.org.