You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2018/01/12 15:12:21 UTC
[isis] branch ISIS-1740-where-am-i updated: ISIS-1740 new Facet:
NavigableParentFacet + major rework to integrate with Isis' meta-model
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch ISIS-1740-where-am-i
in repository https://gitbox.apache.org/repos/asf/isis.git
The following commit(s) were added to refs/heads/ISIS-1740-where-am-i by this push:
new f5d87ab ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate with Isis' meta-model
f5d87ab is described below
commit f5d87abdc412f9b1e70bc915cf134cd0919f8c7f
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Jan 12 16:11:31 2018 +0100
ISIS-1740 new Facet: NavigableParentFacet + major rework to integrate
with Isis' meta-model
---
.../services/navparent/NavigableParentService.java | 20 +++
.../isis/core/commons/reflection/Reflect.java | 11 +-
.../isis/core/metamodel/facets/Annotations.java | 2 +-
.../object/navparent/NavigableParentFacet.java | 44 ++++++
.../navparent/NavigableParentFacetAbstract.java | 36 +++++
.../NavigableParentAnnotationFacetFactory.java | 163 +++++++++++++++++++++
.../method/NavigableParentFacetMethod.java | 68 +++++++++
.../method/NavigableParentFacetMethodFactory.java | 64 ++++++++
.../core/metamodel/spec/ObjectSpecification.java | 19 ++-
.../specimpl/ObjectSpecificationAbstract.java | 12 ++
.../core/metamodel/util/pchain/ParentChain.java | 63 +++-----
...ingParentChain.java => ParentChainDefault.java} | 43 ++----
.../metamodel/util/pchain/SimpleParentChain.java | 75 ----------
.../isis/core/metamodel/util/pchain/SoftCache.java | 124 ----------------
.../dflt/ProgrammingModelFacetsJava5.java | 4 +
.../NavigableParentFacetMethodFactoryTest.java | 70 +++++++++
.../navparent/NavigableParentFacetMethodTest.java | 83 +++++++++++
.../NavigableParentAnnotationFacetFactoryTest.java | 120 +++++++++++++++
.../annotation/NavigableParentTestSamples.java | 49 +++++++
.../testspec/ObjectSpecificationStub.java | 5 +
.../model/models/whereami/WhereAmIModel.java | 2 +-
.../models/whereami/WhereAmIModelDefault.java | 18 ++-
22 files changed, 806 insertions(+), 289 deletions(-)
diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
new file mode 100644
index 0000000..129122a
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/navparent/NavigableParentService.java
@@ -0,0 +1,20 @@
+package org.apache.isis.applib.services.navparent;
+
+import org.apache.isis.applib.annotation.Programmatic;
+
+/**
+ *
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ */
+public interface NavigableParentService {
+
+ /**
+ * Return the navigable parent (a domain-object or a domain-view-model) of the object,
+ * used to build a navigable parent chain as required by the 'where-am-I' feature.
+ *
+ */
+ @Programmatic
+ public Object navigableParentOf(Object domainObject);
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java b/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
index 82cc7e5..59bc1c8 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/commons/reflection/Reflect.java
@@ -93,8 +93,8 @@ public class Reflect {
visitor.accept(interf);
}
- public static Method getGetter(Object bean, String propertyName) throws IntrospectionException {
- final BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
+ public static Method getGetter(Class<?> cls, String propertyName) throws IntrospectionException {
+ final BeanInfo beanInfo = Introspector.getBeanInfo(cls);
for(PropertyDescriptor pd:beanInfo.getPropertyDescriptors()){
if(!pd.getName().equals(propertyName))
continue;
@@ -103,6 +103,13 @@ public class Reflect {
return null;
}
+ public static Method getGetter(Object bean, String propertyName) throws IntrospectionException {
+ if(bean==null)
+ return null;
+ return getGetter(bean, propertyName);
+ }
+
+
// -- PRIMITIVE TYPES
private static final Set<Class<?>> primitives = new HashSet<>(Arrays.asList(
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
index b2aaeda..5a4a45e 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
@@ -435,7 +435,7 @@ public final class Annotations {
}
}
- static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
+ public static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
private final Field field;
FieldEvaluator(final Field field, final T annotation) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
new file mode 100644
index 0000000..252eff3
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacet.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+
+/**
+ *
+ * Mechanism for obtaining the navigable parent (a domain-object or a domain-view-model)
+ * of an instance of a class, used to build a navigable parent chain as required by the
+ * 'where-am-I' feature.
+ *
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
+public interface NavigableParentFacet extends Facet {
+
+ /**
+ * Returns the navigable parent (a domain-object or a domain-view-model) for the target object
+ * or null if there is no parent.
+ * @param object
+ * @return
+ */
+ Object navigableParent(final Object object);
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
new file mode 100644
index 0000000..b3468ac
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+
+public abstract class NavigableParentFacetAbstract extends FacetAbstract implements NavigableParentFacet {
+
+ public static Class<? extends Facet> type() {
+ return NavigableParentFacet.class;
+ }
+
+ public NavigableParentFacetAbstract(final FacetHolder holder) {
+ super(type(), holder, Derivation.NOT_DERIVED);
+ }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
new file mode 100644
index 0000000..f3b2102
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import java.beans.IntrospectionException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Parent;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
+import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
+import org.apache.isis.core.metamodel.facets.MethodFinderUtils;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.methodutils.MethodScope;
+import org.apache.isis.core.metamodel.services.ServicesInjector;
+import org.apache.isis.core.metamodel.services.persistsession.PersistenceSessionServiceInternal;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
+import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorVisiting;
+import org.apache.isis.core.metamodel.specloader.validator.ValidationFailures;
+
+/**
+ *
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
+
+ private static final String NAVIGABLE_PARENT_METHOD_NAME = "parent";
+
+
+ public NavigableParentAnnotationFacetFactory() {
+ super(FeatureType.OBJECTS_ONLY);
+ }
+
+ @Override
+ public void process(final ProcessClassContext processClassContext) {
+ final Class<?> cls = processClassContext.getCls();
+ final FacetHolder facetHolder = processClassContext.getFacetHolder();
+
+ final List<Annotations.Evaluator<Parent>> evaluators = Annotations.getEvaluators(cls, Parent.class);
+ if (evaluators.isEmpty()) {
+ return;
+ } else if (evaluators.size()>1) {
+ throw new RuntimeException("unable to determine navigable parent due to ambiguity");
+ }
+
+ final Annotations.Evaluator<Parent> parentEvaluator = evaluators.get(0);
+
+ final Method method;
+
+ // find method that provides the parent ...
+ if(parentEvaluator instanceof Annotations.MethodEvaluator) {
+ // we have a @Parent annotated method
+ method = ((Annotations.MethodEvaluator<Parent>) parentEvaluator).getMethod();
+ } else if(parentEvaluator instanceof Annotations.FieldEvaluator) {
+ // we have a @Parent annotated field (occurs if one uses lombok's @Getter on a field)
+ final Field field = ((Annotations.FieldEvaluator<Parent>) parentEvaluator).getField();
+ try {
+ method = Reflect.getGetter(cls, field.getName());
+ } catch (IntrospectionException e) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ try {
+ FacetUtil.addFacet(new NavigableParentFacetMethod(method, facetHolder));
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ /**
+ * Violation if there is a class that has both a <tt>parent()</tt> method and also
+ * any non-inherited method annotated with <tt>@Parent</tt>.
+ * <p>
+ * If there are only inherited methods annotated with <tt>@Parent</tt> then this is
+ * <i>not</i> a violation; but the imperative <tt>parent()</tt> method will take precedence.
+ * </p>
+ */
+ @Override
+ public void refineMetaModelValidator(MetaModelValidatorComposite metaModelValidator, IsisConfiguration configuration) {
+ metaModelValidator.add(new MetaModelValidatorVisiting(new MetaModelValidatorVisiting.Visitor() {
+
+ //TODO [ahuber] code is a copy of the TitleAnnotationFacetFactory, not sure ...
+ // 1) what the wanted behavior should be (what about annotations in interfaces, ambiguity, etc.)
+ // 2) what this code fragment does
+
+ @Override
+ public boolean visit(ObjectSpecification objectSpec, ValidationFailures validationFailures) {
+ final Class<?> cls = objectSpec.getCorrespondingClass();
+
+ final Method parentMethod =
+ MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, NAVIGABLE_PARENT_METHOD_NAME, Object.class, null);
+ if (parentMethod == null) {
+ return true; // no conflict
+ }
+
+ // determine if cls contains a @Parent annotated method, not inherited from superclass
+ final Class<?> supClass = cls.getSuperclass();
+ if (supClass == null) {
+ return true; // no conflict
+ }
+
+ final List<Method> methods = methodsWithParentAnnotation(cls);
+ final List<Method> superClassMethods = methodsWithParentAnnotation(supClass);
+ if (methods.size() > superClassMethods.size()) {
+ validationFailures.add(
+ "%s: conflict for determining a strategy for retrieval of (navigable) parent for class, "
+ + "contains a method '%s' and an annotation '@%s'",
+ objectSpec.getIdentifier().getClassName(),
+ NAVIGABLE_PARENT_METHOD_NAME,
+ Parent.class.getName());
+ }
+
+ return true;
+ }
+
+ private List<Method> methodsWithParentAnnotation(final Class<?> cls) {
+ return MethodFinderUtils.findMethodsWithAnnotation(cls, MethodScope.OBJECT, Parent.class);
+ }
+
+ }));
+ }
+
+
+ @Override
+ public void setServicesInjector(final ServicesInjector servicesInjector) {
+ super.setServicesInjector(servicesInjector);
+ adapterManager = servicesInjector.getPersistenceSessionServiceInternal();
+ }
+
+ PersistenceSessionServiceInternal adapterManager;
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
new file mode 100644
index 0000000..167a048
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent.method;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacetAbstract;
+
+/**
+ *
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentFacetMethod extends NavigableParentFacetAbstract {
+
+ private final MethodHandle methodHandle;
+
+ public NavigableParentFacetMethod(final Method method, final FacetHolder holder) throws IllegalAccessException {
+ super(holder);
+ this.methodHandle = handleOf(method);
+ }
+
+ @Override
+ public Object navigableParent(Object object) {
+ try {
+ return methodHandle.invoke(object);
+ } catch (final Throwable ex) {
+ return null;
+ }
+ }
+
+ // -- HELPER
+
+ private static MethodHandle handleOf(Method m) throws IllegalAccessException {
+
+ if(!m.isAccessible()) {
+ m.setAccessible(true);
+ MethodHandle mh = MethodHandles.publicLookup().unreflect(m);
+ m.setAccessible(false);
+ return mh;
+ }
+
+ return MethodHandles.publicLookup().unreflect(m);
+
+ }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java
new file mode 100644
index 0000000..ad0db2a
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethodFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent.method;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facetapi.FacetUtil;
+import org.apache.isis.core.metamodel.facetapi.FeatureType;
+import org.apache.isis.core.metamodel.methodutils.MethodScope;
+import org.apache.isis.core.metamodel.facets.MethodFinderUtils;
+import org.apache.isis.core.metamodel.facets.MethodPrefixBasedFacetFactoryAbstract;
+
+/**
+ *
+ * @author ahuber@apache.org
+ * @since 2.0.0
+ *
+ */
+public class NavigableParentFacetMethodFactory extends MethodPrefixBasedFacetFactoryAbstract {
+
+ private static final String NAVIGABLE_PARENT_PREFIX = "parent";
+
+ private static final String[] PREFIXES = { NAVIGABLE_PARENT_PREFIX, };
+
+ public NavigableParentFacetMethodFactory() {
+ super(FeatureType.OBJECTS_ONLY, OrphanValidation.VALIDATE, PREFIXES);
+ }
+
+ @Override
+ public void process(final ProcessClassContext processClassContext) {
+ final Class<?> cls = processClassContext.getCls();
+ final FacetHolder facetHolder = processClassContext.getFacetHolder();
+
+ final Method method =
+ MethodFinderUtils.findMethod(cls, MethodScope.OBJECT, NAVIGABLE_PARENT_PREFIX, Object.class, NO_PARAMETERS_TYPES);
+ if (method == null) {
+ return;
+ }
+ processClassContext.removeMethod(method);
+ try {
+ FacetUtil.addFacet(new NavigableParentFacetMethod(method, facetHolder));
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
index dd405ea..c8c03b9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/ObjectSpecification.java
@@ -23,23 +23,22 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import com.google.common.base.Function;
-
import org.apache.isis.core.commons.authentication.AuthenticationSession;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.consent.InteractionResult;
-import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
-import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.facets.object.domainobject.DomainObjectAnnotationFacetFactoryTest.ObjectType;
import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet;
import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
+import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet;
import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
@@ -52,6 +51,8 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationContainer;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.core.metamodel.specloader.classsubstitutor.ClassSubstitutor;
+import com.google.common.base.Function;
+
/**
* Represents an entity or value (cf {@link java.lang.Class}) within the
* metamodel.
@@ -187,6 +188,14 @@ public interface ObjectSpecification extends Specification, ObjectActionContaine
* returned by the {@link IconFacet}; is not necessarily immutable.
*/
String getIconName(ObjectAdapter object);
+
+ /**
+ * Returns this object's navigable parent, if any.
+ * @param object
+ * @return
+ * @since 2.0.0
+ */
+ Object getNavigableParent(Object object);
/**
*
@@ -351,4 +360,6 @@ public interface ObjectSpecification extends Specification, ObjectActionContaine
boolean isPersistenceCapable();
boolean isPersistenceCapableOrViewModel();
+
+
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
index 21dcac5..90021e6 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectSpecificationAbstract.java
@@ -24,6 +24,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Stream;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
@@ -66,6 +67,7 @@ import org.apache.isis.core.metamodel.facets.object.icon.IconFacet;
import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet;
import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacet;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
import org.apache.isis.core.metamodel.facets.object.objectspecid.ObjectSpecIdFacet;
import org.apache.isis.core.metamodel.facets.object.parented.ParentedCollectionFacet;
import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
@@ -91,6 +93,7 @@ import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor;
+import org.apache.isis.core.metamodel.util.pchain.ParentChain;
import org.apache.isis.objectstore.jdo.metamodel.facets.object.persistencecapable.JdoPersistenceCapableFacet;
public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implements ObjectSpecification {
@@ -160,6 +163,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
private TitleFacet titleFacet;
private IconFacet iconFacet;
+ private NavigableParentFacet navigableParentFacet;
private CssClassFacet cssClassFacet;
private IntrospectionState introspected = IntrospectionState.NOT_INTROSPECTED;
@@ -348,6 +352,7 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
titleFacet = getFacet(TitleFacet.class);
iconFacet = getFacet(IconFacet.class);
+ navigableParentFacet = getFacet(NavigableParentFacet.class);
cssClassFacet = getFacet(CssClassFacet.class);
}
@@ -379,6 +384,13 @@ public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implem
public String getIconName(final ObjectAdapter reference) {
return iconFacet == null ? null : iconFacet.iconName(reference);
}
+
+ @Override
+ public Object getNavigableParent(final Object object) {
+ return navigableParentFacet == null
+ ? null
+ : navigableParentFacet.navigableParent(object);
+ }
@Deprecated
@Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
index 31ae507..8f9471c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChain.java
@@ -19,17 +19,15 @@
package org.apache.isis.core.metamodel.util.pchain;
-import java.lang.reflect.Method;
import java.util.LinkedHashSet;
-import java.util.LinkedList;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Stream;
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
/**
- * Represents a unidirectional linked ordered set of Pojos (chain), where the chain
+ * Represents a unidirectionally linked ordered set of POJOs (chain), where the chain
* starts at startNode. Each subsequent node is linked via de-referencing a
* singular field (or no-arg method) that is annotated with {@code @Parent}.
* <br/>
@@ -41,40 +39,26 @@ import org.apache.isis.core.commons.reflection.Reflect;
*/
public interface ParentChain {
- static ParentChain simple() {
- return new SimpleParentChain();
- }
-
- static ParentChain caching() {
- return new CachingParentChain();
+ public static ParentChain of(Function<Class<?>, ObjectSpecification> specificationLookup){
+ return new ParentChainDefault(specificationLookup);
}
+ /**
+ * Returns the parent node of this {@code node} or {@code null} if {@code node} has no parent.
+ * @param node
+ * @return
+ */
public Object parentOf(Object node);
- static boolean providesParent(Method m) {
- if(!Reflect.isNoArg(m))
- return false;
- if(!Reflect.isPublic(m))
- return false;
- if(Reflect.isVoid(m))
- return false;
- if(Reflect.isPrimitive(m.getReturnType()))
- return false;
-
- if(m.getName().equals("parent"))
- return true;
-
- if(m.isAnnotationPresent(Parent.class))
- return true;
-
- return false;
- }
-
- default Stream<Object> streamParentChainOf(Object startNode){
+ /**
+ * Returns a Stream of nodes that are chained together by parent references.
+ * The startNode is excluded from the Stream.
+ * @param startNode
+ * @return
+ */
+ public default Stream<Object> streamParentChainOf(Object startNode){
final Set<Object> chain = new LinkedHashSet<>();
- chain.add(startNode);
-
Object next = startNode;
while((next = parentOf(next))!=null) {
@@ -83,17 +67,8 @@ public interface ParentChain {
break;
}
- return chain.stream().skip(1);
+ return chain.stream();
}
-
- default Stream<Object> streamReversedParentChainOf(Object startNode){
- final LinkedList<Object> reverseChain = new LinkedList<Object>();
-
- streamParentChainOf(startNode)
- .forEach(reverseChain::addFirst);
-
- return reverseChain.stream();
- }
-
+
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
similarity index 52%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
index 630e928..2374634 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/CachingParentChain.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/ParentChainDefault.java
@@ -19,48 +19,31 @@
package org.apache.isis.core.metamodel.util.pchain;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.reflect.Method;
+import java.util.function.Function;
-class CachingParentChain extends SimpleParentChain {
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
- private final SoftCache<Class<?>, MethodHandle> cache = new SoftCache<>();
+class ParentChainDefault implements ParentChain {
+
+ private final Function<Class<?>, ObjectSpecification> specificationLookup;
+
+ ParentChainDefault(Function<Class<?>, ObjectSpecification> specificationLookup) {
+ this.specificationLookup = specificationLookup;
+ }
@Override
public Object parentOf(Object node) {
if(node==null)
return null;
- final MethodHandle mh = cache.computeIfAbsent(node.getClass(),
- key->{
- try {
- return methodHandleOf(node);
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- return null;
- }
- });
+ final Class<?> cls = node.getClass();
- if(mh==null)
- return null;
+ final ObjectSpecification spec = specificationLookup.apply(cls);
- try {
- return mh.invoke(node);
- } catch (Throwable e) {
- e.printStackTrace();
+ if(spec==null)
return null;
- }
- }
-
- protected static MethodHandle methodHandleOf(Object node) throws IllegalAccessException{
- final Method getter = parentGetterOf(node);
- return getter!=null ? handleOf(getter) : null;
- }
-
- public static MethodHandle handleOf(Method m) throws IllegalAccessException {
- return MethodHandles.publicLookup().unreflect(m);
+ return spec.getNavigableParent(node);
}
}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
deleted file mode 100644
index b30e427..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SimpleParentChain.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.isis.applib.annotation.Parent;
-import org.apache.isis.core.commons.lang.NullSafe;
-import org.apache.isis.core.commons.reflection.Reflect;
-
-class SimpleParentChain implements ParentChain {
-
- @Override
- public Object parentOf(Object node) {
- if(node==null)
- return null;
-
- final Method getter = parentGetterOf(node);
- if(getter==null)
- return null;
-
- try {
- return getter.invoke(node, Reflect.emptyObjects);
- } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
- e.printStackTrace();
- }
-
- return null;
- }
-
- protected static Method parentGetterOf(Object node) {
- return
- NullSafe.stream(Reflect.getAllDeclaredMethods(node.getClass()))
- .filter(ParentChain::providesParent)
- .findFirst()
- .orElse(findGetterForAnnotatedField(node));
- }
-
- protected static Method findGetterForAnnotatedField(Object node) {
- return
- NullSafe.stream(Reflect.getAllDeclaredFields(node.getClass()))
- .filter(f->f.isAnnotationPresent(Parent.class))
- .findFirst()
- .map(f->getterOf(node, f.getName()))
- .orElse(null);
- }
-
- private static Method getterOf(Object bean, String propertyName) {
- try {
- return Reflect.getGetter(bean, propertyName);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
-}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
deleted file mode 100644
index 34c1ff6..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/util/pchain/SoftCache.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.core.metamodel.util.pchain;
-
-import java.lang.ref.SoftReference;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Function;
-import java.util.function.Supplier;
-
-/**
- * Implements a caching {@code Map} where objects are stored referenced by
- * a unique key, while {@codeMap.Entries} might be garbage collected any time.
- *
- * @author ahuber@apache.org
- *
- * @param <K>
- * @param <T>
- */
-class SoftCache<K,T> {
-
- private Map<K,SoftReference<T>> data;
-
- public SoftCache() {
- data=newMap();
- }
-
- public SoftCache(Supplier<Map<K,SoftReference<T>>> mapFactory) {
- data=mapFactory.get();
- }
-
- /**
- * Note: might be overridden to use a different map implementation for storage
- * @return
- */
- protected Map<K,SoftReference<T>> newMap(){
- return new HashMap<>();
- }
-
- /**
- * Note: call to this method will fool the garbage collector,
- * so that last objects in the entry set will be kept longer,
- * due to latest access
- * @return number of currently usable SoftReferences
- */
- public int computeSize(){
- Map<K,SoftReference<T>> keep = newMap();
- for(Map.Entry<K,SoftReference<T>> entry : data.entrySet()){
- if(entry.getValue()!=null) keep.put(entry.getKey(),entry.getValue());
- }
- data.clear();
- data=keep;
- return data.size();
- }
-
- // keep private! (result is not guaranteed to be accurate,
- // since the garbage collector may change the soft references any time)
- @SuppressWarnings("unused")
- private boolean contains(K key){
- return get(key)!=null;
- }
-
- public void put(K key, T x){
- data.put(key, new SoftReference<T>(x));
- }
-
- public T get(K key){
- SoftReference<T> ref = data.get(key);
- if(ref==null) {
- data.remove(key);
- return null;
- }
- return ref.get();
- }
-
- public void clear() {
- data.clear();
- }
-
- /**
- * Tries to fetch a value from cache and returns it if it's a hit.
- * Otherwise stores and returns the value supplied by the mappingFunction.
- * @param key
- * @param mappingFunction
- * @return either the value stored under key or (if there is no such key) the result from the factory
- */
- public T computeIfAbsent(K key, Function<? super K,? extends T> mappingFunction){
- return computeIfAbsent(key,()->mappingFunction.apply(key));
- }
-
- /**
- * Tries to fetch a value from cache and returns it if it's a hit.
- * Otherwise stores and returns the value supplied by the factory.
- * @param key
- * @param factory
- * @return either the value stored under key or (if there is no such key) the result from the factory
- */
- public T computeIfAbsent(K key, Supplier<T> factory) {
- T res = get(key);
- if(res!=null)
- return res;
- res = factory.get();
- put(key,res);
- return res;
- }
-
-}
-
diff --git a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
index b786d42..0552f7c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
+++ b/core/metamodel/src/main/java/org/apache/isis/progmodels/dflt/ProgrammingModelFacetsJava5.java
@@ -79,6 +79,8 @@ import org.apache.isis.core.metamodel.facets.object.ignore.jdo.RemoveJdoPrefixed
import org.apache.isis.core.metamodel.facets.object.immutable.immutableannot.CopyImmutableFacetOntoMembersFactory;
import org.apache.isis.core.metamodel.facets.object.membergroups.annotprop.MemberGroupLayoutFacetFactory;
import org.apache.isis.core.metamodel.facets.object.mixin.MixinFacetForMixinAnnotationFactory;
+import org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentAnnotationFacetFactory;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
import org.apache.isis.core.metamodel.facets.object.objectspecid.classname.ObjectSpecIdFacetDerivedFromClassNameFactory;
import org.apache.isis.core.metamodel.facets.object.objectvalidprops.impl.ObjectValidPropertiesFacetImplFactory;
import org.apache.isis.core.metamodel.facets.object.parseable.annotcfg.ParseableFacetAnnotationElseConfigurationFactory;
@@ -319,6 +321,8 @@ public final class ProgrammingModelFacetsJava5 extends ProgrammingModelAbstract
addFactory(new TitleAnnotationFacetFactory());
addFactory(new TitleFacetViaMethodsFactory());
addFactory(new IconFacetMethodFactory());
+ addFactory(new NavigableParentAnnotationFacetFactory());
+ addFactory(new NavigableParentFacetMethodFactory());
addFactory(new CssClassFacetMethodFactory());
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
new file mode 100644
index 0000000..b962bbc
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodFactoryTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryTest;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethodFactory;
+
+public class NavigableParentFacetMethodFactoryTest extends AbstractFacetFactoryTest {
+
+ private NavigableParentFacetMethodFactory facetFactory;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ facetFactory = new NavigableParentFacetMethodFactory();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ facetFactory = null;
+ super.tearDown();
+ }
+
+ public void testNavigableParentMethodPickedUpOnClassAndMethodRemoved() {
+ class Customer {
+ @SuppressWarnings("unused")
+ public Object parent() {
+ return null;
+ }
+ }
+ final Method navigableParentMethod = findMethod(Customer.class, "parent");
+
+ facetFactory.process(new ProcessClassContext(Customer.class, methodRemover, facetedMethod));
+
+ final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+ assertThat(facet, is(notNullValue()));
+ assertThat(facet, is(instanceOf(NavigableParentFacetMethod.class)));
+
+ assertTrue(methodRemover.getRemovedMethodMethodCalls().contains(navigableParentMethod));
+ }
+
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
new file mode 100644
index 0000000..70a1dce
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.object.navparent;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentFacetMethodTest {
+
+ private final Mockery mockery = new JUnit4Mockery();
+
+ private NavigableParentFacetMethod facet;
+ private FacetHolder mockFacetHolder;
+
+ private ObjectAdapter mockOwningAdapter;
+
+ private DomainObjectWithProblemInNavigableParentMethod pojo;
+
+ public static class DomainObjectWithProblemInNavigableParentMethod {
+ public String parent() {
+ throw new NullPointerException();
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+
+ pojo = new DomainObjectWithProblemInNavigableParentMethod();
+ mockFacetHolder = mockery.mock(FacetHolder.class);
+ mockOwningAdapter = mockery.mock(ObjectAdapter.class);
+ final Method navigableParentMethod = DomainObjectWithProblemInNavigableParentMethod.class.getMethod("parent");
+ facet = new NavigableParentFacetMethod(navigableParentMethod, mockFacetHolder);
+
+ mockery.checking(new Expectations() {
+ {
+ allowing(mockOwningAdapter).getObject();
+ will(returnValue(pojo));
+ }
+ });
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ facet = null;
+ }
+
+ @Test
+ public void testNavigableParentThrowsException() {
+ final Object parent = facet.navigableParent(mockOwningAdapter.getObject());
+ assertThat(parent, is(nullValue()));
+ }
+
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
new file mode 100644
index 0000000..fff951c
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import java.lang.reflect.Method;
+
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+import org.apache.isis.core.commons.reflection.Reflect;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategory;
+import org.apache.isis.core.metamodel.deployment.DeploymentCategoryProvider;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
+import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
+import org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectA;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.jmock.Expectations;
+import org.jmock.auto.Mock;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NavigableParentAnnotationFacetFactoryTest extends AbstractFacetFactoryJUnit4TestCase {
+
+ private NavigableParentAnnotationFacetFactory facetFactory;
+
+ @Mock
+ private ObjectAdapter mockObjectAdapter;
+ @Mock
+ private AuthenticationSession mockAuthenticationSession;
+
+ @Before
+ public void setUp() throws Exception {
+
+ context.allowing(mockSpecificationLoader);
+
+ facetFactory = new NavigableParentAnnotationFacetFactory();
+ facetFactory.setServicesInjector(mockServicesInjector);
+
+ context.checking(new Expectations() {
+ {
+ allowing(mockServicesInjector).lookupService(AuthenticationSessionProvider.class);
+ will(returnValue(mockAuthenticationSessionProvider));
+
+ allowing(mockServicesInjector).lookupService(DeploymentCategoryProvider.class);
+ will(returnValue(mockDeploymentCategoryProvider));
+
+ allowing(mockDeploymentCategoryProvider).getDeploymentCategory();
+ will(returnValue(DeploymentCategory.PRODUCTION));
+
+ allowing(mockAuthenticationSessionProvider).getAuthenticationSession();
+ will(returnValue(mockAuthenticationSession));
+
+ allowing(mockServicesInjector).getSpecificationLoader();
+ will(returnValue(mockSpecificationLoader));
+
+ allowing(mockServicesInjector).getPersistenceSessionServiceInternal();
+ will(returnValue(mockPersistenceSessionServiceInternal));
+ }
+ });
+
+ facetFactory.setServicesInjector(mockServicesInjector);
+
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ facetFactory = null;
+ super.tearDown();
+ }
+
+ @Test
+ public void testParentAnnotatedMethod() throws Exception {
+ testParentMethod(new DomainObjectA(), "root");
+ }
+
+ // -- HELPER
+
+ private void testParentMethod(Object domainObject, String parentMethodName) throws Exception {
+
+ final Class<?> domainClass = domainObject.getClass();
+
+ facetFactory.process(new ProcessClassContext(domainClass, mockMethodRemover, facetedMethod));
+
+ final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
+ Assert.assertNotNull(facet);
+ Assert.assertTrue(facet instanceof NavigableParentFacetMethod);
+
+ final NavigableParentFacetMethod navigableParentFacetMethod = (NavigableParentFacetMethod) facet;
+ final Method parentMethod = domainClass.getMethod(parentMethodName);
+
+ Assert.assertEquals(
+ parentMethod.invoke(domainObject, Reflect.emptyObjects),
+ navigableParentFacetMethod.navigableParent(domainObject) );
+
+ }
+
+
+
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
new file mode 100644
index 0000000..4326b72
--- /dev/null
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentTestSamples.java
@@ -0,0 +1,49 @@
+package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
+
+import org.apache.isis.applib.annotation.Parent;
+
+class NavigableParentTestSamples {
+
+ // has no navigable parent
+ protected static class DomainObjectRoot {
+
+ @Override
+ public String toString() {
+ return "Root";
+ }
+
+ }
+
+ // has navigable parent 'Root' specified via Annotation
+ protected static class DomainObjectA {
+
+ private final static Object myParent = new DomainObjectRoot();
+
+ @Override
+ public String toString() {
+ return "A";
+ }
+
+ @Parent
+ public Object root() {
+ return myParent;
+ }
+
+ }
+
+ // has navigable parent 'A' specified via method
+ protected static class DomainObjectB {
+
+ private final static Object myParent = new DomainObjectA();
+
+ @Override
+ public String toString() {
+ return "B";
+ }
+
+ public Object parent() {
+ return myParent;
+ }
+
+ }
+}
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
index dc767b3..81481c5 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/testspec/ObjectSpecificationStub.java
@@ -175,6 +175,11 @@ public class ObjectSpecificationStub extends FacetHolderImpl implements ObjectSp
return null;
}
+ @Override
+ public Object getNavigableParent(Object object) {
+ return null;
+ }
+
@Override
public String getCssClass() {
return null;
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
index fdc11f2..7d4a8ce 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModel.java
@@ -26,7 +26,7 @@ import org.apache.isis.viewer.wicket.model.models.EntityModel;
/**
* Represents a navigable chain of parent nodes starting at the current node.
*
- * @author a.huber@corax.at
+ * @author ahuber@apache.org
*
* @since 2.0.0
*
diff --git a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
index 955c2db..042595f 100644
--- a/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
+++ b/core/viewer-wicket-model/src/main/java/org/apache/isis/viewer/wicket/model/models/whereami/WhereAmIModelDefault.java
@@ -19,26 +19,28 @@
package org.apache.isis.viewer.wicket.model.models.whereami;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.LinkedList;
import java.util.stream.Stream;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.util.pchain.ParentChain;
+import org.apache.isis.core.runtime.system.context.IsisContext;
import org.apache.isis.viewer.wicket.model.models.EntityModel;
class WhereAmIModelDefault implements WhereAmIModel {
- private final List<Object> reversedChainOfParents = new ArrayList<>();
+ private final LinkedList<Object> reversedChainOfParents = new LinkedList<>();
private final EntityModel startOfChain;
public WhereAmIModelDefault(EntityModel startOfChain) {
this.startOfChain = startOfChain;
- final Object startPojo = startOfChain.getObject().getObject();
-
- ParentChain.caching()
- .streamReversedParentChainOf(startPojo)
- .forEach(reversedChainOfParents::add);
+ final ObjectAdapter adapter = startOfChain.getObject();
+ final Object startNode = adapter.getObject();
+
+ ParentChain.of(IsisContext.getSessionFactory().getSpecificationLoader()::loadSpecification)
+ .streamParentChainOf(startNode)
+ .forEach(reversedChainOfParents::addFirst);
}
@Override
--
To stop receiving notification emails like this one, please contact
['"commits@isis.apache.org" <co...@isis.apache.org>'].