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:16 UTC

[isis] branch master updated (c05e5ca -> fb552d3)

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

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


    from c05e5ca  Merge branch 'ISIS-1852_parallel_stream_patch'
     new e92b4db  ISIS-1740 initial commit of prototype
     new efdd481  ISIS-1740 refactored method names, added javadoc
     new 266a4e9  ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate with Isis' meta-model
     new 6fc4c5b  ISIS-1740 NavigableParentService never used, so removed
     new 08ce1a6  ISIS-1817 + ISIS-1818 allow configuration of where-am-i feature
     new 1155e89  ISIS-1817 + ISIS-1818 added missing assignment + clarify comments
     new 0645ecd  ISIS-1816 refactoring Annotations + implement hierarchy search
     new f338b8b  ISIS-1816 impl. navigable parent resolving behavior according to spec
     new 2fc93a2  ISIS-1740 Refactoring and consolidating invocation exception handling + introducing MethodHandles to speed up reflective invocation
     new 4126069  ISIS-1816 Refactoring names + simplify
     new cc2f5d0  ISIS-1740: fixes merge issues (NullSafe moved to applib and renamed)
     new 6ee4202  Merge branch 'ISIS-1740-where-am-i'
     add 3e3ad2b  ISIS-1852: reworks unit test, again
     new fb552d3  Merge branch 'ISIS-1852_parallel_stream_patch'

The 13 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../org/apache/isis/applib/annotation/Parent.java  |  34 ++++
 .../isis/core/commons/lang/MethodExtensions.java   |  14 +-
 .../core/commons/lang/ThrowableExtensions.java     |  62 +++++--
 .../isis/core/commons/reflection/Reflect.java      | 202 +++++++++++++++++++++
 .../isis/core/metamodel/facets/Annotations.java    | 199 +++++++++++++++-----
 ...ctionInvocationFacetForDomainEventAbstract.java |  54 +++---
 .../navparent/NavigableParentFacet.java}           |  25 ++-
 .../navparent/NavigableParentFacetAbstract.java}   |   8 +-
 .../NavigableParentAnnotationFacetFactory.java     | 168 +++++++++++++++++
 .../method/NavigableParentFacetMethod.java}        |  44 +++--
 .../method/NavigableParentFacetMethodFactory.java} |  28 ++-
 .../core/metamodel/spec/ObjectSpecification.java   |  12 ++
 .../specimpl/ObjectSpecificationAbstract.java      |  12 ++
 .../core/metamodel/util/pchain/ParentChain.java    |  84 +++++++++
 .../metamodel/util/pchain/ParentChainDefault.java  |  49 +++++
 .../dflt/ProgrammingModelFacetsJava5.java          |   4 +
 .../NavigableParentFacetMethodFactoryTest.java}    |  37 ++--
 .../NavigableParentFacetMethodTest.java}           |  39 ++--
 .../NavigableParentAnnotationFacetFactoryTest.java | 120 ++++++++++++
 .../annotation/NavigableParentTestSamples.java     |  49 +++++
 .../testspec/ObjectSpecificationStub.java          |   5 +
 .../runtime/services/ServiceInstantiatorTest.java  |  37 ++--
 .../model/models/whereami/WhereAmIModel.java       |  65 +++++++
 .../models/whereami/WhereAmIModelDefault.java      | 116 ++++++++++++
 .../viewer/wicket/ui/pages/entity/EntityPage.css   |  29 +++
 .../viewer/wicket/ui/pages/entity/EntityPage.html  |   7 +-
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |  53 +++++-
 27 files changed, 1365 insertions(+), 191 deletions(-)
 create mode 100644 core/applib/src/main/java/org/apache/isis/applib/annotation/Parent.java
 create mode 100644 core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
 copy core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/{actions/defaults/ActionDefaultsFacet.java => object/navparent/NavigableParentFacet.java} (60%)
 copy core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/{collections/modify/CollectionAddToFacetAbstract.java => object/navparent/NavigableParentFacetAbstract.java} (80%)
 create mode 100644 core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
 copy core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/{domainobject/autocomplete/AutoCompleteFacetForAutoCompleteAnnotation.java => navparent/method/NavigableParentFacetMethod.java} (54%)
 copy core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/{icon/method/IconFacetMethodFactory.java => navparent/method/NavigableParentFacetMethodFactory.java} (65%)
 create mode 100644 core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
 create mode 100644 core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
 copy core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/{ident/icon/IconFacetMethodFactoryTest.java => navparent/NavigableParentFacetMethodFactoryTest.java} (67%)
 copy core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/{ident/icon/IconFacetMethodTest.java => navparent/NavigableParentFacetMethodTest.java} (69%)
 create mode 100644 core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
 create mode 100644 core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
 create mode 100644 core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
 create mode 100644 core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java

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

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

Posted by da...@apache.org.
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.

[isis] 04/13: ISIS-1740 NavigableParentService never used, so removed

Posted by da...@apache.org.
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 6fc4c5b70019f223e6020feb4c5d8e50cd9ae380
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jan 12 16:42:03 2018 +0100

    ISIS-1740 NavigableParentService never used, so removed
---
 .../services/navparent/NavigableParentService.java   | 20 --------------------
 1 file changed, 20 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
deleted file mode 100644
index 129122a..0000000
--- a/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
+++ /dev/null
@@ -1,20 +0,0 @@
-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);
-	
-}

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

[isis] 08/13: ISIS-1816 impl. navigable parent resolving behavior according to spec

Posted by da...@apache.org.
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 f338b8b0a907f3581041644abe2f093542b09c88
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 15 11:16:31 2018 +0100

    ISIS-1816 impl. navigable parent resolving behavior according to spec
---
 .../NavigableParentAnnotationFacetFactory.java     | 77 +++++++++-------------
 .../method/NavigableParentFacetMethodFactory.java  |  3 +
 .../dflt/ProgrammingModelFacetsJava5.java          |  2 +-
 3 files changed, 36 insertions(+), 46 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
index f3b2102..7921bda 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -26,6 +26,7 @@ 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.lang.NullSafe;
 import org.apache.isis.core.commons.reflection.Reflect;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
@@ -33,9 +34,7 @@ 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;
@@ -44,6 +43,8 @@ import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVis
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
 
 /**
+ * For detailed behavioral specification see 
+ * <a href="https://issues.apache.org/jira/browse/ISIS-1816">ISIS-1816</a>.
  * 
  * @author ahuber@apache.org
  * @since 2.0.0
@@ -51,9 +52,6 @@ import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
  */
 public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
 
-    private static final String NAVIGABLE_PARENT_METHOD_NAME = "parent";
-
-
     public NavigableParentAnnotationFacetFactory() {
         super(FeatureType.OBJECTS_ONLY);
     }
@@ -63,10 +61,18 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
         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;
+        // Starting from the current domain-object class, we search down the object 
+        // inheritance hierarchy (super class, super super class, ...), until we find 
+        // the first class that has a @Parent annotation. That's the one we use to 
+        // resolve the current domain-object's navigable parent. 
+        
+        final List<Annotations.Evaluator<Parent>> evaluators = 
+        		Annotations.findFirstInHierarchyHaving(cls, Parent.class);
+        
+        if (NullSafe.isEmpty(evaluators)) {
+            return; // no parent resolvable
         } else if (evaluators.size()>1) {
+        	// code should not be reached, since case should be handled by meta-data validation
         	throw new RuntimeException("unable to determine navigable parent due to ambiguity");
         }
         
@@ -84,10 +90,10 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
         	try {
 				method = Reflect.getGetter(cls, field.getName());
 			} catch (IntrospectionException e) {
-				return;
+				return; // no parent resolvable
 			}
         } else {
-        	return;
+        	return; // no parent resolvable
         }
         
         try {
@@ -99,59 +105,40 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
 
 
     /**
-     * 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>
+     * For detailed behavioral specification see 
+     * <a href="https://issues.apache.org/jira/browse/ISIS-1816">ISIS-1816</a>.
      */
     @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<Annotations.Evaluator<Parent>> evaluators = 
+                		Annotations.findFirstInHierarchyHaving(cls, Parent.class);
                 
-                final List<Method> methods = methodsWithParentAnnotation(cls);
-                final List<Method> superClassMethods = methodsWithParentAnnotation(supClass);
-                if (methods.size() > superClassMethods.size()) {
-                    validationFailures.add(
+                if (NullSafe.isEmpty(evaluators)) {
+                	return true; // no conflict
+                } else if (evaluators.size()>1) {
+                	
+                	validationFailures.add(
                             "%s: conflict for determining a strategy for retrieval of (navigable) parent for class, "
-                            + "contains a method '%s' and an annotation '@%s'",
+                            + "contains multiple annotations '@%s', while at most one is allowed.",
                             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);
+                
+                return true; // no conflict
+                
             }
 
         }));
     }
 
-
+    // -- ADAPTER INJECTION
+    
     @Override
     public void setServicesInjector(final ServicesInjector servicesInjector) {
         super.setServicesInjector(servicesInjector);
@@ -159,5 +146,5 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
     }
 
     PersistenceSessionServiceInternal adapterManager;
-
+    
 }
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
index ad0db2a..471dd5e 100644
--- 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
@@ -33,7 +33,10 @@ import org.apache.isis.core.metamodel.facets.MethodPrefixBasedFacetFactoryAbstra
  * @author ahuber@apache.org
  * @since 2.0.0
  *
+ * @deprecated according to proposed spec, <a href="https://issues.apache.org/jira/browse/ISIS-1816">ISIS-1816</a>
+ * let's not introduce new 'keywords'. 
  */
+@Deprecated
 public class NavigableParentFacetMethodFactory extends MethodPrefixBasedFacetFactoryAbstract {
 
     private static final String NAVIGABLE_PARENT_PREFIX = "parent";
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 293dafb..e96588f 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
@@ -328,7 +328,7 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(new TitleFacetViaMethodsFactory());
         addFactory(new IconFacetMethodFactory());
         addFactory(new NavigableParentAnnotationFacetFactory());
-        addFactory(new NavigableParentFacetMethodFactory());
+        //addFactory(new NavigableParentFacetMethodFactory()); //TODO [ahuber] remove once we agreed on deprecation
         addFactory(new CssClassFacetMethodFactory());
 
 

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

[isis] 09/13: ISIS-1740 Refactoring and consolidating invocation exception handling + introducing MethodHandles to speed up reflective invocation

Posted by da...@apache.org.
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 2fc93a23eff85b8f5530bbf6dee0e0f7d07a19f6
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 15 14:06:44 2018 +0100

    ISIS-1740 Refactoring and consolidating invocation exception handling
    + introducing MethodHandles to speed up reflective invocation
---
 .../isis/core/commons/lang/MethodExtensions.java   |  14 +--
 .../core/commons/lang/ThrowableExtensions.java     |  62 ++++++++---
 .../isis/core/commons/reflection/Reflect.java      |  44 ++++++--
 .../isis/core/metamodel/facets/Annotations.java    | 114 +++++++++++++++------
 ...ctionInvocationFacetForDomainEventAbstract.java |  54 +++++-----
 5 files changed, 196 insertions(+), 92 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/MethodExtensions.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/MethodExtensions.java
index fb95bf9..7215a47 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/MethodExtensions.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/MethodExtensions.java
@@ -19,12 +19,9 @@
 
 package org.apache.isis.core.commons.lang;
 
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
-import org.apache.isis.core.metamodel.exceptions.MetaModelException;
-
 public class MethodExtensions {
 
     private MethodExtensions() {
@@ -45,14 +42,9 @@ public class MethodExtensions {
         try {
             Object[] defaultAnyPrimitive = defaultAnyPrimitive(method.getParameterTypes(), arguments);
             return method.invoke(object, defaultAnyPrimitive);
-        } catch (final IllegalArgumentException e) {
-            throw e;
-        } catch (final InvocationTargetException e) {
-            ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method);
-            return null;
-        } catch (final IllegalAccessException e) {
-            throw new MetaModelException("illegal access of " + method, e);
-        }
+        } catch (Exception e) {
+        	return ThrowableExtensions.handleInvocationException(e, method.getName());
+		} 
     }
 
     private static Object[] defaultAnyPrimitive(Class<?>[] parameterTypes, Object[] arguments) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/ThrowableExtensions.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/ThrowableExtensions.java
index 70cf875..855ba1d 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/ThrowableExtensions.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/lang/ThrowableExtensions.java
@@ -19,26 +19,64 @@
 
 package org.apache.isis.core.commons.lang;
 
+import java.lang.invoke.WrongMethodTypeException;
 import java.lang.reflect.InvocationTargetException;
+import java.util.function.Consumer;
 
 import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.core.commons.exceptions.IsisApplicationException;
 import org.apache.isis.core.metamodel.exceptions.MetaModelException;
+import org.apache.isis.core.metamodel.specloader.ReflectiveActionException;
 
 public final class ThrowableExtensions {
 
-    public static void throwWithinIsisException(final InvocationTargetException e, final String error) {
-        final Throwable targetException = e.getTargetException();
-        if (targetException instanceof RecoverableException) {
-            // an application exception from the domain code is re-thrown as an
-            // IsisException with same semantics
-            // TODO: should probably be using ApplicationException here
-            throw new IsisApplicationException(error, targetException);
+	public static Object handleInvocationException(
+    		final Throwable e, 
+    		final String memberName) {
+		return handleInvocationException(e, memberName, null);
+	}
+	
+    public static Object handleInvocationException(
+    		final Throwable e, 
+    		final String memberName, 
+    		final Consumer<RecoverableException> recovery) {
+    	
+    	if(e instanceof InvocationTargetException) {
+			return handleInvocationException(((InvocationTargetException) e).getTargetException(), memberName, recovery);
+		}
+    	if(e instanceof WrongMethodTypeException) {
+			throw new MetaModelException("Wrong method type access of " + memberName, e);
+		}
+    	if(e instanceof IllegalAccessException) {
+			throw new ReflectiveActionException("Illegal access of " + memberName, e);
+	    }
+    	if(e instanceof IllegalStateException) {
+            throw new ReflectiveActionException( String.format(
+                    "IllegalStateException thrown while invoking %s %s",
+                    memberName, e.getMessage()), e);
         }
-        if (targetException instanceof RuntimeException) {
-            throw (RuntimeException) targetException;
-        } else {
-            throw new MetaModelException(targetException);
+		if(e instanceof RecoverableException) {
+			return handleRecoverableException((RecoverableException)e, memberName, recovery);
+	    }
+		if (e instanceof RuntimeException) {
+            throw (RuntimeException) e;
         }
-    }
+        throw new MetaModelException("Exception invoking " + memberName, e);
+	}
+
+
+	private static Object handleRecoverableException(
+			final RecoverableException e, 
+    		final String memberName, 
+    		final Consumer<RecoverableException> recovery) {
+		
+		if(recovery!=null)
+			recovery.accept(e); 
+		
+		// an application exception from the domain code is re-thrown as an
+        // IsisException with same semantics
+        // TODO: should probably be using ApplicationException here
+        throw new IsisApplicationException("Exception invoking " + memberName, e);
+	}
+    
 }
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 59bc1c8..44e9431 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
@@ -22,6 +22,8 @@ import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -46,9 +48,9 @@ public class Reflect {
 
 	public static Object[] emptyObjects = {};
 	public static Class<?>[] emptyClasses = {};
-	
+
 	// -- CLASS REFLECTION
-	
+
 	/**
 	 * Returns declared methods of this class/interface and all super classes/interfaces.
 	 * @param type
@@ -76,7 +78,7 @@ public class Reflect {
 		visitSuperclassesOf(type,c->Stream.of(c.getDeclaredFields()).forEach(fields::add));
 		return fields;
 	}
-	
+
 	public static void visitSuperclassesOf(final Class<?> clazz, final Consumer<Class<?>> visitor){
 		final Class<?> superclass = clazz.getSuperclass();
 		if(superclass!=null){
@@ -92,7 +94,7 @@ public class Reflect {
 		for(Class<?> interf : clazz.getInterfaces())
 			visitor.accept(interf);
 	}
-	
+
 	public static Method getGetter(Class<?> cls, String propertyName) throws IntrospectionException {
 		final BeanInfo beanInfo = Introspector.getBeanInfo(cls);
 		for(PropertyDescriptor pd:beanInfo.getPropertyDescriptors()){
@@ -102,14 +104,35 @@ public class Reflect {
 		}
 		return null;	
 	}
-	
+
 	public static Method getGetter(Object bean, String propertyName) throws IntrospectionException {
 		if(bean==null)
 			return null;
 		return getGetter(bean, propertyName);	
 	}
-	
-	
+
+	// -- METHOD HANDLES
+
+	public static MethodHandle handleOf(Method method) throws IllegalAccessException {
+		if(!method.isAccessible()) {
+			method.setAccessible(true);
+			MethodHandle mh = MethodHandles.publicLookup().unreflect(method);
+			method.setAccessible(false);
+			return mh;	
+		}
+		return MethodHandles.publicLookup().unreflect(method);
+	}
+
+	public static MethodHandle handleOf(Field field) throws IllegalAccessException {
+		if(!field.isAccessible()) {
+			field.setAccessible(true);
+			MethodHandle mh = MethodHandles.lookup().unreflectGetter(field);
+			field.setAccessible(false);
+			return mh;	
+		}
+		return MethodHandles.lookup().unreflectGetter(field);
+	}
+
 	// -- PRIMITIVE TYPES
 
 	private static final Set<Class<?>> primitives = new HashSet<>(Arrays.asList(
@@ -135,7 +158,7 @@ public class Reflect {
 			Short.class
 			//Void.class //separated out into its own predicate: isVoid(...)
 			));
-	
+
 	// -- TYPE PREDICATES
 
 	public static boolean isVoid(Class<?> c) {
@@ -171,4 +194,9 @@ public class Reflect {
 		return isVoid(m.getReturnType());
 	}
 
+
+
+
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
index 3d24c77..381df00 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
@@ -19,13 +19,15 @@
 
 package org.apache.isis.core.metamodel.facets;
 
+import java.beans.IntrospectionException;
 import java.lang.annotation.Annotation;
+import java.lang.invoke.MethodHandle;
 import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -40,6 +42,7 @@ import org.apache.isis.applib.annotation.Property;
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.applib.annotation.Title;
 import org.apache.isis.core.commons.lang.ThrowableExtensions;
+import org.apache.isis.core.commons.reflection.Reflect;
 import org.apache.isis.core.metamodel.exceptions.MetaModelException;
 import org.apache.isis.core.metamodel.methodutils.MethodScope;
 
@@ -381,11 +384,11 @@ public final class Annotations  {
      * @param cls
      * @param annotationClass
      * @return list of {@link Evaluator} that wraps each annotated member found on the class where 
-     * the search stopped, null otherwise
+     * the search stopped, or an empty list if no such {@code annotationClass} annotation found.
      * 
      * @since 2.0.0
      */
-    public static <T extends Annotation> List<Evaluator<T>> findFirstInHierarchyHaving(
+    public static <T extends Annotation> List<Evaluator<T>> firstEvaluatorsInHierarchyHaving(
             final Class<?> cls,
             final Class<T> annotationClass) {
     	
@@ -411,8 +414,8 @@ public final class Annotations  {
     	if(!filter.test(cls))
     		return; // stop visitation
     	
-    	collectMethodEvaluators(cls, annotationClass, visitor);
-    	collectFieldEvaluators(cls, annotationClass, visitor);
+    	visitMethodEvaluators(cls, annotationClass, visitor);
+    	visitFieldEvaluators(cls, annotationClass, visitor);
         
         // search super-classes
         final Class<?> superclass = cls.getSuperclass();
@@ -423,39 +426,39 @@ public final class Annotations  {
     }
     
     @SuppressWarnings({ "rawtypes", "unchecked" })
-	private static <T extends Annotation> void collectMethodEvaluators(
+	private static <T extends Annotation> void visitMethodEvaluators(
             final Class<?> cls,
             final Class<T> annotationClass,
-            final Consumer<Evaluator<T>> action) {
+            final Consumer<Evaluator<T>> visitor) {
     	
     	for (Method method : cls.getDeclaredMethods()) {
             if(MethodScope.OBJECT.matchesScopeOf(method) &&
                     method.getParameterTypes().length == 0) {
                 final Annotation annotation = method.getAnnotation(annotationClass);
                 if(annotation != null) {
-                	action.accept(new MethodEvaluator(method, annotation));
+                	visitor.accept(new MethodEvaluator(method, annotation));
                 }
             }
         }
     }
     
     @SuppressWarnings({ "rawtypes", "unchecked" })
-	private static <T extends Annotation> void collectFieldEvaluators(
+	private static <T extends Annotation> void visitFieldEvaluators(
             final Class<?> cls,
             final Class<T> annotationClass,
-            final Consumer<Evaluator<T>> action) {
+            final Consumer<Evaluator<T>> visitor) {
     	
     	for (final Field field: cls.getDeclaredFields()) {
             final Annotation annotation = field.getAnnotation(annotationClass);
             if(annotation != null) {
-            	action.accept(new FieldEvaluator(field, annotation));
+            	visitor.accept(new FieldEvaluator(field, annotation));
             }
         }
     }
 
     public static abstract class Evaluator<T extends Annotation> {
-
         private final T annotation;
+    	private MethodHandle mh;  
 
         protected Evaluator(final T annotation) {
             this.annotation = annotation;
@@ -465,7 +468,25 @@ public final class Annotations  {
             return annotation;
         }
 
-        public abstract Object value(final Object obj) ;
+        protected abstract MethodHandle createMethodHandle() throws IllegalAccessException;
+        protected abstract String name();
+        
+        public Object value(final Object obj) {
+        	if(mh==null) {
+        		try {
+					mh = createMethodHandle();
+				} catch (IllegalAccessException e) {
+					throw new MetaModelException("illegal access of " + name(), e);
+				}
+        	}
+        	
+        	try {
+				return mh.invoke(obj);
+			} catch (Throwable e) {
+				return ThrowableExtensions.handleInvocationException(e, name());
+			}
+ 
+        }
     }
 
     public static class MethodEvaluator<T extends Annotation> extends Evaluator<T> {
@@ -475,21 +496,31 @@ public final class Annotations  {
             super(annotation);
             this.method = method;
         }
-
-        public Object value(final Object obj)  {
-            try {
-                return method.invoke(obj);
-            } catch (final InvocationTargetException e) {
-                ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method);
-                return null;
-            } catch (final IllegalAccessException e) {
-                throw new MetaModelException("illegal access of " + method, e);
-            }
+        
+        @Override
+        protected String name() {
+        	return method.getName();
         }
 
+//        public Object value(final Object obj)  {
+//            try {
+//                return method.invoke(obj);
+//            } catch (final InvocationTargetException e) {
+//                ThrowableExtensions.throwWithinIsisException(e, "Exception executing " + method);
+//                return null;
+//            } catch (final IllegalAccessException e) {
+//                throw new MetaModelException("illegal access of " + method, e);
+//            }
+//        }
+
         public Method getMethod() {
             return method;
         }
+
+		@Override
+		protected MethodHandle createMethodHandle() throws IllegalAccessException {
+			return Reflect.handleOf(method);
+		}
     }
 
     public static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
@@ -499,19 +530,40 @@ public final class Annotations  {
             super(annotation);
             this.field = field;
         }
-
-        public Object value(final Object obj)  {
-            try {
-                field.setAccessible(true);
-                return field.get(obj);
-            } catch (final IllegalAccessException e) {
-                throw new MetaModelException("illegal access of " + field, e);
-            }
+        
+        @Override
+        protected String name() {
+        	return field.getName();
         }
 
+        @Override
+		protected MethodHandle createMethodHandle() throws IllegalAccessException {
+			return Reflect.handleOf(field);
+		}
+        
+//        public Object value(final Object obj)  {
+//            try {
+//                field.setAccessible(true);
+//                return field.get(obj);
+//            } catch (final IllegalAccessException e) {
+//                throw new MetaModelException("illegal access of " + field, e);
+//            }
+//        }
+
         public Field getField() {
             return field;
         }
+        
+        public Optional<Method> getGetter(Class<?> originatingClass) {
+			try {
+        		return Optional.ofNullable(
+        				Reflect.getGetter(originatingClass, field.getName())	);
+			} catch (IntrospectionException e) {
+				e.printStackTrace();
+			}
+        	return Optional.empty();
+        }
+        
     }
 
     private static List<Class<?>> fieldAnnotationClasses = Collections.unmodifiableList(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
index f6683fd..9053e47 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/action/invocation/ActionInvocationFacetForDomainEventAbstract.java
@@ -27,6 +27,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Callable;
+import java.util.function.Consumer;
 
 import org.apache.isis.applib.NonRecoverableException;
 import org.apache.isis.applib.RecoverableException;
@@ -273,36 +274,29 @@ public abstract class ActionInvocationFacetForDomainEventAbstract
 
                         return ObjectAdapter.Util.unwrap(resultAdapterPossiblyCloned);
 
-                    } catch (IllegalAccessException ex) {
-                        throw new ReflectiveActionException("Illegal access of " + method, ex);
-                    } catch (InvocationTargetException ex) {
-
-                        final Throwable targetException = ex.getTargetException();
-                        if (targetException instanceof IllegalStateException) {
-                            throw new ReflectiveActionException( String.format(
-                                    "IllegalStateException thrown while executing %s %s",
-                                    method, targetException.getMessage()), targetException);
-                        }
-
-                        if(targetException instanceof RecoverableException) {
-                            if (!getTransactionState().canCommit()) {
-                                // something severe has happened to the underlying transaction;
-                                // so escalate this exception to be non-recoverable
-                                final Throwable targetExceptionCause = targetException.getCause();
-                                Throwable nonRecoverableCause = targetExceptionCause != null
-                                        ? targetExceptionCause
-                                        : targetException;
-
-                                // trim to first 300 chars
-                                final String message = trim(nonRecoverableCause.getMessage(), 300);
-
-                                throw new NonRecoverableException(message, nonRecoverableCause);
-                            }
-                        }
-
-                        ThrowableExtensions.throwWithinIsisException(ex, "Exception executing " + method);
-                        return null; // never executed, previous line throws
-                    }
+                    } catch (Exception e) {
+                    	
+                    	final Consumer<RecoverableException> recovery = recoverableException->{
+                    		
+                    		if (!getTransactionState().canCommit()) {
+		                        // something severe has happened to the underlying transaction;
+		                        // so escalate this exception to be non-recoverable
+		                        final Throwable recoverableExceptionCause = recoverableException.getCause();
+		                        Throwable nonRecoverableCause = recoverableExceptionCause != null
+		                                ? recoverableExceptionCause
+		                                : recoverableException;
+		
+		                        // trim to first 300 chars
+		                        final String message = trim(nonRecoverableCause.getMessage(), 300);
+		
+		                        throw new NonRecoverableException(message, nonRecoverableCause);
+                    		}
+                        };
+                    	
+                    	return ThrowableExtensions.handleInvocationException(e, method.getName(), recovery);
+					}
+                    
+                    
                 }
             };
 

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

[isis] 10/13: ISIS-1816 Refactoring names + simplify

Posted by da...@apache.org.
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 412606959ec63729a5d6451a519d86a3ad2708c7
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 15 14:08:35 2018 +0100

    ISIS-1816 Refactoring names + simplify
---
 .../NavigableParentAnnotationFacetFactory.java     | 46 +++++++++++++++-------
 .../method/NavigableParentFacetMethod.java         | 18 +--------
 2 files changed, 34 insertions(+), 30 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
index 7921bda..585228a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -19,15 +19,12 @@
 
 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.lang.NullSafe;
-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;
@@ -67,7 +64,7 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
         // resolve the current domain-object's navigable parent. 
         
         final List<Annotations.Evaluator<Parent>> evaluators = 
-        		Annotations.findFirstInHierarchyHaving(cls, Parent.class);
+        		Annotations.firstEvaluatorsInHierarchyHaving(cls, Parent.class);
         
         if (NullSafe.isEmpty(evaluators)) {
             return; // no parent resolvable
@@ -85,13 +82,11 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
         	// 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; // no parent resolvable
-			}
+        	// we have a @Parent annotated field (useful if one uses lombok's @Getter on a field)
+        	method = ((Annotations.FieldEvaluator<Parent>) parentEvaluator).getGetter(cls).orElse(null);
+        	if(method==null)
+        		return; // code should not be reached, since case should be handled by meta-data validation 
+        	
         } else {
         	return; // no parent resolvable
         }
@@ -117,10 +112,10 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
                 final Class<?> cls = objectSpec.getCorrespondingClass();
                 
                 final List<Annotations.Evaluator<Parent>> evaluators = 
-                		Annotations.findFirstInHierarchyHaving(cls, Parent.class);
+                		Annotations.firstEvaluatorsInHierarchyHaving(cls, Parent.class);
                 
                 if (NullSafe.isEmpty(evaluators)) {
-                	return true; // no conflict
+                	return true; // no conflict, continue validation processing
                 } else if (evaluators.size()>1) {
                 	
                 	validationFailures.add(
@@ -128,9 +123,32 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
                             + "contains multiple annotations '@%s', while at most one is allowed.",
                             objectSpec.getIdentifier().getClassName(),
                             Parent.class.getName());
+                	
+                	return true; // continue validation processing
+                } 
+                
+                final Annotations.Evaluator<Parent> parentEvaluator = evaluators.get(0);
+                
+                if(parentEvaluator instanceof Annotations.FieldEvaluator) {
+                	// we have a @Parent annotated field (useful if one uses lombok's @Getter on a field)
+                	
+                	final Annotations.FieldEvaluator<Parent> fieldEvaluator = 
+                			(Annotations.FieldEvaluator<Parent>) parentEvaluator;
+                	
+                	if(!fieldEvaluator.getGetter(cls).isPresent()) {
+                		
+	            		validationFailures.add(
+	                            "%s: unable to determine a strategy for retrieval of (navigable) parent for class, "
+	                            + "field '%s' annotated with '@%s' does not provide a getter.",
+	                            objectSpec.getIdentifier().getClassName(),
+	                            fieldEvaluator.getField().getName(),
+	                            Parent.class.getName());
+                	}
+                	
                 }
                 
-                return true; // no conflict
+                
+                return true; //continue validation processing
                 
             }
 
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
index 167a048..f74b8df 100644
--- 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
@@ -20,9 +20,9 @@
 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.commons.reflection.Reflect;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacetAbstract;
 
@@ -38,7 +38,7 @@ public class NavigableParentFacetMethod extends NavigableParentFacetAbstract {
 	
 	public NavigableParentFacetMethod(final Method method, final FacetHolder holder) throws IllegalAccessException {
 		super(holder);
-		this.methodHandle = handleOf(method);
+		this.methodHandle = Reflect.handleOf(method);
 	}
 
 	@Override
@@ -50,19 +50,5 @@ public class NavigableParentFacetMethod extends NavigableParentFacetAbstract {
 		}
 	}
 	
-	// -- 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);
-
-	}
 
 }

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

[isis] 03/13: ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate with Isis' meta-model

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 266a4e9964e5483b53456895641f49c1a580fcab
Author: Andi Huber <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      | 174 +++++++++++++++++++++
 .../isis/core/metamodel/facets/Annotations.java    |   2 +-
 .../object/navparent/NavigableParentFacet.java     |  44 ++++++
 .../navparent/NavigableParentFacetAbstract.java    |  36 +++++
 .../NavigableParentAnnotationFacetFactory.java     | 163 +++++++++++++++++++
 .../method/NavigableParentFacetMethod.java         |  68 ++++++++
 .../method/NavigableParentFacetMethodFactory.java  |  64 ++++++++
 .../core/metamodel/spec/ObjectSpecification.java   |  12 ++
 .../specimpl/ObjectSpecificationAbstract.java      |  12 ++
 .../core/metamodel/util/pchain/ParentChain.java    |  63 +++-----
 ...ingParentChain.java => ParentChainDefault.java} |  43 ++---
 .../metamodel/util/pchain/SimpleParentChain.java   |  75 ---------
 .../isis/core/metamodel/util/pchain/SoftCache.java | 124 ---------------
 .../dflt/ProgrammingModelFacetsJava5.java          |   4 +
 .../NavigableParentFacetMethodFactoryTest.java     |  70 +++++++++
 .../navparent/NavigableParentFacetMethodTest.java  |  83 ++++++++++
 .../NavigableParentAnnotationFacetFactoryTest.java | 120 ++++++++++++++
 .../annotation/NavigableParentTestSamples.java     |  49 ++++++
 .../testspec/ObjectSpecificationStub.java          |   5 +
 .../model/models/whereami/WhereAmIModel.java       |   2 +-
 .../models/whereami/WhereAmIModelDefault.java      |  18 ++-
 22 files changed, 968 insertions(+), 283 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
new file mode 100644
index 0000000..129122a
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
@@ -0,0 +1,20 @@
+package org.apache.isis.applib.services.navparent;
+
+import org.apache.isis.applib.annotation.Programmatic;
+
+/**
+ * 
+ * @author 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
new file mode 100644
index 0000000..59bc1c8
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.core.commons.reflection;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+/**
+ * 
+ * Provides shortcuts for common java.lang.reflect idioms.
+ * 
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
+public class Reflect {
+
+	public static Object[] emptyObjects = {};
+	public static Class<?>[] emptyClasses = {};
+	
+	// -- CLASS REFLECTION
+	
+	/**
+	 * Returns declared methods of this class/interface and all super classes/interfaces.
+	 * @param type
+	 * @return
+	 */
+	public static List<Method> getAllDeclaredMethods(Class<?> type) {
+		final List<Method> methods = new ArrayList<>();
+
+		Stream.of(type.getDeclaredMethods()).forEach(methods::add);
+		visitInterfaces(type,c->Stream.of(c.getDeclaredMethods()).forEach(methods::add));
+		visitSuperclassesOf(type,c->Stream.of(c.getDeclaredMethods()).forEach(methods::add));
+		return methods;
+	}
+
+	/**
+	 * Returns declared fields of this class/interface and all super classes/interfaces.
+	 * @param type
+	 * @return
+	 */
+	public static List<Field> getAllDeclaredFields(Class<?> type) {
+		final List<Field> fields = new ArrayList<>();
+
+		Stream.of(type.getDeclaredFields()).forEach(fields::add);
+		visitInterfaces(type,c->Stream.of(c.getDeclaredFields()).forEach(fields::add));
+		visitSuperclassesOf(type,c->Stream.of(c.getDeclaredFields()).forEach(fields::add));
+		return fields;
+	}
+	
+	public static void visitSuperclassesOf(final Class<?> clazz, final Consumer<Class<?>> visitor){
+		final Class<?> superclass = clazz.getSuperclass();
+		if(superclass!=null){
+			visitor.accept(superclass);
+			visitSuperclassesOf(superclass, visitor);
+		}
+	}
+
+	public static void visitInterfaces(final Class<?> clazz, final Consumer<Class<?>> visitor){
+		if(clazz.isInterface())
+			visitor.accept(clazz);
+
+		for(Class<?> interf : clazz.getInterfaces())
+			visitor.accept(interf);
+	}
+	
+	public static Method getGetter(Class<?> cls, String propertyName) throws IntrospectionException {
+		final BeanInfo beanInfo = Introspector.getBeanInfo(cls);
+		for(PropertyDescriptor pd:beanInfo.getPropertyDescriptors()){
+			if(!pd.getName().equals(propertyName))
+				continue;
+			return pd.getReadMethod();
+		}
+		return null;	
+	}
+	
+	public static Method getGetter(Object bean, String propertyName) throws IntrospectionException {
+		if(bean==null)
+			return null;
+		return getGetter(bean, propertyName);	
+	}
+	
+	
+	// -- PRIMITIVE TYPES
+
+	private static final Set<Class<?>> primitives = new HashSet<>(Arrays.asList(
+			boolean.class,
+			byte.class,
+			char.class,
+			double.class, 
+			float.class,
+			int.class,
+			long.class,
+			short.class
+			//void.class //separated out into its own predicate: isVoid(...)
+			));
+
+	private static final Set<Class<?>> primitiveWrappers = new HashSet<>(Arrays.asList(
+			Boolean.class,
+			Byte.class,
+			Character.class,
+			Double.class,
+			Float.class,
+			Integer.class,
+			Long.class,
+			Short.class
+			//Void.class //separated out into its own predicate: isVoid(...)
+			));
+	
+	// -- TYPE PREDICATES
+
+	public static boolean isVoid(Class<?> c) {
+		Objects.requireNonNull(c);
+		return c == void.class || c == Void.class;
+	}
+
+	public static boolean isPrimitive(Class<?> c) {
+		Objects.requireNonNull(c);
+		return primitives.contains(c);
+	}
+
+	public static boolean isPrimitiveWrapper(Class<?> c) {
+		Objects.requireNonNull(c);
+		return primitiveWrappers.contains(c);
+	}
+
+
+	// -- METHOD PREDICATES
+
+	public static boolean isNoArg(Method m) {
+		Objects.requireNonNull(m);
+		return m.getParameterTypes().length==0;
+	}
+
+	public static boolean isPublic(Method m) {
+		Objects.requireNonNull(m);
+		return Modifier.isPublic(m.getModifiers());
+	}
+
+	public static boolean isVoid(Method m) {
+		Objects.requireNonNull(m);
+		return isVoid(m.getReturnType());
+	}
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
index b2aaeda..5a4a45e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
@@ -435,7 +435,7 @@ public final class Annotations  {
         }
     }
 
-    static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
+    public static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
         private final Field field;
 
         FieldEvaluator(final Field field, final T annotation) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
new file mode 100644
index 0000000..252eff3
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+
+/**
+ * 
+ * Mechanism for obtaining the navigable parent (a domain-object or a domain-view-model) 
+ * of an instance of a class, used to build a navigable parent chain as required by the 
+ * 'where-am-I' feature.
+ * 
+ * @author 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 7d4e842..12833a5 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -54,6 +54,8 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
 
+import com.google.common.base.Function;
+
 /**
  * Represents an entity or value (cf {@link java.lang.Class}) within the
  * metamodel.
@@ -209,6 +211,14 @@ public interface ObjectSpecification extends Specification, ObjectActionContaine
      * returned by the {@link IconFacet}; is not necessarily immutable.
      */
     String getIconName(ObjectAdapter object);
+    
+    /**
+     * Returns this object's navigable parent, if any. 
+     * @param object
+     * @return
+     * @since 2.0.0
+     */
+    Object getNavigableParent(Object object);
 
     /**
      *
@@ -373,4 +383,6 @@ public interface ObjectSpecification extends Specification, ObjectActionContaine
 
     boolean isPersistenceCapable();
     boolean isPersistenceCapableOrViewModel();
+
+	
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index d939016..ab0faa9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
@@ -66,6 +67,7 @@ import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
 import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
 import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
 import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
 import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
 import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
 import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
@@ -91,6 +93,7 @@ import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
 import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
 import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
+import org.apache.isis.core.metamodel.util.pchain.ParentChain;
 import org.apache.isis.objectstore.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacet;
 
 public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implements ObjectSpecification {
@@ -160,6 +163,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
 
     private TitleFacet titleFacet;
     private IconFacet iconFacet;
+    private NavigableParentFacet navigableParentFacet;
     private CssClassFacet cssClassFacet;
 
     private IntrospectionState introspected = IntrospectionState.NOT_INTROSPECTED;
@@ -346,6 +350,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
 
         titleFacet = getFacet(TitleFacet.class);
         iconFacet = getFacet(IconFacet.class);
+        navigableParentFacet = getFacet(NavigableParentFacet.class);
         cssClassFacet = getFacet(CssClassFacet.class);
     }
 
@@ -377,6 +382,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
     public String getIconName(final ObjectAdapter reference) {
         return iconFacet == null ? null : iconFacet.iconName(reference);
     }
+    
+    @Override
+    public Object getNavigableParent(final Object object) {
+        return navigableParentFacet == null 
+        		? null 
+        		: navigableParentFacet.navigableParent(object);
+    }
 
     @Deprecated
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
index 31ae507..8f9471c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
@@ -19,17 +19,15 @@
  
 package org.apache.isis.core.metamodel.util.pchain;
 
-import java.lang.reflect.Method;
 import java.util.LinkedHashSet;
-import java.util.LinkedList;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Stream;
 
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
 /**
- * Represents a unidirectional linked ordered set of Pojos (chain), where the chain 
+ * Represents a unidirectionally linked ordered set of POJOs (chain), where the chain 
  * starts at startNode. Each subsequent node is linked via de-referencing a 
  * singular field (or no-arg method) that is annotated with {@code @Parent}.
  * <br/>
@@ -41,40 +39,26 @@ import org.apache.isis.core.commons.reflection.Reflect;
  */
 public interface ParentChain {
 	
-	static ParentChain simple() {
-		return new SimpleParentChain();
-	}
-	
-	static ParentChain caching() {
-		return new CachingParentChain();
+	public static ParentChain of(Function<Class<?>, ObjectSpecification> specificationLookup){
+		return new ParentChainDefault(specificationLookup);
 	}
 	
+	/**
+	 * Returns the parent node of this {@code node} or {@code null} if {@code node} has no parent.
+	 * @param node
+	 * @return
+	 */
 	public Object parentOf(Object node);
 	
-	static boolean providesParent(Method m) {
-		if(!Reflect.isNoArg(m))
-			return false;
-		if(!Reflect.isPublic(m))
-			return false;
-		if(Reflect.isVoid(m)) 
-			return false;
-		if(Reflect.isPrimitive(m.getReturnType())) 
-			return false;
-		
-		if(m.getName().equals("parent"))
-			return true;
-		
-		if(m.isAnnotationPresent(Parent.class))
-			return true;
-		
-		return false;
-	}
-
-	default Stream<Object> streamParentChainOf(Object startNode){
+	/**
+	 * Returns a Stream of nodes that are chained together by parent references. 
+	 * The startNode is excluded from the Stream.
+	 * @param startNode
+	 * @return
+	 */
+	public default Stream<Object> streamParentChainOf(Object startNode){
 		final Set<Object> chain = new LinkedHashSet<>();
 		
-		chain.add(startNode);
-		
 		Object next = startNode;
 		
 		while((next = parentOf(next))!=null) {
@@ -83,17 +67,8 @@ public interface ParentChain {
 				break;
 		}
 		
-		return chain.stream().skip(1);
+		return chain.stream();
 	}
-	
-	default Stream<Object> streamReversedParentChainOf(Object startNode){
-		final LinkedList<Object> reverseChain = new LinkedList<Object>();
-		
-		streamParentChainOf(startNode)
-		.forEach(reverseChain::addFirst);
-		
-		return reverseChain.stream();
-	}
-	
+
 	
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
similarity index 52%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
index 630e928..2374634 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
@@ -19,48 +19,31 @@
 
 package org.apache.isis.core.metamodel.util.pchain;
 
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
+import java.util.function.Function;
 
-class CachingParentChain extends SimpleParentChain {
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
-	private final SoftCache<Class<?>, MethodHandle> cache = new SoftCache<>();
+class ParentChainDefault implements ParentChain {
+	
+	private final Function<Class<?>, ObjectSpecification> specificationLookup;
+	
+	ParentChainDefault(Function<Class<?>, ObjectSpecification> specificationLookup) {
+		this.specificationLookup = specificationLookup;
+	}
 
 	@Override
 	public Object parentOf(Object node) {
 		if(node==null)
 			return null;
 		
-		final MethodHandle mh = cache.computeIfAbsent(node.getClass(), 
-				key->{
-					try {
-						return methodHandleOf(node);
-					} catch (IllegalAccessException e) {
-						e.printStackTrace();
-						return null;
-					}
-				});
+		final Class<?> cls = node.getClass();
 		
-		if(mh==null)
-			return null;
+		final ObjectSpecification spec = specificationLookup.apply(cls);
 		
-		try {
-			return mh.invoke(node);
-		} catch (Throwable e) {
-			e.printStackTrace();
+		if(spec==null)
 			return null;
-		}
 		
-	}
-	
-	protected static MethodHandle methodHandleOf(Object node) throws IllegalAccessException{
-		final Method getter = parentGetterOf(node);
-		return getter!=null ? handleOf(getter) : null;
-	}
-
-	public static MethodHandle handleOf(Method m) throws IllegalAccessException {
-		return MethodHandles.publicLookup().unreflect(m);
+		return spec.getNavigableParent(node);
 	}
 	
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
deleted file mode 100644
index b30e427..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.lang.NullSafe;
-import org.apache.isis.core.commons.reflection.Reflect;
-
-class SimpleParentChain implements ParentChain {
-
-	@Override
-	public Object parentOf(Object node) {
-		if(node==null)
-			return null;
-		
-		final Method getter = parentGetterOf(node);
-		if(getter==null)
-			return null;
-		
-		try {
-			return getter.invoke(node, Reflect.emptyObjects);
-		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
-			e.printStackTrace();
-		}
-		
-		return null;
-	}
-	
-	protected static Method parentGetterOf(Object node) {
-		return
-		NullSafe.stream(Reflect.getAllDeclaredMethods(node.getClass()))
-		.filter(ParentChain::providesParent)
-		.findFirst()
-		.orElse(findGetterForAnnotatedField(node));
-	}
-	
-	protected static Method findGetterForAnnotatedField(Object node) {
-		return 
-		NullSafe.stream(Reflect.getAllDeclaredFields(node.getClass()))
-		.filter(f->f.isAnnotationPresent(Parent.class))
-		.findFirst()
-		.map(f->getterOf(node, f.getName()))
-		.orElse(null);
-	}
-	
-	private static Method getterOf(Object bean, String propertyName) {
-		try {
-			return Reflect.getGetter(bean, propertyName);
-		} catch (Exception e) {
-			e.printStackTrace();
-			return null;
-		}
-	}
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
deleted file mode 100644
index 34c1ff6..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *       http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-/**
- * Implements a caching {@code Map} where objects are stored referenced by 
- * a unique key, while {@codeMap.Entries} might be garbage collected any time. 
- * 
- * @author 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 96e54b9..293dafb 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -83,6 +83,8 @@ import org.apache.isis.core.metamodel.facets.object.ignore.jdo.RemoveJdoPrefixed
 import org.apache.isis.core.metamodel.facets.object.immutable.immutableannot.CopyImmutableFacetOntoMembersFactory;
 import org.apache.isis.core.metamodel.facets.object.membergroups.annotprop.MemberGroupLayoutFacetFactory;
 import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacetForMixinAnnotationFactory;
+import org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentAnnotationFacetFactory;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
 import org.apache.isis.core.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassNameFactory;
 import org.apache.isis.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImplFactory;
 import org.apache.isis.core.metamodel.facets.object.parseable.annotcfg.ParseableFacetAnnotationElseConfigurationFactory;
@@ -325,6 +327,8 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
         addFactory(new TitleAnnotationFacetFactory());
         addFactory(new TitleFacetViaMethodsFactory());
         addFactory(new IconFacetMethodFactory());
+        addFactory(new NavigableParentAnnotationFacetFactory());
+        addFactory(new NavigableParentFacetMethodFactory());
         addFactory(new CssClassFacetMethodFactory());
 
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
new file mode 100644
index 0000000..b962bbc
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
@@ -0,0 +1,70 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
+
+public class NavigableParentFacetMethodFactoryTest extends AbstractFacetFactoryTest {
+
+    private NavigableParentFacetMethodFactory facetFactory;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        facetFactory = new NavigableParentFacetMethodFactory();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        facetFactory = null;
+        super.tearDown();
+    }
+
+    public void testNavigableParentMethodPickedUpOnClassAndMethodRemoved() {
+        class Customer {
+            @SuppressWarnings("unused")
+            public Object parent() {
+                return null;
+            }
+        }
+        final Method navigableParentMethod = findMethod(Customer.class, "parent");
+
+        facetFactory.process(new ProcessClassContext(Customer.class, methodRemover, facetedMethod));
+
+        final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+        assertThat(facet, is(notNullValue()));
+        assertThat(facet, is(instanceOf(NavigableParentFacetMethod.class)));
+
+        assertTrue(methodRemover.getRemovedMethodMethodCalls().contains(navigableParentMethod));
+    }
+
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
new file mode 100644
index 0000000..70a1dce
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
@@ -0,0 +1,83 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentFacetMethodTest {
+
+    private final Mockery mockery = new JUnit4Mockery();
+
+    private NavigableParentFacetMethod facet;
+    private FacetHolder mockFacetHolder;
+
+    private ObjectAdapter mockOwningAdapter;
+
+    private DomainObjectWithProblemInNavigableParentMethod pojo;
+
+    public static class DomainObjectWithProblemInNavigableParentMethod {
+        public String parent() {
+            throw new NullPointerException();
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+
+        pojo = new DomainObjectWithProblemInNavigableParentMethod();
+        mockFacetHolder = mockery.mock(FacetHolder.class);
+        mockOwningAdapter = mockery.mock(ObjectAdapter.class);
+        final Method navigableParentMethod = DomainObjectWithProblemInNavigableParentMethod.class.getMethod("parent");
+        facet = new NavigableParentFacetMethod(navigableParentMethod, mockFacetHolder);
+
+        mockery.checking(new Expectations() {
+            {
+                allowing(mockOwningAdapter).getObject();
+                will(returnValue(pojo));
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        facet = null;
+    }
+
+    @Test
+    public void testNavigableParentThrowsException() {
+        final Object parent = facet.navigableParent(mockOwningAdapter.getObject());
+        assertThat(parent, is(nullValue()));
+    }
+
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
new file mode 100644
index 0000000..fff951c
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
@@ -0,0 +1,120 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
+import org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectA;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.auto.Mock;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentAnnotationFacetFactoryTest extends AbstractFacetFactoryJUnit4TestCase {
+
+    private NavigableParentAnnotationFacetFactory facetFactory;
+
+    @Mock
+    private ObjectAdapter mockObjectAdapter;
+    @Mock
+    private AuthenticationSession mockAuthenticationSession;
+
+    @Before
+    public void setUp() throws Exception {
+
+        context.allowing(mockSpecificationLoader);
+
+        facetFactory = new NavigableParentAnnotationFacetFactory();
+        facetFactory.setServicesInjector(mockServicesInjector);
+
+        context.checking(new Expectations() {
+            {
+                allowing(mockServicesInjector).lookupService(AuthenticationSessionProvider.class);
+                will(returnValue(mockAuthenticationSessionProvider));
+
+                allowing(mockServicesInjector).lookupService(DeploymentCategoryProvider.class);
+                will(returnValue(mockDeploymentCategoryProvider));
+
+                allowing(mockDeploymentCategoryProvider).getDeploymentCategory();
+                will(returnValue(DeploymentCategory.PRODUCTION));
+
+                allowing(mockAuthenticationSessionProvider).getAuthenticationSession();
+                will(returnValue(mockAuthenticationSession));
+
+                allowing(mockServicesInjector).getSpecificationLoader();
+                will(returnValue(mockSpecificationLoader));
+
+                allowing(mockServicesInjector).getPersistenceSessionServiceInternal();
+                will(returnValue(mockPersistenceSessionServiceInternal));
+            }
+        });
+
+        facetFactory.setServicesInjector(mockServicesInjector);
+
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        facetFactory = null;
+        super.tearDown();
+    }
+
+    @Test
+    public void testParentAnnotatedMethod() throws Exception {
+    	testParentMethod(new DomainObjectA(), "root");
+    }
+
+    // -- HELPER
+    
+    private void testParentMethod(Object domainObject, String parentMethodName) throws Exception {
+    	
+    	final Class<?> domainClass = domainObject.getClass();
+    	
+        facetFactory.process(new ProcessClassContext(domainClass, mockMethodRemover, facetedMethod));
+
+        final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+        Assert.assertNotNull(facet);
+        Assert.assertTrue(facet instanceof NavigableParentFacetMethod);
+        
+        final NavigableParentFacetMethod navigableParentFacetMethod = (NavigableParentFacetMethod) facet;
+        final Method parentMethod = domainClass.getMethod(parentMethodName);
+        
+        Assert.assertEquals(
+        		parentMethod.invoke(domainObject, Reflect.emptyObjects), 
+        		navigableParentFacetMethod.navigableParent(domainObject)	);
+        
+    }
+    
+    
+    
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
new file mode 100644
index 0000000..4326b72
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
@@ -0,0 +1,49 @@
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import org.apache.isis.applib.annotation.Parent;
+
+class NavigableParentTestSamples {
+
+	// has no navigable parent
+	protected static class DomainObjectRoot {
+
+		@Override
+		public String toString() {
+			return "Root";
+		}
+
+	}
+
+	// has navigable parent 'Root' specified via Annotation
+	protected static class DomainObjectA {
+		
+		private final static Object myParent = new DomainObjectRoot();
+
+		@Override
+		public String toString() {
+			return "A";
+		}
+
+		@Parent
+		public Object root() {
+			return myParent;
+		}
+		
+	}
+	
+	// has navigable parent 'A' specified via method
+	protected static class DomainObjectB {
+		
+		private final static Object myParent = new DomainObjectA();
+
+		@Override
+		public String toString() {
+			return "B";
+		}
+
+		public Object parent() {
+			return myParent;
+		}
+		
+	}
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index 086bb4c..6eb9c84 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -189,6 +189,11 @@ public class ObjectSpecificationStub extends FacetHolderImpl implements ObjectSp
         return null;
     }
 
+	@Override
+	public Object getNavigableParent(Object object) {
+		return null;
+	}
+    
     @Override
     public String getCssClass() {
         return null;
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
index fdc11f2..7d4a8ce 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
@@ -26,7 +26,7 @@ import org.apache.isis.viewer.wicket.model.models.EntityModel;
 /**
  * Represents a navigable chain of parent nodes starting at the current node. 
  * 
- * @author a.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
danhaywood@apache.org.

[isis] 06/13: ISIS-1817 + ISIS-1818 added missing assignment + clarify comments

Posted by da...@apache.org.
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 1155e89564761b6a94e2f02637f0044d9e9cabc8
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 15 09:08:29 2018 +0100

    ISIS-1817 + ISIS-1818 added missing assignment + clarify comments
---
 .../model/models/whereami/WhereAmIModelDefault.java       | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

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 ff06907..9b335ba 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
@@ -72,7 +72,7 @@ class WhereAmIModelDefault implements WhereAmIModel {
 	@Override
 	public Stream<EntityModel> streamParentChainReversed() {
 		if(!isWhereAmIEnabled)
-			return Stream.empty(); // unexpected call, we could log a warning
+			return Stream.empty(); //[ahuber] unexpected call, we could log a warning
 		
 		return reversedChainOfParents.stream()
 		.map(this::toEntityModel);
@@ -89,11 +89,20 @@ class WhereAmIModelDefault implements WhereAmIModel {
 	private void overrideFromConfigIfNew(IsisConfiguration configuration) {
 		
 		//[ahuber] without evidence that this significantly improves performance, 
-		// we use the smart update idiom here ...
-		final int newConfigHash = System.identityHashCode(configuration);
+		// (skipping 2 hash-table lookups) we use the smart update idiom here ...
+		//
+		// Note: Updates are expected to occur only once per application life-cycle,
+		// however this class might be loaded by a class-loader, that endures multiple
+		// application life-cycles. Chances of hash-collisions are simply neglected.
+		
+		// that's the hash of the object (we don't care about the actual config values)
+		// assuming that, we get a new (immutable) config instance each app's life-cycle:
+		final int newConfigHash = System.identityHashCode(configuration); 
 		if(newConfigHash == configHash)
 			return;
 		
+		configHash = newConfigHash;
+		
 		isWhereAmIEnabled = configuration.getBoolean(
 				CONFIG_KEY_IS_WHERE_AM_I_FEATURE_ENABLED,
 				IS_WHERE_AM_I_FEATURE_ENABLED_DEFAULT);

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

[isis] 11/13: ISIS-1740: fixes merge issues (NullSafe moved to applib and renamed)

Posted by da...@apache.org.
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 cc2f5d0736d1db38b189397dbb35a1f14e964712
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Feb 14 15:12:56 2018 +0000

    ISIS-1740: fixes merge issues (NullSafe moved to applib and renamed)
---
 .../navparent/annotation/NavigableParentAnnotationFacetFactory.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
index 585228a..3ebb7b8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -23,8 +23,8 @@ import java.lang.reflect.Method;
 import java.util.List;
 
 import org.apache.isis.applib.annotation.Parent;
+import org.apache.isis.applib.internal.base._NullSafe;
 import org.apache.isis.core.commons.config.IsisConfiguration;
-import org.apache.isis.core.commons.lang.NullSafe;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FacetUtil;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
@@ -66,7 +66,7 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
         final List<Annotations.Evaluator<Parent>> evaluators = 
         		Annotations.firstEvaluatorsInHierarchyHaving(cls, Parent.class);
         
-        if (NullSafe.isEmpty(evaluators)) {
+        if (_NullSafe.isEmpty(evaluators)) {
             return; // no parent resolvable
         } else if (evaluators.size()>1) {
         	// code should not be reached, since case should be handled by meta-data validation
@@ -114,7 +114,7 @@ public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract
                 final List<Annotations.Evaluator<Parent>> evaluators = 
                 		Annotations.firstEvaluatorsInHierarchyHaving(cls, Parent.class);
                 
-                if (NullSafe.isEmpty(evaluators)) {
+                if (_NullSafe.isEmpty(evaluators)) {
                 	return true; // no conflict, continue validation processing
                 } else if (evaluators.size()>1) {
                 	

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

[isis] 13/13: Merge branch 'ISIS-1852_parallel_stream_patch'

Posted by da...@apache.org.
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 fb552d3523e11c6383b93401c5a744e064411f85
Merge: 6ee4202 3e3ad2b
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Feb 14 15:14:05 2018 +0000

    Merge branch 'ISIS-1852_parallel_stream_patch'

 .../runtime/services/ServiceInstantiatorTest.java  | 37 +++++++++++++++-------
 1 file changed, 25 insertions(+), 12 deletions(-)

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

[isis] 05/13: ISIS-1817 + ISIS-1818 allow configuration of where-am-i feature

Posted by da...@apache.org.
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 08ce1a64f305287be398f610db5f734c685fda29
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jan 12 18:30:07 2018 +0100

    ISIS-1817 + ISIS-1818 allow configuration of where-am-i feature
---
 .../core/metamodel/util/pchain/ParentChain.java    | 20 ++++++++---
 .../model/models/whereami/WhereAmIModel.java       |  7 ++++
 .../models/whereami/WhereAmIModelDefault.java      | 41 ++++++++++++++++++++--
 3 files changed, 61 insertions(+), 7 deletions(-)

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 8f9471c..80a1724 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
@@ -35,6 +35,7 @@ import org.apache.isis.core.metamodel.spec.ObjectSpecification;
  * startNode --@Parent--&gt; node2 --@Parent--&gt; node3 ...
  * 
  * @author ahuber@apache.org
+ * @since 2.0.0
  *
  */
 public interface ParentChain {
@@ -51,23 +52,32 @@ public interface ParentChain {
 	public Object parentOf(Object node);
 	
 	/**
-	 * Returns a Stream of nodes that are chained together by parent references. 
-	 * The startNode is excluded from the Stream.
+	 * Returns a Stream of nodes that are chained together by parent references. <br/> 
+	 * The {@code startNode} is excluded from the Stream.  <br/><br/>
+	 * The chain stops either because there is no more resolvable parent,<br/>
+	 * or we reached the {@code maxChainLength},<br/>
+	 * or we reached a node that is already part of the chain.
+	 * 
 	 * @param startNode
+	 * @param maxChainLength maximum length of the chain returned 
 	 * @return
 	 */
-	public default Stream<Object> streamParentChainOf(Object startNode){
+	public default Stream<Object> streamParentChainOf(Object startNode, int maxChainLength){
 		final Set<Object> chain = new LinkedHashSet<>();
 		
 		Object next = startNode;
 		
+		chain.add(startNode); // for infinite loop detection
+		
 		while((next = parentOf(next))!=null) {
-			final boolean doContinue = chain.add(next);
+			final boolean doContinue = chain.add(next); // stops if the we get to a node we already traversed before
 			if(!doContinue)
 				break;
+			if(chain.size()>=maxChainLength)
+				break;
 		}
 		
-		return chain.stream();
+		return chain.stream().skip(1);
 	}
 
 	
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 7d4a8ce..9de0c22 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
@@ -33,6 +33,13 @@ import org.apache.isis.viewer.wicket.model.models.EntityModel;
  */
 public interface WhereAmIModel {
 
+	public final static String CONFIG_KEY_IS_WHERE_AM_I_FEATURE_ENABLED = "isis.viewer.whereAmI.enabled";
+	public final static String CONFIG_KEY_MAX_NAVIGABLE_PARENT_CHAIN_LENGTH = "isis.viewer.whereAmI.maxParentChainLength";
+	
+	public final static boolean IS_WHERE_AM_I_FEATURE_ENABLED_DEFAULT = true;
+	public final static int MAX_NAVIGABLE_PARENT_CHAIN_LENGTH_DEFAULT = 64;
+	
+	
 	public static WhereAmIModel of(EntityModel startOfChain) {
 		return new WhereAmIModelDefault(startOfChain);
 	}
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 042595f..ff06907 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
@@ -22,27 +22,40 @@ package org.apache.isis.viewer.wicket.model.models.whereami;
 import java.util.LinkedList;
 import java.util.stream.Stream;
 
+import org.apache.isis.core.commons.config.IsisConfiguration;
 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;
 
+/**
+ * 
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
 class WhereAmIModelDefault implements WhereAmIModel {
 
 	private final LinkedList<Object> reversedChainOfParents = new LinkedList<>();
 	private final EntityModel startOfChain;
 	
+	private static boolean isWhereAmIEnabled = IS_WHERE_AM_I_FEATURE_ENABLED_DEFAULT;
+	private static int maxChainLength = MAX_NAVIGABLE_PARENT_CHAIN_LENGTH_DEFAULT;
+	private static int configHash = 0;
+	
 	public WhereAmIModelDefault(EntityModel startOfChain) {
 		this.startOfChain = startOfChain;
 		
+		overrideFromConfigIfNew(startOfChain.getPersistenceSession().getConfiguration());		
+		
 		final ObjectAdapter adapter = startOfChain.getObject();
 		final Object startNode = adapter.getObject();
 		
 		ParentChain.of(IsisContext.getSessionFactory().getSpecificationLoader()::loadSpecification)
-		.streamParentChainOf(startNode)
+		.streamParentChainOf(startNode, maxChainLength)
 		.forEach(reversedChainOfParents::addFirst);
 	}
-	
+
 	@Override
 	public EntityModel getStartOfChain() {
 		return startOfChain;
@@ -50,11 +63,17 @@ class WhereAmIModelDefault implements WhereAmIModel {
 	
 	@Override
 	public boolean isShowWhereAmI() {
+		if(!isWhereAmIEnabled)
+			return false; // this will prevent rendering 
+		
 		return !reversedChainOfParents.isEmpty();
 	}
 
 	@Override
 	public Stream<EntityModel> streamParentChainReversed() {
+		if(!isWhereAmIEnabled)
+			return Stream.empty(); // unexpected call, we could log a warning
+		
 		return reversedChainOfParents.stream()
 		.map(this::toEntityModel);
 	}
@@ -67,4 +86,22 @@ class WhereAmIModelDefault implements WhereAmIModel {
 				.adapterFor(domainObject)	);
 	}
 	
+	private void overrideFromConfigIfNew(IsisConfiguration configuration) {
+		
+		//[ahuber] without evidence that this significantly improves performance, 
+		// we use the smart update idiom here ...
+		final int newConfigHash = System.identityHashCode(configuration);
+		if(newConfigHash == configHash)
+			return;
+		
+		isWhereAmIEnabled = configuration.getBoolean(
+				CONFIG_KEY_IS_WHERE_AM_I_FEATURE_ENABLED,
+				IS_WHERE_AM_I_FEATURE_ENABLED_DEFAULT);
+		maxChainLength = configuration.getInteger(
+				CONFIG_KEY_MAX_NAVIGABLE_PARENT_CHAIN_LENGTH,
+				MAX_NAVIGABLE_PARENT_CHAIN_LENGTH_DEFAULT);
+		
+	}
+
+	
 }

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

[isis] 07/13: ISIS-1816 refactoring Annotations + implement hierarchy search

Posted by da...@apache.org.
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 0645ecde12e94d416bf858387eeca960cb0bc60a
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 15 11:15:35 2018 +0100

    ISIS-1816 refactoring Annotations + implement hierarchy search
---
 .../isis/core/metamodel/facets/Annotations.java    | 103 ++++++++++++++++-----
 1 file changed, 80 insertions(+), 23 deletions(-)

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 5a4a45e..3d24c77 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
@@ -26,13 +26,13 @@ import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 import javax.validation.constraints.Pattern;
 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
-import com.google.common.collect.Lists;
-
 import org.apache.isis.applib.annotation.Collection;
 import org.apache.isis.applib.annotation.CollectionLayout;
 import org.apache.isis.applib.annotation.MemberOrder;
@@ -43,6 +43,8 @@ import org.apache.isis.core.commons.lang.ThrowableExtensions;
 import org.apache.isis.core.metamodel.exceptions.MetaModelException;
 import org.apache.isis.core.metamodel.methodutils.MethodScope;
 
+import com.google.common.collect.Lists;
+
 public final class Annotations  {
     
     private Annotations() {}
@@ -352,48 +354,103 @@ public final class Annotations  {
 
     /**
      * Searches for all no-arg methods or fields with a specified title, returning an
-     * {@link Evaluator} object that wraps either.  Will search up hierarchy also.
+     * {@link Evaluator} object that wraps either. Will search up hierarchy also, 
+     * including implemented interfaces.
      */
     public static <T extends Annotation> List<Evaluator<T>> getEvaluators(
             final Class<?> cls,
             final Class<T> annotationClass) {
-        List<Evaluator<T>> evaluators = Lists.newArrayList();
-        appendEvaluators(cls, annotationClass, evaluators);
+        final List<Evaluator<T>> evaluators = Lists.newArrayList();
+        visitEvaluators(cls, annotationClass, evaluators::add);
+        
+        // search implemented interfaces
+        final Class<?>[] interfaces = cls.getInterfaces();
+        for (final Class<?> iface : interfaces) {
+        	visitEvaluators(iface, annotationClass, evaluators::add);
+        }
+        
         return evaluators;
     }
+    
+    /**
+     * Starting from the current class {@code cls}, we search down the inheritance 
+     * hierarchy (super class, super super class, ...), until we find 
+     * the first class that has at least a field or no-arg method with {@code annotationClass} annotation.
+     * <br/>
+     * In this hierarchy traversal, implemented interfaces are not processed.      
+     * @param cls
+     * @param annotationClass
+     * @return list of {@link Evaluator} that wraps each annotated member found on the class where 
+     * the search stopped, null otherwise
+     * 
+     * @since 2.0.0
+     */
+    public static <T extends Annotation> List<Evaluator<T>> findFirstInHierarchyHaving(
+            final Class<?> cls,
+            final Class<T> annotationClass) {
+    	
+    	 final List<Evaluator<T>> evaluators = Lists.newArrayList();
+    	 visitEvaluatorsWhile(cls, annotationClass, __->evaluators.isEmpty(), evaluators::add);
+         
+         return evaluators;
+    }
 
-    private static <T extends Annotation> void appendEvaluators(
+    private static <T extends Annotation> void visitEvaluators(
+    		final Class<?> cls,
+            final Class<T> annotationClass,
+            final Consumer<Evaluator<T>> visitor) {
+    	visitEvaluatorsWhile(cls, annotationClass, __->true, visitor);
+    }
+    
+    private static <T extends Annotation> void visitEvaluatorsWhile(
             final Class<?> cls,
             final Class<T> annotationClass,
-            final List<Evaluator<T>> evaluators) {
+            Predicate<Class<?>> filter,
+            final Consumer<Evaluator<T>> visitor) {
+    	
+    	if(!filter.test(cls))
+    		return; // stop visitation
+    	
+    	collectMethodEvaluators(cls, annotationClass, visitor);
+    	collectFieldEvaluators(cls, annotationClass, visitor);
+        
+        // search super-classes
+        final Class<?> superclass = cls.getSuperclass();
+        if (superclass != null) {
+        	visitEvaluatorsWhile(superclass, annotationClass, filter, visitor);
+        }
 
-        for (Method method : cls.getDeclaredMethods()) {
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	private static <T extends Annotation> void collectMethodEvaluators(
+            final Class<?> cls,
+            final Class<T> annotationClass,
+            final Consumer<Evaluator<T>> action) {
+    	
+    	for (Method method : cls.getDeclaredMethods()) {
             if(MethodScope.OBJECT.matchesScopeOf(method) &&
                     method.getParameterTypes().length == 0) {
                 final Annotation annotation = method.getAnnotation(annotationClass);
                 if(annotation != null) {
-                    evaluators.add(new MethodEvaluator(method, annotation));
+                	action.accept(new MethodEvaluator(method, annotation));
                 }
             }
         }
-        for (final Field field: cls.getDeclaredFields()) {
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+	private static <T extends Annotation> void collectFieldEvaluators(
+            final Class<?> cls,
+            final Class<T> annotationClass,
+            final Consumer<Evaluator<T>> action) {
+    	
+    	for (final Field field: cls.getDeclaredFields()) {
             final Annotation annotation = field.getAnnotation(annotationClass);
             if(annotation != null) {
-                evaluators.add(new FieldEvaluator(field, annotation));
+            	action.accept(new FieldEvaluator(field, annotation));
             }
         }
-
-        // search superclasses
-        final Class<?> superclass = cls.getSuperclass();
-        if (superclass != null) {
-            appendEvaluators(superclass, annotationClass, evaluators);
-        }
-
-        // search implemented interfaces
-        final Class<?>[] interfaces = cls.getInterfaces();
-        for (final Class<?> iface : interfaces) {
-            appendEvaluators(iface, annotationClass, evaluators);
-        }
     }
 
     public static abstract class Evaluator<T extends Annotation> {

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

[isis] 12/13: Merge branch 'ISIS-1740-where-am-i'

Posted by da...@apache.org.
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 6ee4202247019250c000dc2bf6faded9c4ea2fb2
Merge: c05e5ca cc2f5d0
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Feb 14 15:13:20 2018 +0000

    Merge branch 'ISIS-1740-where-am-i'

 .../org/apache/isis/applib/annotation/Parent.java  |  34 ++++
 .../isis/core/commons/lang/MethodExtensions.java   |  14 +-
 .../core/commons/lang/ThrowableExtensions.java     |  62 +++++--
 .../isis/core/commons/reflection/Reflect.java      | 202 +++++++++++++++++++++
 .../isis/core/metamodel/facets/Annotations.java    | 199 +++++++++++++++-----
 ...ctionInvocationFacetForDomainEventAbstract.java |  54 +++---
 .../object/navparent/NavigableParentFacet.java}    |  26 +++
 .../navparent/NavigableParentFacetAbstract.java}   |  18 ++
 .../NavigableParentAnnotationFacetFactory.java     | 168 +++++++++++++++++
 .../method/NavigableParentFacetMethod.java         |  54 ++++++
 .../method/NavigableParentFacetMethodFactory.java  |  67 +++++++
 .../core/metamodel/spec/ObjectSpecification.java   |  12 ++
 .../specimpl/ObjectSpecificationAbstract.java      |  12 ++
 .../core/metamodel/util/pchain/ParentChain.java    |  84 +++++++++
 .../metamodel/util/pchain/ParentChainDefault.java  |  49 +++++
 .../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       |  65 +++++++
 .../models/whereami/WhereAmIModelDefault.java      | 116 ++++++++++++
 .../viewer/wicket/ui/pages/entity/EntityPage.css   |  29 +++
 .../viewer/wicket/ui/pages/entity/EntityPage.html  |   7 +-
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |  53 +++++-
 26 files changed, 1552 insertions(+), 104 deletions(-)

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

[isis] 02/13: ISIS-1740 refactored method names, added javadoc

Posted by da...@apache.org.
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 efdd48115b7f8237be2756248c2cfa532054d6c1
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Oct 27 16:03:36 2017 +0200

    ISIS-1740 refactored method names, added javadoc
---
 .../model/models/whereami/WhereAmIModel.java       | 28 ++++++++++++++++++----
 .../models/whereami/WhereAmIModelDefault.java      | 24 +++++++++----------
 .../viewer/wicket/ui/pages/entity/EntityPage.java  |  4 ++--
 3 files changed, 38 insertions(+), 18 deletions(-)

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 7ecfba0..fdc11f2 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
@@ -23,16 +23,36 @@ import java.util.stream.Stream;
 
 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
+ * 
+ * @since 2.0.0
+ *
+ */
 public interface WhereAmIModel {
 
-	public static WhereAmIModel of(EntityModel endOfChain) {
-		return new WhereAmIModelDefault(endOfChain);
+	public static WhereAmIModel of(EntityModel startOfChain) {
+		return new WhereAmIModelDefault(startOfChain);
 	}
 
+	/**
+	 * The navigable parent chain requires a minimum length of 2 in order to be shown.
+	 * @return whether the where-am-I hint should be shown or hidden
+	 */
 	public boolean isShowWhereAmI();
 	
-	public Stream<EntityModel> streamParentChain();
+	/**
+	 * Streams the linked nodes of this model's navigable parent chain in reverse order.
+	 * @return reversed order stream of linked parent nodes, which does not include the start node
+	 */
+	public Stream<EntityModel> streamParentChainReversed();
 	
-	public EntityModel getEndOfChain();
+	/**
+	 *  
+	 * @return the immutable start node of the navigable parent chain
+	 */
+	public EntityModel getStartOfChain();
 	
 }
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 b3f6679..955c2db 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
@@ -28,32 +28,32 @@ import org.apache.isis.viewer.wicket.model.models.EntityModel;
 
 class WhereAmIModelDefault implements WhereAmIModel {
 
-	private final List<Object> chainOfParents = new ArrayList<>();
-	private final EntityModel endOfChain;
+	private final List<Object> reversedChainOfParents = new ArrayList<>();
+	private final EntityModel startOfChain;
 	
-	public WhereAmIModelDefault(EntityModel endOfChain) {
-		this.endOfChain = endOfChain;
+	public WhereAmIModelDefault(EntityModel startOfChain) {
+		this.startOfChain = startOfChain;
 		
-		final Object startPojo = endOfChain.getObject().getObject();
+		final Object startPojo = startOfChain.getObject().getObject();
 
 		ParentChain.caching()
 		.streamReversedParentChainOf(startPojo)
-		.forEach(chainOfParents::add);
+		.forEach(reversedChainOfParents::add);
 	}
 	
 	@Override
-	public EntityModel getEndOfChain() {
-		return endOfChain;
+	public EntityModel getStartOfChain() {
+		return startOfChain;
 	}
 	
 	@Override
 	public boolean isShowWhereAmI() {
-		return !chainOfParents.isEmpty();
+		return !reversedChainOfParents.isEmpty();
 	}
 
 	@Override
-	public Stream<EntityModel> streamParentChain() {
-		return chainOfParents.stream()
+	public Stream<EntityModel> streamParentChainReversed() {
+		return reversedChainOfParents.stream()
 		.map(this::toEntityModel);
 	}
 	
@@ -61,7 +61,7 @@ class WhereAmIModelDefault implements WhereAmIModel {
 
 	private EntityModel toEntityModel(Object domainObject) {
 		return new EntityModel(
-				endOfChain.getPersistenceSession()
+				startOfChain.getPersistenceSession()
 				.adapterFor(domainObject)	);
 	}
 	
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 e9e04dc..d105c48 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
@@ -226,11 +226,11 @@ public class EntityPage extends PageAbstract {
     	
     	final RepeatingView listItems = new RepeatingView("whereAmI-items");
     	
-    	whereAmIModel.streamParentChain().forEach(entityModel->
+    	whereAmIModel.streamParentChainReversed().forEach(entityModel->
     		listItems.add(new EntityIconAndTitlePanel(listItems.newChildId(), entityModel))	
 		);
     	
-    	listItems.add(new Label(listItems.newChildId(), whereAmIModel.getEndOfChain().getTitle()));
+    	listItems.add(new Label(listItems.newChildId(), whereAmIModel.getStartOfChain().getTitle()));
     	
     	whereAmIContainer.addOrReplace(listItems);
     	

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