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 2019/01/08 07:26:00 UTC

[isis] 10/10: ISIS-2081: updates @ActionLayout and XML to allow redirect policy to be specified on a case-by-case basis.

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 ee80c6154131750dbda2ea90d32ea368eccfe7a0
Author: danhaywood <da...@haywood-associates.co.uk>
AuthorDate: Tue Jan 8 06:59:10 2019 +0000

    ISIS-2081: updates @ActionLayout and XML to allow redirect policy to be specified on a case-by-case basis.
---
 .../asciidoc/applib/layout/component/component.xsd | 10 ++++
 .../isis/applib/annotation/ActionLayout.java       |  8 +++
 .../apache/isis/applib/annotation/Redirect.java    | 49 ++++++++++++++++
 .../applib/layout/component/ActionLayoutData.java  | 14 +++++
 .../actions/layout/ActionLayoutFacetFactory.java   | 10 ++++
 .../RedirectFacetFromActionLayoutAnnotation.java   | 43 ++++++++++++++
 .../actions/layout/RedirectFacetFromActionXml.java | 43 ++++++++++++++
 .../facets/actions/redirect/RedirectFacet.java     | 33 +++++++++++
 .../actions/redirect/RedirectFacetAbstract.java    | 66 ++++++++++++++++++++++
 .../actions/redirect/RedirectFacetFallback.java    | 31 ++++++++++
 .../services/grid/GridSystemServiceAbstract.java   |  2 +
 .../wicket/ui/panels/FormExecutorDefault.java      | 50 +++++++++++++---
 12 files changed, 350 insertions(+), 9 deletions(-)

diff --git a/adocs/documentation/src/main/asciidoc/applib/layout/component/component.xsd b/adocs/documentation/src/main/asciidoc/applib/layout/component/component.xsd
index bb76e01..d4439b0 100644
--- a/adocs/documentation/src/main/asciidoc/applib/layout/component/component.xsd
+++ b/adocs/documentation/src/main/asciidoc/applib/layout/component/component.xsd
@@ -53,6 +53,7 @@
     <xs:attribute name="namedEscaped" type="xs:boolean"/>
     <xs:attribute name="position" type="tns:position"/>
     <xs:attribute name="promptStyle" type="tns:promptStyle"/>
+    <xs:attribute name="redirect" type="tns:redirect"/>
   </xs:complexType>
 
   <xs:complexType name="serviceAction">
@@ -119,6 +120,7 @@
     <xs:attribute name="paged" type="xs:int"/>
   </xs:complexType>
 
+
   <xs:simpleType name="bookmarkPolicy">
     <xs:restriction base="xs:string">
       <xs:enumeration value="AS_ROOT"/>
@@ -169,6 +171,14 @@
     </xs:restriction>
   </xs:simpleType>
 
+  <xs:simpleType name="redirect">
+    <xs:restriction base="xs:string">
+      <xs:enumeration value="AS_CONFIGURED"/>
+      <xs:enumeration value="EVEN_IF_SAME"/>
+      <xs:enumeration value="ONLY_IF_DIFFERS"/>
+    </xs:restriction>
+  </xs:simpleType>
+
   <xs:simpleType name="labelPosition">
     <xs:restriction base="xs:string">
       <xs:enumeration value="DEFAULT"/>
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/ActionLayout.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/ActionLayout.java
index 3c91af5..6e14e62 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/annotation/ActionLayout.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/ActionLayout.java
@@ -133,6 +133,14 @@ public @interface ActionLayout {
     // //////////////////////////////////////
 
     /**
+     * If the action returns its target, then determines whether to update the page or
+     * instead to redirect (forcing a re-rendering of a new page).
+     */
+    Redirect redirectPolicy() default Redirect.AS_CONFIGURED;
+
+    // //////////////////////////////////////
+
+    /**
      * For actions of domain services that can be viewed and contributed (that is, whose
      * {@link DomainService#nature() nature} is either {@link org.apache.isis.applib.annotation.NatureOfService#VIEW}
      * or {@link org.apache.isis.applib.annotation.NatureOfService#VIEW_CONTRIBUTIONS_ONLY}), specifies how the
diff --git a/core/applib/src/main/java/org/apache/isis/applib/annotation/Redirect.java b/core/applib/src/main/java/org/apache/isis/applib/annotation/Redirect.java
new file mode 100644
index 0000000..da75e16
--- /dev/null
+++ b/core/applib/src/main/java/org/apache/isis/applib/annotation/Redirect.java
@@ -0,0 +1,49 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.applib.annotation;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * The available policies for rendering the next page if the result is the same as the target
+ * (in other words, an action that returns "this").
+ */
+@XmlType(
+        namespace = "http://isis.apache.org/applib/layout/component"
+)
+public enum Redirect {
+    /**
+     * As defined by configuration property <code>isis.viewer.wicket.redirectEvenIfSameObject</code>
+     */
+    AS_CONFIGURED,
+
+    /**
+     * Redirect (meaning render a new page) even if the result of the action is the same as the target.
+     */
+    EVEN_IF_SAME,
+
+    /**
+     * Don't redirect if the result is the same as the target, instead just update the existing page.
+     *
+     * <p>
+     *     Of course, a redirect is still performed if the result of the action is different from the target.
+     * </p>
+     */
+    ONLY_IF_DIFFERS,
+}
diff --git a/core/applib/src/main/java/org/apache/isis/applib/layout/component/ActionLayoutData.java b/core/applib/src/main/java/org/apache/isis/applib/layout/component/ActionLayoutData.java
index e7b6e8d..0dd1e68 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/layout/component/ActionLayoutData.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/layout/component/ActionLayoutData.java
@@ -28,6 +28,7 @@ import javax.xml.bind.annotation.XmlType;
 
 import org.apache.isis.applib.annotation.BookmarkPolicy;
 import org.apache.isis.applib.annotation.PromptStyle;
+import org.apache.isis.applib.annotation.Redirect;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.layout.links.Link;
 
@@ -224,6 +225,19 @@ public class ActionLayoutData implements Serializable, Owned<ActionLayoutDataOwn
 
 
 
+    private Redirect redirect;
+
+    @XmlAttribute(required = false)
+    public Redirect getRedirect() {
+        return redirect;
+    }
+
+    public void setRedirect(Redirect redirect) {
+        this.redirect = redirect;
+    }
+
+
+
     private ActionLayoutDataOwner owner;
     /**
      * Owner.
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
index b836e74..14d9293 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/ActionLayoutFacetFactory.java
@@ -31,6 +31,8 @@ import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.actions.notcontributed.NotContributedFacet;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacetFallback;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacet;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacetFallback;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
@@ -130,6 +132,14 @@ public class ActionLayoutFacetFactory extends FacetFactoryAbstract implements Co
         FacetUtil.addFacet(actionPositionFacet);
 
 
+        // redirectPolicy
+        RedirectFacet redirectFacet = RedirectFacetFromActionLayoutAnnotation.create(actionLayout, holder);
+        if(redirectFacet == null) {
+            redirectFacet = new RedirectFacetFallback(holder);
+        }
+        FacetUtil.addFacet(redirectFacet);
+
+
         // contributing
         if (isContributingServiceOrMixinObject(processMethodContext)) {
             NotContributedFacet notContributedFacet = NotContributedFacetForLayoutProperties.create(properties, holder);
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionLayoutAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionLayoutAnnotation.java
new file mode 100644
index 0000000..65fb7e8
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionLayoutAnnotation.java
@@ -0,0 +1,43 @@
+/*
+ *  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.actions.layout;
+
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Redirect;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacet;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacetAbstract;
+
+public class RedirectFacetFromActionLayoutAnnotation extends RedirectFacetAbstract {
+
+    public static RedirectFacet create(final ActionLayout actionLayout, final FacetHolder holder) {
+        if(actionLayout == null) {
+            return null;
+        }
+        final Redirect redirect = actionLayout.redirectPolicy();
+        return redirect != null ? new RedirectFacetFromActionLayoutAnnotation(redirect, holder) : null;
+    }
+
+    public RedirectFacetFromActionLayoutAnnotation(
+            final Redirect policy, final FacetHolder holder) {
+        super(policy, holder);
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionXml.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionXml.java
new file mode 100644
index 0000000..6be2bfc
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/layout/RedirectFacetFromActionXml.java
@@ -0,0 +1,43 @@
+/*
+ *  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.actions.layout;
+
+import org.apache.isis.applib.annotation.Redirect;
+import org.apache.isis.applib.layout.component.ActionLayoutData;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacet;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacetAbstract;
+
+public class RedirectFacetFromActionXml extends RedirectFacetAbstract {
+
+    public static RedirectFacet create(final ActionLayoutData actionLayout, final FacetHolder holder) {
+        if(actionLayout == null) {
+            return null;
+        }
+        final Redirect redirect = actionLayout.getRedirect();
+        return redirect != null ? new RedirectFacetFromActionXml(redirect, holder) : null;
+    }
+
+    public RedirectFacetFromActionXml(
+            final Redirect policy, final FacetHolder holder) {
+        super(policy, holder);
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacet.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacet.java
new file mode 100644
index 0000000..4a47165
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacet.java
@@ -0,0 +1,33 @@
+/*
+ *  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.actions.redirect;
+
+import org.apache.isis.applib.annotation.Redirect;
+import org.apache.isis.core.metamodel.facetapi.Facet;
+
+/**
+ * Whether to force a redirect when an action returns the same object.
+ */
+public interface RedirectFacet extends Facet {
+
+
+    Redirect policy();
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetAbstract.java
new file mode 100644
index 0000000..60805f4
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetAbstract.java
@@ -0,0 +1,66 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.core.metamodel.facets.actions.redirect;
+
+import java.util.Map;
+
+import org.apache.isis.applib.annotation.Redirect;
+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 RedirectFacetAbstract extends FacetAbstract implements RedirectFacet {
+
+    public static Class<? extends Facet> type() {
+        return RedirectFacet.class;
+    }
+
+    private final Redirect redirect;
+
+    protected RedirectFacetAbstract(
+            final Redirect redirect,
+            final FacetHolder holder) {
+        this(redirect, holder, Derivation.NOT_DERIVED);
+    }
+
+    protected RedirectFacetAbstract(
+            final Redirect redirect,
+            final FacetHolder holder,
+            final Derivation derivation) {
+        super(type(), holder, derivation);
+        this.redirect = redirect;
+    }
+
+    @Override
+    public Redirect policy() {
+        return redirect;
+    }
+
+    @Override
+    protected String toStringValues() {
+        return "redirect=" + redirect;
+    }
+
+    @Override public void appendAttributesTo(final Map<String, Object> attributeMap) {
+        super.appendAttributesTo(attributeMap);
+        attributeMap.put("redirect", redirect);
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetFallback.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetFallback.java
new file mode 100644
index 0000000..fbd4e82
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/actions/redirect/RedirectFacetFallback.java
@@ -0,0 +1,31 @@
+/*
+ *  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.actions.redirect;
+
+import org.apache.isis.applib.annotation.Redirect;
+import org.apache.isis.core.metamodel.facetapi.FacetHolder;
+
+public class RedirectFacetFallback extends RedirectFacetAbstract {
+
+    public RedirectFacetFallback(final FacetHolder holder) {
+        super(Redirect.AS_CONFIGURED, holder);
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
index 59b90f6..dce4e8b 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/grid/GridSystemServiceAbstract.java
@@ -64,6 +64,7 @@ import org.apache.isis.core.metamodel.facets.actions.layout.DescribedAsFacetForA
 import org.apache.isis.core.metamodel.facets.actions.layout.HiddenFacetForActionXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.NamedFacetForActionXml;
 import org.apache.isis.core.metamodel.facets.actions.layout.PromptStyleFacetForActionXml;
+import org.apache.isis.core.metamodel.facets.actions.layout.RedirectFacetFromActionXml;
 import org.apache.isis.core.metamodel.facets.actions.position.ActionPositionFacet;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
@@ -298,6 +299,7 @@ public abstract class GridSystemServiceAbstract<G extends org.apache.isis.applib
                 FacetUtil.addOrReplaceFacet(HiddenFacetForActionXml.create(actionLayoutData, objectAction));
                 FacetUtil.addOrReplaceFacet(NamedFacetForActionXml.create(actionLayoutData, objectAction));
                 FacetUtil.addOrReplaceFacet(PromptStyleFacetForActionXml.create(actionLayoutData, objectAction));
+                FacetUtil.addOrReplaceFacet(RedirectFacetFromActionXml.create(actionLayoutData, objectAction));
             }
 
             @Override
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
index aa1ca99..6ccd458 100644
--- a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/panels/FormExecutorDefault.java
@@ -48,6 +48,7 @@ import org.apache.isis.core.commons.authentication.MessageBroker;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
 import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
+import org.apache.isis.core.metamodel.facets.actions.redirect.RedirectFacet;
 import org.apache.isis.core.metamodel.facets.properties.renderunchanged.UnchangingFacet;
 import org.apache.isis.core.metamodel.services.ServicesInjector;
 import org.apache.isis.core.metamodel.specloader.SpecificationLoader;
@@ -58,6 +59,7 @@ import org.apache.isis.core.runtime.system.session.IsisSessionFactory;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
 import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.ActionModel;
 import org.apache.isis.viewer.wicket.model.models.BookmarkableModel;
 import org.apache.isis.viewer.wicket.model.models.EntityModel;
 import org.apache.isis.viewer.wicket.model.models.FormExecutor;
@@ -161,7 +163,14 @@ public final class FormExecutorDefault<M extends BookmarkableModel<ObjectAdapter
             // hook to close prompt etc.
             onExecuteAndProcessResults(targetIfAny);
 
-            if (resultDiffersOrAlwaysRedirect(targetAdapter, resultAdapter) ||
+            final M model = this.model;
+            RedirectFacet redirectFacet = null;
+            if(model instanceof ActionModel) {
+                final ActionModel actionModel = (ActionModel) model;
+                redirectFacet = actionModel.getActionMemento().getAction(getSpecificationLoader()).getFacet(RedirectFacet.class);
+            }
+
+            if (shouldRedirect(targetAdapter, resultAdapter, redirectFacet) ||
                 hasBlobsOrClobs(page)                                       ||
                 targetIfAny == null                                             ) {
 
@@ -239,32 +248,55 @@ public final class FormExecutorDefault<M extends BookmarkableModel<ObjectAdapter
         }
     }
 
-    private boolean resultDiffersOrAlwaysRedirect(
+    private boolean shouldRedirect(
+            final ObjectAdapter targetAdapter,
+            final ObjectAdapter resultAdapter,
+            final RedirectFacet redirectFacet) {
+
+        switch (redirectFacet.policy()) {
+
+        case EVEN_IF_SAME:
+        default:
+            return true;
+
+        case AS_CONFIGURED:
+            final boolean redirectEvenIfSameObject = getSettings().isRedirectEvenIfSameObject();
+            if (redirectEvenIfSameObject) {
+                return true;
+            }
+            // fall through to...
+
+        case ONLY_IF_DIFFERS:
+            return differs(targetAdapter, resultAdapter);
+        }
+    }
+
+    private static boolean differs(
             final ObjectAdapter targetAdapter,
             final ObjectAdapter resultAdapter) {
+
         final ObjectAdapterMemento targetOam = ObjectAdapterMemento.createOrNull(targetAdapter);
         final ObjectAdapterMemento resultOam = ObjectAdapterMemento.createOrNull(resultAdapter);
 
-        return resultDiffersOrAlwaysRedirect(targetOam, resultOam);
+        return differs(targetOam, resultOam);
     }
 
-    private boolean resultDiffersOrAlwaysRedirect(
+    private static boolean differs(
             final ObjectAdapterMemento targetOam,
             final ObjectAdapterMemento resultOam) {
 
         final Bookmark resultBookmark = resultOam != null ? resultOam.asHintingBookmark() : null;
         final Bookmark targetBookmark = targetOam != null ? targetOam.asHintingBookmark() : null;
 
-        return resultDiffersOrAlwaysRedirect(targetBookmark, resultBookmark);
+        return differs(targetBookmark, resultBookmark);
     }
 
-    private boolean resultDiffersOrAlwaysRedirect(
+    private static boolean differs(
             final Bookmark targetBookmark,
             final Bookmark resultBookmark) {
-        final boolean redirectEvenIfSameObject = getSettings().isRedirectEvenIfSameObject();
 
         if(resultBookmark == null && targetBookmark == null) {
-            return redirectEvenIfSameObject;
+            return true;
         }
         if (resultBookmark == null || targetBookmark == null) {
             return true;
@@ -272,7 +304,7 @@ public final class FormExecutorDefault<M extends BookmarkableModel<ObjectAdapter
         final String resultBookmarkStr = asStr(resultBookmark);
         final String targetBookmarkStr = asStr(targetBookmark);
 
-        return !Objects.equals(resultBookmarkStr, targetBookmarkStr)  || redirectEvenIfSameObject;
+        return !Objects.equals(resultBookmarkStr, targetBookmarkStr);
     }
 
     private boolean hasBlobsOrClobs(final Page page) {