myfaces-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From skitch...@apache.org
Subject svn commit: r691228 - /myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java
Date Tue, 02 Sep 2008 12:58:41 GMT
Author: skitching
Date: Tue Sep  2 05:58:41 2008
New Revision: 691228

URL: http://svn.apache.org/viewvc?rev=691228&view=rev
Log:
Only clear access-scoped conversations after first render of new view.
This hopefully fixes problems with AJAX requests.
Also hopefully fixes problems after validation failure.

Modified:
    myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java

Modified: myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java
URL: http://svn.apache.org/viewvc/myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java?rev=691228&r1=691227&r2=691228&view=diff
==============================================================================
--- myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java
(original)
+++ myfaces/orchestra/trunk/core/src/main/java/org/apache/myfaces/orchestra/conversation/jsf/AccessScopePhaseListener.java
Tue Sep  2 05:58:41 2008
@@ -22,6 +22,7 @@
 import java.util.Iterator;
 import java.util.Set;
 
+import javax.faces.component.UIViewRoot;
 import javax.faces.event.PhaseEvent;
 import javax.faces.event.PhaseId;
 import javax.faces.event.PhaseListener;
@@ -36,16 +37,31 @@
 /**
  * Handle access-scoped conversations.
  * <p>
- * At the end of request processing, delete any access-scope conversations for which no
- * bean in that scope has been accessed during the <i>render phase</i> of the
request.
- * Therefore if a postback occurs which accesses a specific conversation A, and that
- * conversation then forwards to a different view whose render phase does not refer
- * to any bean in conversation A, then the conversation is discarded. This ensures
- * that conversations are discarded as soon as possible, and in particular that if
- * control returns to the a view that accesses conversation A on the next cycle then
- * a new conversation instance is created.
+ * After a <i>new view</i> has been rendered, delete any access-scope conversations
for which no
+ * bean in that scope has been accessed <i>during the render phase</i> of the
request.
  * <p>
- * If a view happens to want its postbacks handled by a bean in conversation A,
+ * This allows a page which handles a postback to store data into beans in an access-scoped
+ * conversation, then navigate to a new page. That information is available for the new
+ * page during its rendering. And if that data is referenced, it will remain around
+ * until the user does a GET request, or a postback that causes navigation again. Then
+ * following the rendering of that new target page, any access-scoped conversations will
be
+ * discarded except for those that the new target page references.
+ * <p>
+ * Any access-scoped conversations that a page was using, but which the new page does NOT
use
+ * are therefore automatically cleaned up at the earliest possibility - after rendering of
the
+ * new page has completed.
+ * <p>
+ * Note: When a "master" and "detail" page pair exist, that navigating master->detail->master->detail
+ * correctly uses a fresh conversation for the second call to the detail page (and not reuse
the
+ * access-scoped data from the first call). By only counting accesses during the render phase,
this
+ * works correctly.
+ * <p>
+ * Note: Access-scoped conversations must be preserved when AJAX calls cause only
+ * part of a page to be processed, and must be preserved when conversion/validation failure
+ * cause reads of the values of input components to be skipped. By deleting unaccessed
+ * conversations only after the <i>first</i> render, this happens automatically.
+ * <p>
+ * Note: If a view happens to want its postbacks handled by a bean in conversation A,
  * but the render phase never references anything in that conversation, then the
  * conversation will be effectively request-scoped. This is not expected to be a
  * problem in practice as it would be a pretty odd view that has stateful event
@@ -56,8 +72,8 @@
  * to false, ie not rendered, but does force a method-invocation on the backingBean
  * instance). Alternatively, a manual-scoped conversation can be used.
  * <p>
- * Note that if FacesContext.responseComplete is called during processing, then
- * no phase-listeners for the RENDER_RESPONSE phase are executed. And any navigation
+ * Note: If FacesContext.responseComplete is called during processing of a postback,
+ * then no phase-listeners for the RENDER_RESPONSE phase are executed. And any navigation
  * rule that specifies "redirect" causes responseComplete to be invoked. Therefore
  * access-scoped beans are not cleaned up immediately. However the view being
  * redirected to always runs its "render" phase only, no postback. The effect, 
@@ -65,14 +81,21 @@
  * the same view: in both cases, the access-scoped beans are kept if the next view
  * refers to them, and discarded otherwise. 
  * <p>
- * Note also that if responseComplete is invoked by another PhaseListener in the
- * beforePhase for RENDER_RESPONSE then the beforePhase for all other PhaseListeners
- * is still invoked, but no afterPhase methods are invoked. This means that this
- * phase listener will not discard any access-scoped conversations, as that is
- * only done in the afterPhase method. In particular, this applies to tomahawk
- * PPR requests, where the PPRPhaseListener uses responseComplete(). This behaviour
- * is exactly what is wanted; partial-page-rendering should not cause access-scoped
- * conversations to terminate.
+ * Note: Some AJAX libraries effectively do their own "rendering" pass from within
+ * a custom PhaseListener, during the beforePhase for RENDER_RESPONSE. This could
+ * have undesirable effects on Orchestra - except that for all AJAX requests, the
+ * viewRoot restored during RESTORE_VIEW will be the same viewRoot used during
+ * render phase - so this PhaseListener will ignore the request anyway.
+ * <p>
+ * Backwards-compatibility note: The behaviour of this class has changed between
+ * releases 1.2 and 1.3. In earlier releases, the access-scope checking ran on every
+ * request (not just GET or navigation). Suppose a bean is in its own access-scoped
+ * conversation, and the only reference to that bean is from a component that is
+ * rendered or not depending upon a checkbox editable by the user. In the old version,
+ * hiding the component would cause the access-scoped conversation to be discarded
+ * (not accessed), while the current code will not discard it. The new behaviour does
+ * fix a couple of bugs: access-scoped conversations discarded during AJAX requests
+ * and after conversion/validation failure.
  * 
  * @since 1.1
  */
@@ -81,47 +104,75 @@
     private static final long serialVersionUID = 1L;
     private final Log log = LogFactory.getLog(AccessScopePhaseListener.class);
 
+    private static final String OLD_VIEW_KEY = AccessScopePhaseListener.class.getName() +
":oldView";
+
     public PhaseId getPhaseId()
     {
-        return PhaseId.RENDER_RESPONSE;
+        return PhaseId.ANY_PHASE;
     }
 
     public void beforePhase(PhaseEvent event)
     {
-        AccessScopeManager accessManager = AccessScopeManager.getInstance();
-        
-        if (event.getFacesContext().getResponseComplete())
+        PhaseId pid = event.getPhaseId();
+        if (pid == PhaseId.RENDER_RESPONSE)
         {
-            // Unusual case. If something set responseComplete before the rendering phase
started, then
-            // the rendering phase would simply be skipped and we would not be here. So presumably
some
-            // PhaseListener that ran in this phase but before this PhaseListener has decided
to implement
-            // its own form of "rendering" itself and then set responseComplete to bypass
the normal
-            // rendering phase.
-            //
-            // When this occurs, any accesses to conversation-scoped beans will already have
happened.
-            // If we were to call "beginRecording" here as normal then then at afterPhase
all conversations
-            // would appear to be unaccessed which is not true - they may have been accessed,
but before
-            // this method ran. So instead, just disable checking for this particular request.
This
-            // solution isn't 100% correct, but should be close enough, and no other alternative
is
-            // simple. The correct solution would possibly be to use a custom Lifecycle class
to do the
-            // access-checking rather than a PhaseListener; that would then avoid any dependency
on the
-            // order in which PhaseListener classes are registered. Note, however, that some
ajax
-            // libraries use a custom Lifecycle class themselves, so again there are potential
-            // registration ordering issues there.
-            //
-            // This situation happens with the NetAdvantage ajax library at least, and perhaps
others.
-            accessManager.setIgnoreRequest();
+            beforeRenderPhase(event);
+        }
+    }
+
+    public void afterPhase(PhaseEvent event)
+    {
+        PhaseId pid = event.getPhaseId();
+        if (pid == PhaseId.RESTORE_VIEW)
+        {
+            afterRestoreViewPhase(event);
         }
-        else
+        else if (pid == PhaseId.RENDER_RESPONSE)
         {
-            // normal case
-            accessManager.beginRecording();
+            afterRenderPhase(event);
         }
     }
 
-    public void afterPhase(PhaseEvent event)
+    public void afterRestoreViewPhase(PhaseEvent event)
     {
-        invalidateAccessScopedConversations(event.getFacesContext().getViewRoot().getViewId());
+        javax.faces.context.FacesContext fc = event.getFacesContext();
+        UIViewRoot oldViewRoot = fc.getViewRoot();
+        fc.getExternalContext().getRequestMap().put(OLD_VIEW_KEY, oldViewRoot);
+    }
+
+    public void beforeRenderPhase(PhaseEvent event)
+    {
+        AccessScopeManager accessManager = AccessScopeManager.getInstance();
+        accessManager.beginRecording();
+    }
+
+    public void afterRenderPhase(PhaseEvent event)
+    {
+        javax.faces.context.FacesContext fc = event.getFacesContext();
+        UIViewRoot viewRoot = fc.getViewRoot();
+        UIViewRoot oldViewRoot = (UIViewRoot) fc.getExternalContext().getRequestMap().get(OLD_VIEW_KEY);
+        if (viewRoot != oldViewRoot)
+        {
+            // Either this is a GET request (oldViewRoot is null) or this is a postback which
+            // triggered a navigation (oldViewRoot is not null, but is a different instance).
+            // In these cases (and only in these cases) we want to discard unaccessed conversations
at
+            // the end of the render phase.
+            //
+            // There are reasons why it is not a good idea to run the invalidation check
+            // on every request:
+            // (a) it doesn't work well with AJAX requests; an ajax request that only accesses
+            //    part of the page should not cause access-scoped conversations to be discarded.
+            // (b) on conversion or validation failure, conversations that are only referenced
+            //    via the "value" attribute of an input component will not be accessed because
+            //    the "submittedValue" for the component is used rather than fetching the
value
+            //    from the backing bean.
+            // (c) running each time is somewhat inefficient
+            //
+            // Note that this means that an access-scoped conversation will continue to live
+            // even when the components that reference it are not rendered, ie it was not
+            // technically "accessed" during a request.
+            invalidateAccessScopedConversations(event.getFacesContext().getViewRoot().getViewId());
+        }
     }
 
     /**



Mime
View raw message