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:19 UTC
[isis] 03/13: ISIS-1740 new Facet: NavigableParentFacet + major
rework to integrate with Isis' meta-model
This is an automated email from the ASF dual-hosted git repository.
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.