tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hls...@apache.org
Subject svn commit: r686612 - in /tapestry/tapestry5/trunk: src/site/apt/ src/site/apt/cookbook/ src/site/apt/guide/ tapestry-core/src/main/java/org/apache/tapestry5/ tapestry-core/src/main/java/org/apache/tapestry5/annotations/ tapestry-core/src/main/java/org...
Date Sun, 17 Aug 2008 13:56:36 GMT
Author: hlship
Date: Sun Aug 17 06:56:35 2008
New Revision: 686612

URL: http://svn.apache.org/viewvc?rev=686612&view=rev
Log:
TAPESTRY-2595: Application State Objects are not persisted back to the session at the end of the request

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedApplicationStateObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedApplicationStateObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestEvent.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestListener.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHub.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImplTest.java
Modified:
    tapestry/tapestry5/trunk/src/site/apt/cookbook/defaultparameter.apt
    tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt
    tapestry/tapestry5/trunk/src/site/apt/cookbook/index.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/appstate.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/beaneditform.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/pagenav.apt
    tapestry/tapestry5/trunk/src/site/apt/guide/reload.apt
    tapestry/tapestry5/trunk/src/site/apt/index.apt
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/ApplicationState.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategy.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategyTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/IOCTestCase.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/TestBase.java

Modified: tapestry/tapestry5/trunk/src/site/apt/cookbook/defaultparameter.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/cookbook/defaultparameter.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/cookbook/defaultparameter.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/cookbook/defaultparameter.apt Sun Aug 17 06:56:35 2008
@@ -22,6 +22,7 @@
 ---
 public class OutputGadget
 {
+  @Property
   @Parameter(required=true, principal=true)
   private Gadget gadget;
 
@@ -50,13 +51,18 @@
   parameter is principal, so that the validate and translate parameters can computer
   defaults, based on the
   type and annotations bound to the value parameter.
-  
-  Using the autoconnect attribute you can also let Tapestry generate the defaultGadget() method for you. In this case 
+
+* autoconnect attribute
+
+  Because this is such a common idiom, it has been made simpler for you.  Rather than writing the code above,
+  you can simple use the autoconnect attribute of the Parameter annotation.  This, effectively, creates
+  the defaultGadget() method for you. In this case
   the code of component OutputGadget can be reduced to:
   
 ---
 public class OutputGadget
 {
+  @Property
   @Parameter(required=true, principal=true, autoconnect = true)
   private Gadget gadget;
 

Modified: tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt Sun Aug 17 06:56:35 2008
@@ -190,7 +190,7 @@
     public void contributeAlias(
             Configuration<AliasContribution> configuration,
 
-            @InjectService("AppRequestExceptionHandler")
+            @Local
             RequestExceptionHandler handler)
     {
         configuration.add(AliasContribution.create(RequestExceptionHandler.class, handler));
@@ -210,11 +210,15 @@
   in this module, "AppRequestExceptionHandler").  Without a little more work, Tapestry will be unable to determine
   which one to use when an exception does occur.
 
+  We'll use the {{{../guide/alias.html}Alias service}} to remove this ambiguity.
+
   The contributeAlias() method makes a contribution to the Alias service's configuration.  The Alias service
   is used to disambiguate injections when just a service interface is given; one service will become the "Alias", hiding the
   existence of the others.
 
   Here we inject the AppRequestExceptionHandler service and contribute it as the alias for type RequestExceptionHandler.
+  The @Local annotation is used to select the RequestHandler service defined by this module, AppModule.  Once contributed
+  into Alias, it becomes the default service injected through the framework.
     
   This finally brings us to the point where we can see the result:
 

Modified: tapestry/tapestry5/trunk/src/site/apt/cookbook/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/cookbook/index.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/cookbook/index.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/cookbook/index.apt Sun Aug 17 06:56:35 2008
@@ -5,5 +5,5 @@
   The Tapestry Cookbook is a collection of tips and tricks for commonly occuring
   patterns in Tapestry.
 
-  The cookbook conists of a number of pages; use the standard site navigation (on the left side of the page)
+  The cookbook consists of a number of pages; use the standard site navigation (on the left side of the page)
   to reach the pages.
\ No newline at end of file

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/appstate.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/appstate.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/appstate.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/appstate.apt Sun Aug 17 06:56:35 2008
@@ -46,7 +46,7 @@
   Scalable web applications do not create the server-side session needlessly. If you can avoid creating the session, especially on first
   access to your web application, you will be able to handle an order of magnitude more users. So, if you can avoid creating the ASO, you should do so.
   
-  But how to avoid creating it?  Simply checking ("_myState != null") will force the creation of the ASO and the session to store it in.
+  But how to avoid creating it?  Simply checking ("myState != null") will force the creation of the ASO and the session to store it in.
   
   Instead, create a second field:
   
@@ -71,6 +71,23 @@
 
   Each ASO is managed according to a persistence strategy. The default persistence strategy, "session", stores the ASOs inside the session.
   The session is created as needed.
+
+* Clustering Issue
+
+  Application State Objects are, by design, mutable objects.  This means that, once read from the session, they are often modified.
+  This poses a problem <in a cluster>, because the modified version of the ASO should be propogated to the other server(s) in the cluster.
+
+  Tapestry handles this automatically; at the end of a request, every ASO that has been accessed from the session will be restored
+  into the session.  This will trigger the application server to synchronize the new state of the ASO around the the cluster.
+
+  This can be optimized: the interface
+  {{{../apidocs/org/apache/tapestry5/OptimizedApplicationStateObject.html}OptimizedApplicationStateObject}}  can be
+  implemented by   your ASO.  This provides control over whether the ASO is "dirty" and needs to be stored.  Thus, in the majority of
+  requests where the internal state of the ASO is not changed, it will not be restored into the session.
+
+  The easiest way to take advantage of this is to extend from the
+  {{{../apidocs/org/apache/tapestry5/BaseOptimizedApplicationStateObject.html}BaseOptimizedApplicationStateObject}}, and
+  invoke markDirty() from the setter methods.
   
 Configuring ASOs
 

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/beaneditform.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/beaneditform.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/beaneditform.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/beaneditform.apt Sun Aug 17 06:56:35 2008
@@ -23,7 +23,9 @@
   * Enum: as a drop-down list
   
   * Boolean: as a checkbox
-  
+
+  * Date: as a JavaScript calendar
+
   []
   
   Resolving a property type to an editor type involves a search up the inheritance hierarchy: thus the super-type of Integer, Long, BigDecimal, etc. is

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/pagenav.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/pagenav.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/pagenav.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/pagenav.apt Sun Aug 17 06:56:35 2008
@@ -8,30 +8,30 @@
   
   Any individual request will be targetted at a single page.  Requests come in two forms: 
   
-  * <action> requests target a specific component on a specific page, triggering an event within that component
+  * <component event> requests target a specific component on a specific page, triggering an event within that component
   
   * <render> requests target a specific page, and stream the HTML markup for that page back to the client
   
   []
   
-  This dictomy between action requests 
+  This dictomy between component event requests
   and render requests is new in Tapestry 5. It is in some ways based on ideas from the Portlet specification and differentiating
   the two types of requests alleviates a number of problems in traditional web applications related to the browser back button, or to the user hitting the
   refresh button in their browser.
   
-Action Requests
+Component Event Requests
   
-  Action requests may take the form of hyperlinks
+  Component event requests may take the form of hyperlinks
   ({{{../component-parameters.html#org.apache.tapestry5.corelib.components.actionlink}ActionLink}}) or form submissions
   ({{{../component-parameters.html#org.apache.tapestry5.corelib.components.form}Form}}).
   
   In both cases, the value returned from an {{{event.html}event handler method}} controls the response sent to the client web browser.
   
-  The URL for an action request identifies the name of the page, the nested id of the component, and the name of the event to trigger on the component (this is usually "action").
-  Further, an action request may contain additional context information, which will be provided to the event handler method.  
+  The URL for a component event request identifies the name of the page, the nested id of the component, and the name of the event to trigger on the component (this is usually "action").
+  Further, a component event request may contain additional context information, which will be provided to the event handler method.
   
   These URLs expose a bit of the internal structure of the application.  Over time, as an application grows and is maintained, the ids of components may change. This means that
-  action request URLs should not be bookmarked.  Fortunately, users will rarely have the chance to do so (see below).
+  component event request URLs should not be bookmarked.  Fortunately, users will rarely have the chance to do so (see below).
   
 * Null response
 
@@ -41,7 +41,8 @@
   to generate the page.
   
   The user will see the newly generated content in their browser. In addition, the URL in the browser's address bar will be a render request URL.  Render request URLs are
-  shorter and contain less application structure (for instance, they don't include component ids or event types).  Render requests URLs are what your users will bookmark. The action request URLs
+  shorter and contain less application structure (for instance, they don't include component ids or event types).
+  Render requests URLs are what your users will bookmark. The component event request URLs
   are transitory, meaningful only while the application is actively engaged, and not meant to be used in later sessions.
   
 * String response
@@ -93,7 +94,7 @@
   
 Page Render Requests
 
-  Render requests are simpler in structure and behavior than action requests. In the simplest case, the URL is simply the
+  Render requests are simpler in structure and behavior than component event requests. In the simplest case, the URL is simply the
   logical name of the page.
   
   Pages may have an <activation context>.  The activation context represents persistent information about the state of the page.  In practical terms,
@@ -154,14 +155,14 @@
   . . .
 +-----+
 
-  Here's the relevant part: when the page renders, it is likely to include more action request URLs (links and forms). The action requests
+  Here's the relevant part: when the page renders, it is likely to include more component event request URLs (links and forms). The component event requests
   for those links and forms will <also> start by activating the page, before performing other work. This forms an unbroken chain of requests
   that include the same activation context.
   
   To some degree, this same effect could be accomplished using a {{{persist.html}persistent page value}}, but that requires an active session,
   and the result is not bookmarkable.
 
-  The activate event handler may also return a value, which is treated identically to a return value of an action request event trigger.  This will typically
+  The activate event handler may also return a value, which is treated identically to a return value of a component event request event trigger.  This will typically
   be used in an access validation scenario.
   
 Page Navigation Patterns
@@ -171,7 +172,7 @@
   Let's take a typical master/detail relationship using the concept of a product catalog page.  In this example, the 
   ProductListing page is a list of products, and the ProductDetails page must display the details for a specific product.
   
-* Action Requests / Persistent Data
+* component event requests / Persistent Data
 
   In this pattern, the ProductListing page uses action events and a persistent field on the ProductDetails page.
   
@@ -218,7 +219,7 @@
 
   This is a minimal approach, perhaps good enough for a prototype. 
   
-  When the user clicks a link, the action request URL will initially be something like "http://.../productlisting.select/99" and the final
+  When the user clicks a link, the component event request URL will initially be something like "http://.../productlisting.select/99" and the final
   render request URL will be something like "http://.../productdetails".  Notice that the product id ("99") does not appear in the render request URL.
   
   It has some minor flaws:
@@ -229,7 +230,7 @@
   
   * The URL does not indicate the identity of the product; if the user bookmarks the URL and comes back later, they will trigger the previous case (no valid product id).
   
-* Action Requests / Persistent Data
+* Component Event Requests / Persistent Data
 
   We can improve the previous example without changing the ProductListing page,  using a passivation and activation context
   to avoid the session and make the links more bookmarkable.

Modified: tapestry/tapestry5/trunk/src/site/apt/guide/reload.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/reload.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/reload.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/reload.apt Sun Aug 17 06:56:35 2008
@@ -16,16 +16,13 @@
 
   When a template changes, all page instances (as well as the hiearchy of components below them) are discarded and
   reconstructed with the new template. However, classes are not reloaded in this case.
-  
-  A future version of Tapestry may be more selective, removing only page instances that are affected by the changed
-  file(s).
-  
+
 * Class Reloading
 
-  On a change to <any> class inside a controlled package (or any sub-package of a controlled package), Tapestry will
-  discard all page instances, and discard the class loader.
+  On a change to <any> loaded class from inside a controlled package (or any sub-package of a controlled package), Tapestry will
+  discard all page instances, and discard the class loader. 
   
-  {{{persist.html}Persistent data}} on the pages will usually not be affected (as it is stored separately, in the session).
+  {{{persist.html}Persistent field data}} on the pages will usually not be affected (as it is stored separately, in the session).
   This allows you to make fairly significant changes to a component class even while the application continues to run.
   
 Page and Component Packages
@@ -33,6 +30,19 @@
   Only page and component classes are subject to reload.
   
   Reloading is based on package name; the packages that are reloaded are derived from the {{{conf.html}application configuration}}.
+
+  If your root package is <code>org.example.myapp</code>, then only classes in the following packages will be
+  scanned for automatic reloads:
+
+  * org.example.myapp.pages
+
+  * org.example.myapp.components
+
+  * org.example.myapp.mixins
+
+  * org.example.myapp.base
+
+  []
   
 File System Only
 

Modified: tapestry/tapestry5/trunk/src/site/apt/index.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/index.apt?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/index.apt Sun Aug 17 06:56:35 2008
@@ -84,6 +84,9 @@
 
 New And Of Note
 
+  * Application State Objects are now automatically saved back to the session at the end of the request,
+    which ensures that ASO data is properly replicated across at cluster.
+
   * The new {{{apidocs/org/apache/tapestry5/ioc/annotations/Local.html}@Local}}
     annotation makes it easier to reference services within the same module when injecting.  
 
@@ -114,7 +117,7 @@
 What's changed since Tapestry 4?
 
   Tapestry 5 is an all new code base, written from the ground up to take Java web
-  component development to new levels of productivity.
+  application development to new levels of productivity.
   
   This new release removes many limitations of Tapestry 4:
   

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedApplicationStateObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedApplicationStateObject.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedApplicationStateObject.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedApplicationStateObject.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,57 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+/**
+ * Base class for creating optimized application state objects.  Works as a {@link
+ * javax.servlet.http.HttpSessionBindingListener} to determine when the object is no longer dirty.
+ */
+public abstract class BaseOptimizedApplicationStateObject implements OptimizedApplicationStateObject, HttpSessionBindingListener
+{
+    private transient boolean dirty;
+
+    public final boolean isApplicationStateObjectDirty()
+    {
+        return dirty;
+    }
+
+    /**
+     * Invoked by the servlet container when the value is stored (or re-stored) as an attribute of the session. This
+     * clears the dirty flag.
+     */
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        dirty = false;
+    }
+
+    /**
+     * Does nothing.
+     */
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+    }
+
+    /**
+     * Invoked by the subclass whenever the internal state of the ASO changes. Typically, this is invoked from mutator
+     * methods.
+     */
+    protected final void markDirty()
+    {
+        dirty = true;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedApplicationStateObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedApplicationStateObject.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedApplicationStateObject.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedApplicationStateObject.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,31 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5;
+
+/**
+ * <em>Optional</em> interface that may be implemented by an Application State Object.
+ *
+ * @see org.apache.tapestry5.annotations.ApplicationState
+ * @see org.apache.tapestry5.services.ApplicationStateManager
+ */
+public interface OptimizedApplicationStateObject
+{
+    /**
+     * Determines if the application state object has changed its state since being read from the session.
+     *
+     * @return true if the ASO has changed and needs resaving, false otherwise
+     */
+    boolean isApplicationStateObjectDirty();
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/ApplicationState.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/ApplicationState.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/ApplicationState.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/annotations/ApplicationState.java Sun Aug 17 06:56:35 2008
@@ -14,8 +14,6 @@
 
 package org.apache.tapestry5.annotations;
 
-import org.apache.tapestry5.services.ApplicationStateManager;
-
 import java.lang.annotation.Documented;
 import static java.lang.annotation.ElementType.FIELD;
 import java.lang.annotation.Retention;
@@ -24,13 +22,13 @@
 
 /**
  * Marker annotation for a field that is an <em>application state object</em> as controlled by the {@link
- * ApplicationStateManager}.
+ * org.apache.tapestry5.services.ApplicationStateManager}.
  * <p/>
  * An ASO file may have a companion field, of type boolean, used to see if the ASO has been created yet. If another
- * field exists with the same name, suffixed with "Exists" (i.e., "_aso" for the ASO field, and "_asoExists" for the
+ * field exists with the same name, suffixed with "Exists" (i.e., "aso" for the ASO field, and "asoExists" for the
  * companion field) and the type of that field is boolean, then access to the field will determine whether the ASO has
- * already been created. This is necessary because even a null check ("_aso != null") will force the ASO to be created.
- * Instead, check the companion boolean field ("_asoExists").
+ * already been created. This is necessary because even a null check ("aso != null") may force the ASO to be created.
+ * Instead, check the companion boolean field ("asoExists").
  */
 @Target(FIELD)
 @Documented

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestEvent.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestEvent.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestEvent.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,46 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.events;
+
+import org.apache.tapestry5.services.Request;
+
+import java.util.EventObject;
+
+public final class EndOfRequestEvent extends EventObject
+{
+    private final Request request;
+
+
+    /**
+     * Constructs a prototypical Event.
+     *
+     * @param source The Request which is completing.
+     * @throws IllegalArgumentException if source is null.
+     */
+    public EndOfRequestEvent(Request source)
+    {
+        super(source);
+
+        this.request = source;
+    }
+
+    /**
+     * The Request object (the source of the event).
+     */
+    public Request getRequest()
+    {
+        return request;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestListener.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestListener.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/events/EndOfRequestListener.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,29 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.events;
+
+/**
+ * Event listener interface for objects that need to know when the current request finishes.
+ */
+public interface EndOfRequestListener
+{
+    /**
+     * Notified at the end of the request.  This notification occurs after the response has been sent to the client,
+     * which means that it is to late to (for example) create a new HttpSession.
+     *
+     * @param event identifies the request which did complete
+     */
+    void requestDidComplete(EndOfRequestEvent event);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHub.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHub.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHub.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHub.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,35 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.services;
+
+import org.apache.tapestry5.internal.events.EndOfRequestListener;
+import org.apache.tapestry5.services.Request;
+
+/**
+ * Manages request notifications for the {@link org.apache.tapestry5.internal.events.EndOfRequestListener} interface.
+ */
+public interface EndOfRequestListenerHub
+{
+    void addEndOfRequestListener(EndOfRequestListener listener);
+
+    void removeEndOfRequestListener(EndOfRequestListener listener);
+
+    /**
+     * Invoked at the end of the request to notify the listeners.
+     *
+     * @param request which just completed
+     */
+    void fire(Request request);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImpl.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImpl.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,47 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.services;
+
+import org.apache.tapestry5.internal.events.EndOfRequestEvent;
+import org.apache.tapestry5.internal.events.EndOfRequestListener;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.services.Request;
+
+import java.util.List;
+
+public class EndOfRequestListenerHubImpl implements EndOfRequestListenerHub
+{
+    private final List<EndOfRequestListener> listeners = CollectionFactory.newThreadSafeList();
+
+    public void addEndOfRequestListener(EndOfRequestListener listener)
+    {
+        listeners.add(listener);
+    }
+
+    public void removeEndOfRequestListener(EndOfRequestListener listener)
+    {
+        listeners.remove(listener);
+    }
+
+    public void fire(Request request)
+    {
+        EndOfRequestEvent event = new EndOfRequestEvent(request);
+
+        for (EndOfRequestListener l : listeners)
+        {
+            l.requestDidComplete(event);
+        }
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalModule.java Sun Aug 17 06:56:35 2008
@@ -83,6 +83,7 @@
         binder.bind(PageResourcesSource.class, PageResourcesSourceImpl.class);
         binder.bind(RequestSecurityManager.class, RequestSecurityManagerImpl.class);
         binder.bind(InternalRequestGlobals.class, InternalRequestGlobalsImpl.class);
+        binder.bind(EndOfRequestListenerHub.class);
     }
 
     /**

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategy.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategy.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategy.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategy.java Sun Aug 17 06:56:35 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,22 +14,27 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.OptimizedApplicationStateObject;
+import org.apache.tapestry5.internal.events.EndOfRequestEvent;
+import org.apache.tapestry5.internal.events.EndOfRequestListener;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.services.ApplicationStateCreator;
 import org.apache.tapestry5.services.ApplicationStatePersistenceStrategy;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Session;
 
+import java.util.Map;
+
 /**
  * Stores ASOs in the {@link Session}, which will be created as necessary.
- * <p/>
- * TODO: Re-storing the object back into the session at the end of the request. That's going to require some kind of
- * end-of-request notification.
  */
 public class SessionApplicationStatePersistenceStrategy implements
-        ApplicationStatePersistenceStrategy
+        ApplicationStatePersistenceStrategy, EndOfRequestListener
 {
     static final String PREFIX = "aso:";
 
+    static final String ASO_MAP_ATTRIBUTE = "org.apache.tapestry.application-state-object-map";
+
     private final Request request;
 
     public SessionApplicationStatePersistenceStrategy(Request request)
@@ -49,7 +54,16 @@
 
         String key = buildKey(asoClass);
 
-        T aso = (T) session.getAttribute(key);
+        Map<String, Object> asoMap = getASOMap();
+
+        T aso = (T) asoMap.get(key);
+
+        if (aso != null) return aso;
+
+        // Otherwise, get/create it in the session and record it in the
+        // aso map.
+
+        aso = (T) session.getAttribute(key);
 
         if (aso == null)
         {
@@ -57,6 +71,8 @@
             session.setAttribute(key, aso);
         }
 
+        asoMap.put(key, aso);
+
         return aso;
     }
 
@@ -70,6 +86,8 @@
         String key = buildKey(asoClass);
 
         getSession().setAttribute(key, aso);
+
+        getASOMap().put(key, aso);
     }
 
     public <T> boolean exists(Class<T> asoClass)
@@ -81,4 +99,56 @@
         return session != null && session.getAttribute(key) != null;
     }
 
+    private Map<String, Object> getASOMap()
+    {
+        Map<String, Object> result = (Map<String, Object>) request.getAttribute(ASO_MAP_ATTRIBUTE);
+
+        if (result == null)
+        {
+            result = CollectionFactory.newMap();
+            request.setAttribute(ASO_MAP_ATTRIBUTE, result);
+        }
+
+        return result;
+    }
+
+    public void requestDidComplete(EndOfRequestEvent event)
+    {
+        Map<String, Object> map = getASOMap();
+
+        for (String key : map.keySet())
+        {
+
+            Object aso = map.get(key);
+
+            if (aso == null) continue;
+
+            if (needsRestore(aso))
+            {
+                Session session = request.getSession(true);
+
+                // It is expected that the ASO implements HttpSessionBindingListener and
+                // can clear its dirty flag as it is saved.
+
+                session.setAttribute(key, aso);
+            }
+        }
+    }
+
+    private boolean needsRestore(Object aso)
+    {
+        // We could check for basic immutable types here, but those are not typically ASOs.
+        // ASOs tend to be more complex, mutable objects.
+
+        if (aso instanceof OptimizedApplicationStateObject)
+        {
+            OptimizedApplicationStateObject optimized = (OptimizedApplicationStateObject) aso;
+
+            return optimized.isApplicationStateObjectDirty();
+        }
+
+        // If not optimized, assume that it is (in fact) dirty.
+
+        return true;
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java Sun Aug 17 06:56:35 2008
@@ -578,6 +578,8 @@
 
                                          LocalizationSetter localizationSetter,
 
+                                         final EndOfRequestListenerHub endOfRequestListenerHub,
+
                                          ObjectLocator locator)
     {
         RequestFilter staticFilesFilter = new StaticFilesFilter(context);
@@ -592,6 +594,21 @@
             }
         };
 
+        RequestFilter fireEndOfRequestEvent = new RequestFilter()
+        {
+            public boolean service(Request request, Response response, RequestHandler handler) throws IOException
+            {
+                try
+                {
+                    return handler.service(request, response);
+                }
+                finally
+                {
+                    endOfRequestListenerHub.fire(request);
+                }
+            }
+        };
+
         configuration.add("CheckForUpdates",
                           new CheckForUpdatesFilter(updateListenerHub, checkInterval, updateTimeout), "before:*");
 
@@ -599,7 +616,9 @@
 
         configuration.add("ErrorFilter", locator.autobuild(RequestErrorFilter.class));
 
-        configuration.add("StoreIntoGlobals", storeIntoGlobals);
+        configuration.add("StoreIntoGlobals", storeIntoGlobals, "after:StaticFiles", "before:ErrorFilter");
+
+        configuration.add("EndOfRequest", fireEndOfRequestEvent, "after:StoreIntoGlobals", "before:ErrorFilter");
 
         configuration.add("Localization", new LocalizationFilter(localizationSetter), "after:ErrorFilter");
     }
@@ -1230,9 +1249,15 @@
     public void contributeApplicationStatePersistenceStrategySource(
             MappedConfiguration<String, ApplicationStatePersistenceStrategy> configuration,
 
-            Request request)
+            @Local
+            ApplicationStatePersistenceStrategy sessionStategy)
+    {
+        configuration.add("session", sessionStategy);
+    }
+
+    public ApplicationStatePersistenceStrategy buildSessionApplicationStatePersistenceStrategy(ObjectLocator locator)
     {
-        configuration.add("session", new SessionApplicationStatePersistenceStrategy(request));
+        return locator.autobuild(SessionApplicationStatePersistenceStrategy.class);
     }
 
     public void contributeAssetSource(MappedConfiguration<String, AssetFactory> configuration,

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImplTest.java?rev=686612&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImplTest.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/EndOfRequestListenerHubImplTest.java Sun Aug 17 06:56:35 2008
@@ -0,0 +1,68 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.internal.services;
+
+import org.apache.tapestry5.internal.events.EndOfRequestEvent;
+import org.apache.tapestry5.internal.events.EndOfRequestListener;
+import org.apache.tapestry5.internal.test.InternalBaseTestCase;
+import org.apache.tapestry5.services.Request;
+import org.easymock.Capture;
+import static org.easymock.EasyMock.capture;
+import org.testng.annotations.Test;
+
+public class EndOfRequestListenerHubImplTest extends InternalBaseTestCase
+{
+    @Test
+    public void add_and_notify()
+    {
+        EndOfRequestListenerHub hub = new EndOfRequestListenerHubImpl();
+        final Request request = mockRequest();
+
+        EndOfRequestListener listener = newMock(EndOfRequestListener.class);
+
+        Capture<EndOfRequestEvent> eventCapture = newCapture();
+
+        listener.requestDidComplete(capture(eventCapture));
+
+        replay();
+
+        hub.addEndOfRequestListener(listener);
+
+        hub.fire(request);
+
+        verify();
+
+        assertSame(eventCapture.getValue().getRequest(), request);
+    }
+
+
+    @Test
+    public void add_remove_notify()
+    {
+        EndOfRequestListenerHub hub = new EndOfRequestListenerHubImpl();
+        final Request request = mockRequest();
+
+        EndOfRequestListener listener = newMock(EndOfRequestListener.class);
+
+        replay();
+
+        hub.addEndOfRequestListener(listener);
+        hub.removeEndOfRequestListener(listener);
+
+        hub.fire(request);
+
+        verify();
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategyTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategyTest.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategyTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionApplicationStatePersistenceStrategyTest.java Sun Aug 17 06:56:35 2008
@@ -14,16 +14,27 @@
 
 package org.apache.tapestry5.internal.services;
 
+import org.apache.tapestry5.OptimizedApplicationStateObject;
+import org.apache.tapestry5.internal.events.EndOfRequestListener;
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
 import org.apache.tapestry5.internal.transform.pages.ReadOnlyBean;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.services.ApplicationStateCreator;
 import org.apache.tapestry5.services.ApplicationStatePersistenceStrategy;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Session;
+import org.easymock.Capture;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.eq;
 import org.testng.annotations.Test;
 
+import java.util.Collections;
+import java.util.Map;
+
 public class SessionApplicationStatePersistenceStrategyTest extends InternalBaseTestCase
 {
+    private static final String ASO_MAP_ATTRIBUTE = SessionApplicationStatePersistenceStrategy.ASO_MAP_ATTRIBUTE;
+
     @SuppressWarnings("unchecked")
     @Test
     public void get_aso_already_exists()
@@ -34,16 +45,23 @@
         Object aso = new ReadOnlyBean();
         String key = "aso:" + asoClass.getName();
         ApplicationStateCreator creator = mockApplicationStateCreator();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
 
         train_getSession(request, true, session);
         train_getAttribute(session, key, aso);
 
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+
         replay();
 
         ApplicationStatePersistenceStrategy strategy = new SessionApplicationStatePersistenceStrategy(request);
 
         assertSame(strategy.get(asoClass, creator), aso);
 
+        // Check that the ASO Map was updated.
+
+        assertSame(asoMap.get(key), aso);
+
         verify();
     }
 
@@ -75,6 +93,7 @@
         Object aso = new ReadOnlyBean();
         String key = "aso:" + asoClass.getName();
         ApplicationStateCreator creator = mockApplicationStateCreator();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
 
         // First for exists()
         train_getSession(request, false, session);
@@ -82,6 +101,9 @@
 
         // Second for get()
         train_getSession(request, true, session);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+        // Not in map
         train_getAttribute(session, key, null);
 
         train_create(creator, aso);
@@ -102,6 +124,8 @@
 
         assertTrue(strategy.exists(asoClass));
 
+        assertSame(asoMap.get(key), aso);
+
         verify();
     }
 
@@ -114,16 +138,172 @@
         Class asoClass = ReadOnlyBean.class;
         Object aso = new ReadOnlyBean();
         String key = "aso:" + asoClass.getName();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
+
+        train_getSession(request, true, session);
+        session.setAttribute(key, aso);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+
+        replay();
+
+        ApplicationStatePersistenceStrategy strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.set(asoClass, aso);
+
+        assertSame(asoMap.get(key), aso);
+
+        verify();
+    }
+
+    @Test
+    public void aso_map_created_as_needed()
+    {
+        Request request = mockRequest();
+        Session session = mockSession();
+        Class asoClass = ReadOnlyBean.class;
+        Object aso = new ReadOnlyBean();
+        String key = "aso:" + asoClass.getName();
+        Capture<Map<String, Object>> asoMapCapture = newCapture();
 
         train_getSession(request, true, session);
         session.setAttribute(key, aso);
 
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, null);
+
+        request.setAttribute(eq(ASO_MAP_ATTRIBUTE), capture(asoMapCapture));
+
         replay();
 
         ApplicationStatePersistenceStrategy strategy = new SessionApplicationStatePersistenceStrategy(request);
 
         strategy.set(asoClass, aso);
 
+        assertSame(asoMapCapture.getValue().get(key), aso);
+
+        verify();
+    }
+
+    @Test
+    public void restore_map_is_empty()
+    {
+        Request request = mockRequest();
+        Map<String, Object> asoMap = Collections.emptyMap();
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+
+        replay();
+
+        EndOfRequestListener strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.requestDidComplete(null);
+
+        verify();
+    }
+
+    @Test
+    public void restore_aso_is_null()
+    {
+        Request request = mockRequest();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
+
+        asoMap.put("some.key", null);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+
+        replay();
+
+        EndOfRequestListener strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.requestDidComplete(null);
+
+        verify();
+
+    }
+
+
+    @Test
+    public void restore_non_optimized_object()
+    {
+        Request request = mockRequest();
+        Session session = mockSession();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
+
+        String key = "foo:bar";
+        Object aso = new Object();
+
+        asoMap.put(key, aso);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+        train_getSession(request, true, session);
+        session.setAttribute(key, aso);
+
+        replay();
+
+        EndOfRequestListener strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.requestDidComplete(null);
+
+        verify();
+    }
+
+    @Test
+    public void restore_optimized_object_is_dirty()
+    {
+        Request request = mockRequest();
+        Session session = mockSession();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
+
+        String key = "foo:bar";
+        OptimizedApplicationStateObject aso = mockOptimizedApplicationStateObject(true);
+
+        asoMap.put(key, aso);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+        train_getSession(request, true, session);
+        session.setAttribute(key, aso);
+
+        replay();
+
+        EndOfRequestListener strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.requestDidComplete(null);
+
         verify();
     }
+
+    @Test
+    public void restore_optimized_object_is_clean()
+    {
+        Request request = mockRequest();
+        Session session = mockSession();
+        Map<String, Object> asoMap = CollectionFactory.newMap();
+
+        String key = "foo:bar";
+        OptimizedApplicationStateObject aso = mockOptimizedApplicationStateObject(false);
+
+        asoMap.put(key, aso);
+
+        train_getAttribute(request, ASO_MAP_ATTRIBUTE, asoMap);
+
+        replay();
+
+        EndOfRequestListener strategy = new SessionApplicationStatePersistenceStrategy(request);
+
+        strategy.requestDidComplete(null);
+
+        verify();
+    }
+
+    private OptimizedApplicationStateObject mockOptimizedApplicationStateObject(boolean dirty)
+    {
+
+        OptimizedApplicationStateObject object = newMock(OptimizedApplicationStateObject.class);
+
+        expect(object.isApplicationStateObjectDirty()).andReturn(dirty);
+
+        return object;
+    }
+
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/IOCTestCase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/IOCTestCase.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/IOCTestCase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/IOCTestCase.java Sun Aug 17 06:56:35 2008
@@ -393,4 +393,14 @@
     {
         expect(locator.autobuild(beanClass)).andReturn(instance);
     }
+
+    protected final void train_get(PerthreadManager manager, String key, Object object)
+    {
+        expect(manager.get(key)).andReturn(object);
+    }
+
+    protected final PerthreadManager mockPerthreadManager()
+    {
+        return newMock(PerthreadManager.class);
+    }
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/TestBase.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/TestBase.java?rev=686612&r1=686611&r2=686612&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/TestBase.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/test/TestBase.java Sun Aug 17 06:56:35 2008
@@ -14,10 +14,7 @@
 
 package org.apache.tapestry5.ioc.test;
 
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
-import org.easymock.IExpectationSetters;
-import org.easymock.IMocksControl;
+import org.easymock.*;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 
@@ -47,7 +44,6 @@
  */
 public class TestBase extends Assert
 {
-
     private static class ThreadLocalControl extends ThreadLocal<IMocksControl>
     {
         @Override
@@ -112,7 +108,7 @@
      *
      * @param throwable the exception to be thrown by the most recent method call on any mock
      */
-    protected final void setThrowable(Throwable throwable)
+    protected static final void setThrowable(Throwable throwable)
     {
         EasyMock.expectLastCall().andThrow(throwable);
     }
@@ -122,7 +118,7 @@
      *
      * @param answer callback for the most recent method invocation
      */
-    protected final void setAnswer(IAnswer answer)
+    protected static final void setAnswer(IAnswer answer)
     {
         EasyMock.expectLastCall().andAnswer(answer);
     }
@@ -132,7 +128,7 @@
      * method that is expected to throw an exception.
      */
 
-    protected final void unreachable()
+    protected static final void unreachable()
     {
         fail("This code should not be reachable.");
     }
@@ -145,7 +141,7 @@
      * @return expectation setter, for setting return value, etc.
      */
     @SuppressWarnings("unchecked")
-    protected final <T> IExpectationSetters<T> expect(T value)
+    protected static final <T> IExpectationSetters<T> expect(T value)
     {
         return EasyMock.expect(value);
     }
@@ -156,7 +152,7 @@
      * @param t          throwable to check
      * @param substrings some number of expected substrings
      */
-    protected final void assertMessageContains(Throwable t, String... substrings)
+    protected static final void assertMessageContains(Throwable t, String... substrings)
     {
         String message = t.getMessage();
 
@@ -174,7 +170,7 @@
      * @param actual   actual values to check
      * @param expected expected values
      */
-    protected final <T> void assertListsEquals(List<T> actual, List<T> expected)
+    protected static final <T> void assertListsEquals(List<T> actual, List<T> expected)
     {
         int count = Math.min(actual.size(), expected.size());
 
@@ -189,11 +185,11 @@
     /**
      * Convenience for {@link #assertListsEquals(List, List)}.
      *
-     * @param <T>      tyoe of objects to compare
+     * @param <T>      type of objects to compare
      * @param actual   actual values to check
      * @param expected expected values
      */
-    protected final <T> void assertListsEquals(List<T> actual, T... expected)
+    protected static final <T> void assertListsEquals(List<T> actual, T... expected)
     {
         assertListsEquals(actual, Arrays.asList(expected));
     }
@@ -201,12 +197,20 @@
     /**
      * Convenience for {@link #assertListsEquals(List, List)}.
      *
-     * @param <T>      tyoe of objects to compare
+     * @param <T>      type of objects to compare
      * @param actual   actual values to check
      * @param expected expected values
      */
-    protected final <T> void assertArraysEqual(T[] actual, T... expected)
+    protected static final <T> void assertArraysEqual(T[] actual, T... expected)
     {
         assertListsEquals(Arrays.asList(actual), expected);
     }
+
+    /**
+     * A factory method to create EasyMock Capture objects.
+     */
+    protected static final <T> Capture<T> newCapture()
+    {
+        return new Capture<T>();
+    }
 }



Mime
View raw message