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>();
+ }
}