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/[EMAIL PROTECTED]
     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 
[EMAIL PROTECTED]
+ * 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 [EMAIL PROTECTED]
- * 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 [EMAIL PROTECTED] 
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 [EMAIL PROTECTED] 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 [EMAIL PROTECTED] #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 [EMAIL PROTECTED] #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>();
+    }
 }


Reply via email to