You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2005/09/10 16:44:58 UTC

svn commit: r280011 - in /jakarta/tapestry/trunk: ./ framework/src/java/org/apache/tapestry/ framework/src/java/org/apache/tapestry/engine/state/ framework/src/test/org/apache/tapestry/ framework/src/test/org/apache/tapestry/engine/state/ src/documenta...

Author: hlship
Date: Sat Sep 10 07:44:51 2005
New Revision: 280011

URL: http://svn.apache.org/viewcvs?rev=280011&view=rev
Log:
TAPESTRY-637: Application state objects are stored back to the session, even if unchanged

Added:
    jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/BaseSessionStoreOptimized.java
    jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/SessionStoreOptimized.java
    jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/TestBaseSessionStoreOptimized.java
Modified:
    jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/ApplicationStateManager.java
    jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/SessionScopeManager.java
    jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/engine/state/TestSessionScopeManager.java
    jakarta/tapestry/trunk/src/documentation/content/xdocs/UsersGuide/state.xml
    jakarta/tapestry/trunk/src/documentation/content/xdocs/links.ent
    jakarta/tapestry/trunk/status.xml

Added: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/BaseSessionStoreOptimized.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/BaseSessionStoreOptimized.java?rev=280011&view=auto
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/BaseSessionStoreOptimized.java (added)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/BaseSessionStoreOptimized.java Sat Sep 10 07:44:51 2005
@@ -0,0 +1,67 @@
+// Copyright 2005 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.tapestry;
+
+import java.io.Serializable;
+
+import javax.servlet.http.HttpSessionBindingEvent;
+import javax.servlet.http.HttpSessionBindingListener;
+
+/**
+ * Base implementation of {@link org.apache.tapestry.SessionStoreOptimized}. Subclasses should
+ * invoke {@link #markSessionStoreNeeded()} any time internal state changed.
+ * 
+ * @author Howard M. Lewis Ship
+ * @since 4.0
+ */
+public class BaseSessionStoreOptimized implements SessionStoreOptimized, Serializable,
+        HttpSessionBindingListener
+{
+    private static final long serialVersionUID = -2786704444616789831L;
+
+    private transient boolean _dirty;
+
+    /**
+     * Clears the dirty flag.
+     */
+    public void valueBound(HttpSessionBindingEvent event)
+    {
+        _dirty = false;
+    }
+
+    /**
+     * Does nothing.
+     */
+    public void valueUnbound(HttpSessionBindingEvent event)
+    {
+    }
+
+    /**
+     * Sets the dirty flag.
+     */
+    protected void markSessionStoreNeeded()
+    {
+        _dirty = true;
+    }
+
+    /**
+     * Returns the dirty flag.
+     */
+    public boolean isStoreToSessionNeeded()
+    {
+        return _dirty;
+    }
+
+}

Added: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/SessionStoreOptimized.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/SessionStoreOptimized.java?rev=280011&view=auto
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/SessionStoreOptimized.java (added)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/SessionStoreOptimized.java Sat Sep 10 07:44:51 2005
@@ -0,0 +1,40 @@
+// Copyright 2005 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.tapestry;
+
+/**
+ * <em>Optional</em> interface implemented by Application State Objects. This interface allows
+ * Tapestry to optimize the storage of the objects into the
+ * {@link org.apache.tapestry.web.WebSession session}, only storing the objects when they require
+ * storage due to a change in internal state.
+ * 
+ * @author Howard M. Lewis Ship
+ * @since 4.0
+ */
+public interface SessionStoreOptimized
+{
+    /**
+     * Queried by the {@link org.apache.tapestry.engine.state.StateObjectManager} to see if the
+     * object actually needs to be stored. Objects that implement this interface should store an
+     * internal flag. The flag should be set when any change to the object's internal state occurs.
+     * The flag should be cleared when the object is stored into the session (typically, by
+     * implementing {@link javax.servlet.http.HttpSessionBindingListener}.
+     * 
+     * @return true if the object needs to be stored back into the session, false if the internal
+     *         state of the object is unchanged
+     */
+    boolean isStoreToSessionNeeded();
+
+}

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/ApplicationStateManager.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/ApplicationStateManager.java?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/ApplicationStateManager.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/ApplicationStateManager.java Sat Sep 10 07:44:51 2005
@@ -54,7 +54,7 @@
     public void store(String objectName, Object stateObject);
 
     /**
-     * Asks each {@link StateObjectManager}to store each object obtained.
+     * Asks each {@link StateObjectManager} to store each object obtained.
      */
 
     public void flush();

Modified: jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/SessionScopeManager.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/SessionScopeManager.java?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/SessionScopeManager.java (original)
+++ jakarta/tapestry/trunk/framework/src/java/org/apache/tapestry/engine/state/SessionScopeManager.java Sat Sep 10 07:44:51 2005
@@ -14,6 +14,7 @@
 
 package org.apache.tapestry.engine.state;
 
+import org.apache.tapestry.SessionStoreOptimized;
 import org.apache.tapestry.web.WebRequest;
 import org.apache.tapestry.web.WebSession;
 
@@ -74,6 +75,14 @@
 
     public void store(String objectName, Object stateObject)
     {
+        if (stateObject instanceof SessionStoreOptimized)
+        {
+            SessionStoreOptimized optimized = (SessionStoreOptimized) stateObject;
+
+            if (!optimized.isStoreToSessionNeeded())
+                return;
+        }
+
         String key = buildKey(objectName);
 
         WebSession session = getSession();

Added: jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/TestBaseSessionStoreOptimized.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/TestBaseSessionStoreOptimized.java?rev=280011&view=auto
==============================================================================
--- jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/TestBaseSessionStoreOptimized.java (added)
+++ jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/TestBaseSessionStoreOptimized.java Sat Sep 10 07:44:51 2005
@@ -0,0 +1,76 @@
+// Copyright 2005 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.tapestry;
+
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionBindingEvent;
+
+import org.apache.hivemind.test.HiveMindTestCase;
+
+/**
+ * Tests for {@link org.apache.tapestry.BaseSessionStoreOptimized}.
+ * 
+ * @author Howard M. Lewis Ship
+ * @since 4.0
+ */
+public class TestBaseSessionStoreOptimized extends HiveMindTestCase
+{
+    public void testMarkDirty()
+    {
+        BaseSessionStoreOptimized object = new BaseSessionStoreOptimized();
+
+        assertEquals(false, object.isStoreToSessionNeeded());
+
+        object.markSessionStoreNeeded();
+
+        assertEquals(true, object.isStoreToSessionNeeded());
+    }
+
+    public void testMarkClean()
+    {
+        HttpSession session = (HttpSession) newMock(HttpSession.class);
+
+        BaseSessionStoreOptimized object = new BaseSessionStoreOptimized();
+
+        object.markSessionStoreNeeded();
+
+        replayControls();
+
+        object.valueBound(new HttpSessionBindingEvent(session, "sessionid", object));
+
+        assertEquals(false, object.isStoreToSessionNeeded());
+
+        verifyControls();
+    }
+
+    public void testUnboundDoesNothing()
+    {
+        HttpSession session = (HttpSession) newMock(HttpSession.class);
+
+        BaseSessionStoreOptimized object = new BaseSessionStoreOptimized();
+
+        object.markSessionStoreNeeded();
+
+        replayControls();
+
+        object.valueUnbound(new HttpSessionBindingEvent(session, "sessionid", object));
+
+        assertEquals(true, object.isStoreToSessionNeeded());
+
+        verifyControls();
+
+    }
+
+}

Modified: jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/engine/state/TestSessionScopeManager.java
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/engine/state/TestSessionScopeManager.java?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/engine/state/TestSessionScopeManager.java (original)
+++ jakarta/tapestry/trunk/framework/src/test/org/apache/tapestry/engine/state/TestSessionScopeManager.java Sat Sep 10 07:44:51 2005
@@ -15,6 +15,7 @@
 package org.apache.tapestry.engine.state;
 
 import org.apache.hivemind.test.HiveMindTestCase;
+import org.apache.tapestry.SessionStoreOptimized;
 import org.apache.tapestry.web.WebRequest;
 import org.apache.tapestry.web.WebSession;
 import org.easymock.MockControl;
@@ -51,11 +52,9 @@
 
     private WebSession newSession(String key, Object value)
     {
-        MockControl control = newControl(WebSession.class);
-        WebSession session = (WebSession) control.getMock();
+        WebSession session = newSession();
 
-        session.getAttribute(key);
-        control.setReturnValue(value);
+        trainGetAttribute(session, key, value);
 
         return session;
     }
@@ -139,11 +138,9 @@
         Object stateObject = new Object();
         StateObjectFactory factory = newFactory(stateObject);
 
-        MockControl control = newControl(WebSession.class);
-        WebSession session = (WebSession) control.getMock();
+        WebSession session = newSession();
 
-        session.getAttribute("state:myapp:fred");
-        control.setReturnValue(null);
+        trainGetAttribute(session, "state:myapp:fred", null);
 
         session.setAttribute("state:myapp:fred", stateObject);
 
@@ -160,12 +157,67 @@
         verifyControls();
     }
 
+    protected void trainGetAttribute(WebSession session, String name, Object attribute)
+    {
+        session.getAttribute(name);
+
+        getControl(session).setReturnValue(attribute);
+    }
+
     public void testStore()
     {
         Object stateObject = new Object();
 
-        MockControl control = newControl(WebSession.class);
-        WebSession session = (WebSession) control.getMock();
+        WebSession session = newSession();
+
+        session.setAttribute("state:myapp:fred", stateObject);
+
+        WebRequest request = newRequest(session);
+
+        replayControls();
+
+        SessionScopeManager m = new SessionScopeManager();
+        m.setRequest(request);
+        m.setApplicationId("myapp");
+
+        m.store("fred", stateObject);
+
+        verifyControls();
+    }
+
+    protected WebSession newSession()
+    {
+        return (WebSession) newMock(WebSession.class);
+    }
+
+    protected SessionStoreOptimized newOptimized(boolean dirty)
+    {
+        SessionStoreOptimized optimized = (SessionStoreOptimized) newMock(SessionStoreOptimized.class);
+
+        optimized.isStoreToSessionNeeded();
+        getControl(optimized).setReturnValue(dirty);
+
+        return optimized;
+    }
+
+    public void testStoreOptimizedClean()
+    {
+        Object stateObject = newOptimized(false);
+
+        SessionScopeManager m = new SessionScopeManager();
+
+        replayControls();
+
+        m.store("fred", stateObject);
+
+        verifyControls();
+    }
+
+    public void testStoreOptimizedDirty()
+    {
+        Object stateObject = newOptimized(true);
+
+        WebSession session = newSession();
 
         session.setAttribute("state:myapp:fred", stateObject);
 

Modified: jakarta/tapestry/trunk/src/documentation/content/xdocs/UsersGuide/state.xml
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/src/documentation/content/xdocs/UsersGuide/state.xml?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/src/documentation/content/xdocs/UsersGuide/state.xml (original)
+++ jakarta/tapestry/trunk/src/documentation/content/xdocs/UsersGuide/state.xml Sat Sep 10 07:44:51 2005
@@ -291,7 +291,7 @@
 In Tapestry 3.0, many users were frustrated that they had to specify the type of property
 in both the Java code and in the page specification. This is a violation of the
 <link href="http://c2.com/cgi/wiki?DontRepeatYourself">Dont Repeat Yourself</link> principal -- requiring
-coordination is just an inviation for the two sides to get out of synchronization. Starting with Tapestry 4.0,
+coordination is just an invitation for the two sides to get out of synchronization. Starting with Tapestry 4.0,
 there is no type attribute on the &spec.property; element; instead Tapestry matches the type to the
 property type of any existing accessor methods, and simply uses Object when there are no accessor methods.  In
 this example, the persistent itemsPerPage property will be type int, because of the abstract accessor methods.
@@ -451,6 +451,39 @@
   
 </section>  <!-- state.aso.access -->
 
+<section id="state.aso.optimize">
+  <title>Optimizing Storage</title>
+  
+<p>
+  Normally, Tapestry has no way of knowing when the internal state of an ASO has changed. On any request
+  where the ASO is accessed, Tapestry assumes that its internal state has changed. The ASO is re-stored
+  at the end of the request. For session-scoped ASOs in a cluster, this is critical to ensure that
+  information is properly distributed around the cluster.
+</p>  
+
+<p>
+  However, it can also be expensive. Assuming that your application mostly <em>reads</em> information
+  out of the ASO, that means a lot of wasted resources needlessly copying the ASO around the cluster.
+</p>
+
+<p>
+  To control this, ASOs may <em>optionally</em> implement the &SessionStoreOptimized; interface.
+  The method isStoreToSessionNeeded() will be checked; if it returns false, the object will 
+  <em>not</em> be stored.
+</p>
+
+<p>
+  Typically, the ASO will store a dirty flag, and will set the dirty flag on any change
+  to internal state. The flag will be returned by isStoreToSessionNeeded(). The ASO
+  will also implement the HttpSessionBindingListener interface, and clear
+  the flag in valueBound().
+</p>
+
+<p>
+  A base class, &BaseSessionStoreOptimized;, implements this behavior.
+</p>
+  
+</section>
   
 </section>  <!-- state.aso -->
 
@@ -488,7 +521,7 @@
 </p>	
 
 <p>
-The <code>state:</code> &binding-reference; combined with the &Conditional; component makes it easy for you to skip
+The <code>state:</code> &binding-reference; combined with the &If; component makes it easy for you to skip
 portions of a page if a particular ASO does not already exist; this allows you to avoid accidentally forcing its
 creation on first reference.
 </p>

Modified: jakarta/tapestry/trunk/src/documentation/content/xdocs/links.ent
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/src/documentation/content/xdocs/links.ent?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/src/documentation/content/xdocs/links.ent (original)
+++ jakarta/tapestry/trunk/src/documentation/content/xdocs/links.ent Sat Sep 10 07:44:51 2005
@@ -36,7 +36,9 @@
 <!ENTITY ChangeObserver 			'<link href="&apiroot;/event/ChangeObserver.html">ChangeObserver</link>'>
 <!ENTITY BaseComponent 				'<link href="&apiroot;/BaseComponent.html">BaseComponent</link>'>
 <!ENTITY BaseEngine 				'<link href="&apiroot;/engine/BaseEngine.html">BaseEngine</link>'>
-<!ENTITY BasePage 					'<link href="&apiroot;/html/BasePage.html">BasePage</link>'>
+<!ENTITY BasePage 
+					'<link href="&apiroot;/html/BasePage.html">BasePage</link>'>
+<!ENTITY BaseSessionStoreOptimized  '<link href="&apiroot;/BaseSessionStoreOptimized.html">BaseSessionStoreOptimized</link>'>
 <!ENTITY ComponentPropertySource	'<link href="&apiroot;/services/ComponentPropertySource.html">ComponentPropertySource</link>'>
 <!ENTITY DataSqueezer 				'<link href="&apiroot;/util/io/DataSqueezer.html">DataSqueezer</link>'>
 <!ENTITY Default 					'<link href="&apiroot;/bean/Default.html">Default</link>'>
@@ -62,7 +64,6 @@
 <!ENTITY IEngineService				'<link href="&apiroot;/engine/IEngineService.html">IEngineService</link>'>
 <!ENTITY IForm 						'<link href="&apiroot;/IForm.html">IForm</link>'>
 <!ENTITY IFormComponent 			'<link href="&apiroot;/form/IFormComponent.html">IFormComponent</link>'>
-<!ENTITY IEngineService 			'<link href="&apiroot;/IEngineService.html">IEngineService</link>'>
 <!ENTITY ILink						'<link href="&apiroot;/engine/ILink.html">ILink</link>'>
 <!ENTITY ILinkRenderer			    '<link href="&apiroot;/link/ILinkRenderer.html">ILinkRenderer</link>'>
 <!ENTITY INamespace 				'<link href="&apiroot;/INamespace.html">INamespace</link>'>
@@ -97,7 +98,6 @@
 <!ENTITY PageBeginRenderListener 	'<link href="&apiroot;/event/PageBeginRenderListener.html">PageBeginRenderListener</link>'>
 <!ENTITY PageEndRenderListener 		'<link href="&apiroot;/event/PageEndRenderListener.html">PageEndRenderListener</link>'>
 <!ENTITY PageRenderListener 		'<link href="&apiroot;/event/PageRenderListener.html">PageRenderListener</link>'>
-<!ENTITY PageAttachListener 		'<link href="&apiroot;/event/PageAttachListener.html">PageAttachListener</link>'>
 <!ENTITY PageValidateListener 		'<link href="&apiroot;/event/PageValidateListener.html">PageValidateListener</link>'>
 
 <!ENTITY PageRecorder 				'<link href="&apiroot;/record/PageRecorder.html">PageRecorder</link>'>
@@ -107,6 +107,7 @@
 <!ENTITY RedirectFilter 			'<link href="&apiroot;/RedirectFilter.html">RedirectFilter</link>'>
 <!ENTITY RequestContext 			'<link href="&apiroot;/RequestContext.html">RequestContext</link>'>
 <!ENTITY ServiceEncoder				'<link href="&apiroot;/engine/ServiceEncoder.html">ServiceEncoder</link>'>
+<!ENTITY SessionStoreOptimized      '<link href="&apiroot;/SessionStoreOptimized.html">SessionStoreOptimized</link>'>
 <!ENTITY SimpleEngine 				'<link href="&apiroot;/engine/SimpleEngine.html">SimpleEngine</link>'>
 <!ENTITY SpecificationParser 		'<link href="&apiroot;/parse/SpecificationParser">SpecificationParser</link>'>
 <!ENTITY StaleLinkException 		'<link href="&apiroot;/StaleLinkException.html">StaleLinkException</link>'>

Modified: jakarta/tapestry/trunk/status.xml
URL: http://svn.apache.org/viewcvs/jakarta/tapestry/trunk/status.xml?rev=280011&r1=280010&r2=280011&view=diff
==============================================================================
--- jakarta/tapestry/trunk/status.xml (original)
+++ jakarta/tapestry/trunk/status.xml Sat Sep 10 07:44:51 2005
@@ -61,6 +61,7 @@
       <action type="fix" dev="MB" fixes-bug="TAPESTRY-469">Document If component</action>
       <action type="fix" dev="MB" fixes-bug="TAPESTRY-464">Document For component</action>
       <action type="fix" dev="DS" fixes-bug="TAPESTRY-489">Document FormConditional component</action>
+      <action type="add" dev="HLS" fixes-bug="TAPESTRY-637">Application state objects are stored back to the session, even if unchanged</action>
     </release>
     <release version="4.0-beta-6" date="Sep 7 2005">
       <action type="update" dev="HLS" due-to="Henri Yandell">Convert Tapestry repository from CVS to SVN</action>



---------------------------------------------------------------------
To unsubscribe, e-mail: tapestry-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: tapestry-dev-help@jakarta.apache.org