You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by jo...@apache.org on 2011/06/25 14:31:38 UTC

svn commit: r1139536 - in /tapestry/tapestry5/trunk/tapestry-core: ./ src/main/java/org/apache/tapestry5/ src/main/java/org/apache/tapestry5/internal/services/ src/main/java/org/apache/tapestry5/services/ src/test/cluster/ src/test/cluster/WEB-INF/ src...

Author: joshcanfield
Date: Sat Jun 25 12:31:36 2011
New Revision: 1139536

URL: http://svn.apache.org/viewvc?rev=1139536&view=rev
Log:
TAP-1489 - Created a SessionFactory which determines which implementation to provide to the request.
 - Updated Session object to be able to report if it's been invalidated outside of tapestry
 
TAP-1355 - Removed code that set session attributes to null to force a HttpSessionBindingListener event. 
- Created mini-cluster integration test to ensure proper session storage with different approaches.
- Changed isDirty flags to atomic checkAndSet style to avoid missed updates

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java
      - copied, changed from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/
    tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/
    tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml
      - copied, changed from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/
    tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml
Modified:
    tapestry/tapestry5/trunk/tapestry-core/build.gradle
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.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/RequestImplTest.java
    tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java

Modified: tapestry/tapestry5/trunk/tapestry-core/build.gradle
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/build.gradle?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/build.gradle (original)
+++ tapestry/tapestry5/trunk/tapestry-core/build.gradle Sat Jun 25 12:31:36 2011
@@ -23,6 +23,8 @@ dependencies {
 
   // Antlr3 tool path used with the antlr3 task
   antlr3 "org.antlr:antlr:3.3"
+
+  testRuntime "org.hsqldb:hsqldb:1.8.0.10"
 }
 
 // This may spin out as a plugin once we've got the details down pat

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2008, 2009 The Apache Software Foundation
+// Copyright 2008, 2009, 2011 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,57 +14,33 @@
 
 package org.apache.tapestry5;
 
-import javax.servlet.http.HttpSessionBindingEvent;
-import javax.servlet.http.HttpSessionBindingListener;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Base implementation of
  * {@link org.apache.tapestry5.OptimizedSessionPersistedObject}. Subclasses
- * should invoke {@link #markDirty()} when internal state of the object changes.
+ * should invoke {@link #markDirty()} after the internal state of the object changes.
+ * <p>
+ * Due to the concurrent nature of session attributes it's important that markDirty occurs <strong>after</strong>
+ * the object has been changed. If the change occurs before the object has been mutated it's possible that another
+ * thread may re-store the object before the changes are actually made!
  * <p>
- * Note that (due to TAP5-834), the object will receive a spurious
- * <code>valueUnbound()</code> notification when dirty. Tapestry sets dirty
- * session attributes to null, then to the persisted object, to force a
- * <code>valueBound()</code> notification, and that unfortunately also sends the
- * <code>valueUnbound()</code>.
- * 
  * @since 5.1.1.0
  */
-public abstract class BaseOptimizedSessionPersistedObject implements
-        OptimizedSessionPersistedObject, HttpSessionBindingListener
+public abstract class BaseOptimizedSessionPersistedObject implements OptimizedSessionPersistedObject
 {
-    private transient boolean dirty;
-
-    public final boolean isSessionPersistedObjectDirty()
-    {
-        return dirty;
-    }
+    private transient AtomicBoolean dirty = new AtomicBoolean(false);
 
-    /**
-     * Invoked by the servlet container when the value is stored (or re-stored)
-     * as an attribute of the session. This
-     * clears the dirty flag. Subclasses may override this method, but should
-     * invoke this implementation.
-     */
-    public void valueBound(HttpSessionBindingEvent event)
-    {
-        dirty = false;
-    }
-
-    /**
-     * Does nothing.
-     */
-    public void valueUnbound(HttpSessionBindingEvent event)
+    public final boolean checkAndResetDirtyMarker()
     {
+        return dirty.getAndSet(false);
     }
 
     /**
-     * Invoked by the subclass whenever the internal state of the object
-     * changes. Typically, this is invoked from
-     * mutator methods.
+     * Invoked by the subclass after internal state of the object changes.
      */
     protected final void markDirty()
     {
-        dirty = true;
+        dirty.set(true);
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2011 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.
@@ -17,9 +17,11 @@ package org.apache.tapestry5;
 /**
  * An optional interface implemented by objects that are persisted in the {@link org.apache.tapestry5.services.Session}.
  * At the end of each request, any objects read from the session are re-stored into the session, to ensure that
- * in-memory changes are flushed to other servers in a cluster. Objects that implement this interface are expected to
- * track when they are dirty (have pending changes), so that the save back into the session can be avoided when not
- * necessary.
+ * in-memory changes are flushed to other persistent session stores (e.g. RDBMS, servers in a cluster, etc). Objects
+ * that implement this interface are expected to track when they are dirty (have pending changes), so that the save
+ * back into the session can be avoided when not necessary.
+ * <p>
+ * This method is accessed concurrently.
  *
  * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject
  * @see org.apache.tapestry5.services.SessionPersistedObjectAnalyzer
@@ -28,10 +30,7 @@ package org.apache.tapestry5;
 public interface OptimizedSessionPersistedObject
 {
     /**
-     * Returns true if the object has in-memory changes.  It is the object's responsibility to set its internal flag to
-     * false, typically by implementing {@link javax.servlet.http.HttpSessionBindingListener}.
-     *
-     * @return
+     * @return true if the object has in-memory changes since the last time this method was called.
      */
-    boolean isSessionPersistedObjectDirty();
+    boolean checkAndResetDirtyMarker();
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java Sat Jun 25 12:31:36 2011
@@ -294,4 +294,16 @@ public class SymbolConstants
      * @since 5.3.0
      */
     public static final String MINIFICATION_ENABLED = "tapestry.enable-minification";
+
+    /**
+     * If "true" then at the end of each request the
+     * {@link org.apache.tapestry5.services.SessionPersistedObjectAnalyzer} will be called on each session persisted
+     * object that was accessed during the request.
+     * <p>
+     * This is provided as a performance enhancement for servers that do not use clustered sessions.
+     *
+     * @since 5.3.1
+     */
+    public static final String CLUSTERED_SESSIONS = "tapestry.clustered-sessions";
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2006, 2008, 2010 The Apache Software Foundation
+// Copyright 2006, 2008, 2010, 2011 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.
@@ -84,8 +84,6 @@ public final class ValidationTrackerImpl
 
     private void store(FieldTracker fieldTracker)
     {
-        markDirty();
-
         if (fieldTrackers == null)
             fieldTrackers = CollectionFactory.newList();
 
@@ -98,15 +96,17 @@ public final class ValidationTrackerImpl
             fieldTrackers.add(fieldTracker);
             fieldToTracker.put(key, fieldTracker);
         }
+
+        markDirty();
     }
 
     public void clear()
     {
-        markDirty();
-
         extraErrors = null;
         fieldTrackers = null;
         fieldToTracker = null;
+
+        markDirty();
     }
 
     public String getError(Field field)
@@ -161,12 +161,12 @@ public final class ValidationTrackerImpl
 
     public void recordError(String errorMessage)
     {
-        markDirty();
-
         if (extraErrors == null)
             extraErrors = CollectionFactory.newList();
 
         extraErrors.add(errorMessage);
+
+        markDirty();
     }
 
     public void recordInput(Field field, String input)

Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java (from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java&r1=1139143&r2=1139536&rev=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java Sat Jun 25 12:31:36 2011
@@ -1,41 +1,33 @@
-// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
+//  Copyright 2011 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
+//  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
+//  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.
+//  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.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.services.Session;
 import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
 import java.util.Map;
 
 /**
- * A thin wrapper around {@link HttpSession}.
+ * A thin wrapper around {@link javax.servlet.http.HttpSession}.
  */
-public class SessionImpl implements Session
+public class ClusteredSessionImpl extends SessionImpl
 {
     private final SessionPersistedObjectAnalyzer analyzer;
 
-    private final HttpSession session;
-
-    private boolean invalidated = false;
-
     /**
      * Cache of attribute objects read from, or written to, the real session.
      * This is needed for end-of-request
@@ -43,77 +35,42 @@ public class SessionImpl implements Sess
      */
     private final Map<String, Object> sessionAttributeCache = CollectionFactory.newMap();
 
-    public SessionImpl(HttpSession session, SessionPersistedObjectAnalyzer analyzer)
+    public ClusteredSessionImpl(
+            HttpServletRequest request,
+            HttpSession session,
+            SessionPersistedObjectAnalyzer analyzer)
     {
-        this.session = session;
+        super(request, session);
         this.analyzer = analyzer;
     }
 
+    @Override
     public Object getAttribute(String name)
     {
-        Object result = session.getAttribute(name);
+        Object result = super.getAttribute(name);
 
         sessionAttributeCache.put(name, result);
 
         return result;
     }
 
-    public List<String> getAttributeNames()
-    {
-        return InternalUtils.toList(session.getAttributeNames());
-    }
-
     public void setAttribute(String name, Object value)
     {
-        session.setAttribute(name, value);
+        super.setAttribute(name, value);
 
         sessionAttributeCache.put(name, value);
     }
 
-    public List<String> getAttributeNames(String prefix)
-    {
-        List<String> result = CollectionFactory.newList();
-
-        Enumeration e = session.getAttributeNames();
-        while (e.hasMoreElements())
-        {
-            String name = (String) e.nextElement();
-
-            if (name.startsWith(prefix)) result.add(name);
-        }
-
-        Collections.sort(result);
-
-        return result;
-    }
-
-    public int getMaxInactiveInterval()
-    {
-        return session.getMaxInactiveInterval();
-    }
-
     public void invalidate()
     {
-        invalidated = true;
-
-        session.invalidate();
+        super.invalidate();
 
         sessionAttributeCache.clear();
     }
 
-    public boolean isInvalidated()
-    {
-        return invalidated;
-    }
-
-    public void setMaxInactiveInterval(int seconds)
-    {
-        session.setMaxInactiveInterval(seconds);
-    }
-
     public void restoreDirtyObjects()
     {
-        if (invalidated) return;
+        if (isInvalidated()) return;
 
         if (sessionAttributeCache.isEmpty()) return;
 
@@ -125,15 +82,9 @@ public class SessionImpl implements Sess
 
             if (attributeValue == null) continue;
 
-            if (analyzer.isDirty(attributeValue))
+            if (analyzer.checkAndResetDirtyState(attributeValue))
             {
-                // TAP5-834: Jetty & Tomcat work by object identity, will not update the attribute
-                // and fire the session binding event unless there's a real change. So we set the
-                // attribute to null and then to the new value and that should force the necessary
-                // notification.
-                
-                session.setAttribute(attributeName, null);
-                session.setAttribute(attributeName, attributeValue);
+                super.setAttribute(attributeName, attributeValue);
             }
         }
     }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2011 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.
@@ -28,14 +28,14 @@ public class DefaultSessionPersistedObje
      * An object is dirty <em>unless</em> it has the {@link org.apache.tapestry5.annotations.ImmutableSessionPersistedObject}
      * annotation.
      *
-     * @param object to analyze
+     * @param sessionPersistedObject to analyze
      * @return false if immutable, true otherwise
      */
-    public boolean isDirty(Object object)
+    public boolean checkAndResetDirtyState(Object sessionPersistedObject)
     {
-        boolean immutable = object.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null;
+        boolean immutable = sessionPersistedObject.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null;
 
-        // Imuutable objects are always clean, others are assumed dirty.
+        // Immutable objects are always clean, others are assumed dirty.
         // Go implement OptimizedSessionPersistedObject if you don't like it.
 
         return !immutable;

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2011 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.
@@ -19,8 +19,8 @@ import org.apache.tapestry5.services.Ses
 
 public class OptimizedSessionPersistedObjectAnalyzer implements SessionPersistedObjectAnalyzer<OptimizedSessionPersistedObject>
 {
-    public boolean isDirty(OptimizedSessionPersistedObject object)
+    public boolean checkAndResetDirtyState(OptimizedSessionPersistedObject sessionPersistedObject)
     {
-        return object.isSessionPersistedObjectDirty();
+        return sessionPersistedObject.checkAndResetDirtyMarker();
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java Sat Jun 25 12:31:36 2011
@@ -17,10 +17,8 @@ package org.apache.tapestry5.internal.se
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 import org.apache.tapestry5.services.Request;
 import org.apache.tapestry5.services.Session;
-import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
 
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpSession;
 import java.io.UnsupportedEncodingException;
 import java.util.List;
 import java.util.Locale;
@@ -39,19 +37,20 @@ public class RequestImpl implements Requ
 
     private final String requestEncoding;
 
-    private final SessionPersistedObjectAnalyzer analyzer;
+    private final SessionFactory sessionFactory;
 
     private boolean encodingSet;
 
-    HttpSession hsession;
-
     Session session;
 
-    public RequestImpl(HttpServletRequest request, String requestEncoding, SessionPersistedObjectAnalyzer analyzer)
+    public RequestImpl(
+            HttpServletRequest request,
+            String requestEncoding,
+            SessionFactory sessionFactory)
     {
         this.request = request;
         this.requestEncoding = requestEncoding;
-        this.analyzer = analyzer;
+        this.sessionFactory = sessionFactory;
     }
 
     public List<String> getParameterNames()
@@ -105,27 +104,15 @@ public class RequestImpl implements Requ
 
     public Session getSession(boolean create)
     {
-        if (session != null)
+        if (session != null && session.isInvalidated())
         {
-            // The easy case is when the session was invalidated through the Tapestry Session
-            // object. The hard case is when the HttpSession was invalidated outside of Tapestry,
-            // in which case, request.getSession() will return a new HttpSession instance (or null)
-
-            if (session.isInvalidated() || hsession != request.getSession(false))
-            {
-                session = null;
-                hsession = null;
-            }
+            session = null;
         }
 
-        if (session == null)
+        if (session == null )
         {
-            hsession = request.getSession(create);
-
-            if (hsession != null)
-            {
-                session = new SessionImpl(hsession, analyzer);
-            }
+            // TAP5-1489 - Re-storage of session attributes at end of request should be configurable
+            session = sessionFactory.getSession(create);
         }
 
         return session;
@@ -149,8 +136,7 @@ public class RequestImpl implements Requ
         try
         {
             request.setCharacterEncoding(requestEncoding);
-        }
-        catch (UnsupportedEncodingException ex)
+        } catch (UnsupportedEncodingException ex)
         {
             throw new RuntimeException(ex);
         }
@@ -198,7 +184,9 @@ public class RequestImpl implements Requ
         return request.getLocalPort();
     }
 
-    /** @since 5.2.5 */
+    /**
+     * @since 5.2.5
+     */
     public int getServerPort()
     {
         return request.getServerPort();

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,22 @@
+//  Copyright 2011 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.services.Session;
+
+public interface SessionFactory
+{
+    Session getSession(boolean create);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,59 @@
+//  Copyright 2011 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.SymbolConstants;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+import org.apache.tapestry5.services.Session;
+import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+/**
+ * User: josh_canfield
+ * Date: 6/25/11
+ */
+public class SessionFactoryImpl implements SessionFactory
+{
+    private boolean clustered;
+    private final SessionPersistedObjectAnalyzer analyzer;
+    private final HttpServletRequest request;
+
+    public SessionFactoryImpl(
+            @Symbol(SymbolConstants.CLUSTERED_SESSIONS)
+            boolean clustered,
+            SessionPersistedObjectAnalyzer analyzer,
+            HttpServletRequest request)
+    {
+        this.clustered = clustered;
+        this.analyzer = analyzer;
+        this.request = request;
+    }
+
+    public Session getSession(boolean create)
+    {
+        final HttpSession httpSession = request.getSession(create);
+
+        if (httpSession == null ) return null;
+
+        if (clustered)
+        {
+            return new ClusteredSessionImpl(request, httpSession, analyzer);
+        }
+
+        return new SessionImpl(request, httpSession);
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011 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.
@@ -19,6 +19,7 @@ import org.apache.tapestry5.ioc.internal
 import org.apache.tapestry5.services.Session;
 import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -30,32 +31,20 @@ import java.util.Map;
  */
 public class SessionImpl implements Session
 {
-    private final SessionPersistedObjectAnalyzer analyzer;
-
+    private final HttpServletRequest request;
     private final HttpSession session;
 
     private boolean invalidated = false;
 
-    /**
-     * Cache of attribute objects read from, or written to, the real session.
-     * This is needed for end-of-request
-     * processing.
-     */
-    private final Map<String, Object> sessionAttributeCache = CollectionFactory.newMap();
-
-    public SessionImpl(HttpSession session, SessionPersistedObjectAnalyzer analyzer)
+    public SessionImpl(HttpServletRequest request, HttpSession session)
     {
+        this.request = request;
         this.session = session;
-        this.analyzer = analyzer;
     }
 
     public Object getAttribute(String name)
     {
-        Object result = session.getAttribute(name);
-
-        sessionAttributeCache.put(name, result);
-
-        return result;
+        return session.getAttribute(name);
     }
 
     public List<String> getAttributeNames()
@@ -66,8 +55,6 @@ public class SessionImpl implements Sess
     public void setAttribute(String name, Object value)
     {
         session.setAttribute(name, value);
-
-        sessionAttributeCache.put(name, value);
     }
 
     public List<String> getAttributeNames(String prefix)
@@ -97,12 +84,18 @@ public class SessionImpl implements Sess
         invalidated = true;
 
         session.invalidate();
-
-        sessionAttributeCache.clear();
     }
 
     public boolean isInvalidated()
     {
+        if (invalidated) return true;
+
+        // The easy case is when the session was invalidated through the Tapestry Session
+        // object. The hard case is when the HttpSession was invalidated outside of Tapestry,
+        // in which case, request.getSession() will return a new HttpSession instance (or null)
+
+        invalidated = request.getSession(false) != session;
+
         return invalidated;
     }
 
@@ -113,28 +106,6 @@ public class SessionImpl implements Sess
 
     public void restoreDirtyObjects()
     {
-        if (invalidated) return;
-
-        if (sessionAttributeCache.isEmpty()) return;
-
-        for (Map.Entry<String, Object> entry : sessionAttributeCache.entrySet())
-        {
-            String attributeName = entry.getKey();
-
-            Object attributeValue = entry.getValue();
 
-            if (attributeValue == null) continue;
-
-            if (analyzer.isDirty(attributeValue))
-            {
-                // TAP5-834: Jetty & Tomcat work by object identity, will not update the attribute
-                // and fire the session binding event unless there's a real change. So we set the
-                // attribute to null and then to the new value and that should force the necessary
-                // notification.
-                
-                session.setAttribute(attributeName, null);
-                session.setAttribute(attributeName, attributeValue);
-            }
-        }
     }
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java Sat Jun 25 12:31:36 2011
@@ -72,8 +72,8 @@ public interface Session
     void invalidate();
 
     /**
-     * Checks to see if the session has been invalidated. Note: this only catches calls to {@link #invalidate()}, not
-     * calls to {@link javax.servlet.http.HttpSession#invalidate()}.
+     * Checks to see if the session has been invalidated. Note: since 5.3.1 this will also catch calls to
+     * {@link javax.servlet.http.HttpSession#invalidate()}.
      * 
      * @since 5.1.0.0
      */

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2008 The Apache Software Foundation
+// Copyright 2008, 2011 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.
@@ -17,10 +17,15 @@ package org.apache.tapestry5.services;
 import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
 
 /**
- * Analyzes a session-persisted object, specifically to see if it is dirty or not.  The service implementation uses a
- * mapped configuration to form a {@linkplain org.apache.tapestry5.ioc.services.StrategyBuilder strategy} based on
- * object type. The service is injectable using the {@link org.apache.tapestry5.ioc.annotations.Primary} marker
- * annotation.
+ * Analyzes a session-persisted object, specifically to see if it is dirty or not.
+ * <p/>
+ * This service is provided to support applications which store mutable session attributes where the
+ * session is replicated to a slower medium (e.g. RDMBS, Cluster, etc) this can help alleviate excessive writes
+ * to the session store while ensuring changes are propagated.
+ * <p/>
+ * The service implementation uses a mapped configuration to form a
+ * {@linkplain org.apache.tapestry5.ioc.services.StrategyBuilder strategy} based on object type. The service may be
+ * injected using the {@link org.apache.tapestry5.ioc.annotations.Primary} marker annotation.
  *
  * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject
  * @see org.apache.tapestry5.OptimizedSessionPersistedObject
@@ -30,11 +35,15 @@ import org.apache.tapestry5.ioc.annotati
 public interface SessionPersistedObjectAnalyzer<T>
 {
     /**
-     * Passed an object (never null) to see if it is dirty or not. Dirty objects that are stored in the session are
-     * re-stored into the session at the end of the request.
+     * Atomically check and reset the dirty state of the session persisted object.
+     * <p/>
+     * The implementer should take consideration for the fact that session attributes are accessed concurrently. A
+     * naive check/set algorithm may allow changes to go un-noticed.
      *
-     * @param object
-     * @return true if object needs to be re-stored into the session
+     * @param sessionPersistedObject the session attribute (never null)
+     * @return true if the object needs to be re-stored into the session
+     * @since 5.3.1
      */
-    boolean isDirty(T object);
+    boolean checkAndResetDirtyState(T sessionPersistedObject);
+
 }

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=1139536&r1=1139535&r2=1139536&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 Sat Jun 25 12:31:36 2011
@@ -355,14 +355,14 @@ public final class TapestryModule
     {
         private final RequestHandler handler;
         private final String applicationCharset;
-        private final SessionPersistedObjectAnalyzer analyzer;
+        private final SessionFactory sessionFactory;
 
         public HttpServletRequestHandlerTerminator(RequestHandler handler, String applicationCharset,
-                SessionPersistedObjectAnalyzer analyzer)
+                SessionFactory sessionFactory)
         {
             this.handler = handler;
             this.applicationCharset = applicationCharset;
-            this.analyzer = analyzer;
+            this.sessionFactory = sessionFactory;
         }
 
         public boolean service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
@@ -370,7 +370,7 @@ public final class TapestryModule
         {
             requestGlobals.storeServletRequestResponse(servletRequest, servletResponse);
 
-            Request request = new RequestImpl(servletRequest, applicationCharset, analyzer);
+            Request request = new RequestImpl(servletRequest, applicationCharset, sessionFactory);
             Response response = new ResponseImpl(servletRequest, servletResponse);
 
             // TAP5-257: Make sure that the "initial guess" for request/response
@@ -484,6 +484,7 @@ public final class TapestryModule
         binder.bind(ContextPathEncoder.class, ContextPathEncoderImpl.class);
         binder.bind(ApplicationStatePersistenceStrategy.class, SessionApplicationStatePersistenceStrategy.class)
                 .withId("SessionApplicationStatePersistenceStrategy");
+        binder.bind(SessionFactory.class, SessionFactoryImpl.class);
         binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
         binder.bind(NumericTranslatorSupport.class);
         binder.bind(ClientDataEncoder.class, ClientDataEncoderImpl.class);
@@ -1115,7 +1116,7 @@ public final class TapestryModule
      * Adds coercions:
      * <ul>
      * <li>String to {@link SelectModel}
-     * <li>Map to {@link oSelectModel}
+     * <li>Map to {@link SelectModel}
      * <li>Collection to {@link GridDataSource}
      * <li>null to {@link GridDataSource}
      * <li>List to {@link SelectModel}
@@ -1516,11 +1517,10 @@ public final class TapestryModule
     @Symbol(SymbolConstants.CHARSET)
     String applicationCharset,
 
-    @Primary
-    SessionPersistedObjectAnalyzer analyzer)
+    SessionFactory sessionFactory)
     {
         HttpServletRequestHandler terminator = new HttpServletRequestHandlerTerminator(handler, applicationCharset,
-                analyzer);
+                sessionFactory);
 
         return pipelineBuilder.build(logger, HttpServletRequestHandler.class, HttpServletRequestFilter.class,
                 configuration, terminator);
@@ -2457,6 +2457,8 @@ public final class TapestryModule
 
         configuration.add(SymbolConstants.PRODUCTION_MODE, true);
 
+        configuration.add(SymbolConstants.CLUSTERED_SESSIONS, true);
+
         configuration.add(SymbolConstants.COMPRESS_WHITESPACE, true);
 
         configuration.add(MetaDataConstants.SECURE_PAGE, false);
@@ -2704,7 +2706,7 @@ public final class TapestryModule
     }
 
     /**
-     * The master SessionPesistedObjectAnalyzer.
+     * The master SessionPersistedObjectAnalyzer.
      * 
      * @since 5.1.0.0
      */
@@ -2730,7 +2732,7 @@ public final class TapestryModule
 
         SessionPersistedObjectAnalyzer<Object> immutable = new SessionPersistedObjectAnalyzer<Object>()
         {
-            public boolean isDirty(Object object)
+            public boolean checkAndResetDirtyState(Object sessionPersistedObject)
             {
                 return false;
             }

Copied: tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml (from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml)
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml?p2=tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml&p1=tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml&r1=1139143&r2=1139536&rev=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml Sat Jun 25 12:31:36 2011
@@ -3,10 +3,10 @@
         PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
         "http://java.sun.com/dtd/web-app_2_3.dtd">
 <web-app>
-  <display-name>Integration Test App 5 -- Skinning</display-name>
+  <display-name>Integration Test Cluster</display-name>
   <context-param>
     <param-name>tapestry.app-package</param-name>
-    <param-value>org.apache.tapestry5.integration.app5</param-value>
+    <param-value>org.apache.tapestry5.integration.cluster</param-value>
   </context-param>
   <filter>
     <filter-name>app</filter-name>

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,248 @@
+//  Copyright 2011 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.integration.cluster;
+
+import com.thoughtworks.selenium.DefaultSelenium;
+import com.thoughtworks.selenium.Selenium;
+import org.apache.tapestry5.test.Jetty7Runner;
+import org.apache.tapestry5.test.TapestryTestConstants;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.session.JDBCSessionIdManager;
+import org.eclipse.jetty.server.session.JDBCSessionManager;
+import org.eclipse.jetty.server.session.SessionHandler;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.openqa.selenium.server.RemoteControlConfiguration;
+import org.openqa.selenium.server.SeleniumServer;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+import org.testng.xml.XmlTest;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+
+/**
+ * User: josh_canfield Date: 6/24/11
+ */
+public class ClusterTests
+{
+    private static final String FIREFOX_BROWSER_CMD = "*firefox";
+    private static final int SERVER_A_PORT = 9091;
+    private static final int SERVER_B_PORT = 9092;
+    private static final String SERVER_A_NAME = "server_A";
+    private static final String SERVER_B_NAME = "server_B";
+
+    private static final String CREATE_1 = "//a[contains(text(),'create1')]";
+    private static final String VALUE_1 = "value1";
+
+    private static final String CREATE_2 = "//a[contains(text(),'create2')]";
+    private static final String VALUE_2 = "value2";
+
+    private static final String UPDATE_1 = "//a[contains(text(),'update1')]";
+    private static final String VALUE_3 = "value3";
+
+    private static final String UPDATE_2 = "//a[contains(text(),'update2')]";
+    private static final String VALUE_4 = "value4";
+
+    private static final String CLEAR = "//a[contains(text(),'Clear')]";
+
+    Jetty7Runner serverA;
+
+    Jetty7Runner serverB;
+
+    SeleniumServer seleniumServer;
+    Selenium selenium;
+
+    @BeforeClass
+    void setupServers(XmlTest xmlTest) throws Exception
+    {
+        createJettySessionsTable();
+
+        serverA = configureClusteredJetty(SERVER_A_NAME, SERVER_A_PORT);
+        serverB = configureClusteredJetty(SERVER_B_NAME, SERVER_B_PORT);
+
+        seleniumServer = new SeleniumServer();
+        seleniumServer.start();
+
+        String browserStartCommand = xmlTest.getParameter(TapestryTestConstants.BROWSER_START_COMMAND_PARAMETER);
+        browserStartCommand = browserStartCommand != null ? browserStartCommand : FIREFOX_BROWSER_CMD;
+
+        selenium = new DefaultSelenium(
+                "localhost", RemoteControlConfiguration.DEFAULT_PORT,
+                browserStartCommand, "http://localhost:9091/"
+        );
+        selenium.start();
+    }
+
+    @AfterClass
+    void stopServers()
+    {
+        serverA.stop();
+        serverB.stop();
+        selenium.stop();
+        seleniumServer.stop();
+    }
+
+    @Test
+    public void mutable_pojo_as_session_state_is_always_shared()
+    {
+        // Expect all object changes to be transferred to the other server.
+        String[][] click = {
+                {CREATE_1, VALUE_1, VALUE_1},
+                {CLEAR, "", ""},
+                {CREATE_2, VALUE_2, VALUE_2},
+                {UPDATE_1, VALUE_3, VALUE_3},
+                {UPDATE_2, VALUE_4, VALUE_4}
+        };
+
+        evaluate("PersistedMutablePojoDemo", click);
+    }
+
+    @Test
+    public void immutable_session_persisted_object() throws InterruptedException
+    {
+        // expect only create links to transfer over (We've told tapestry it's immutable)
+        String[][] data = {
+                {CREATE_1, VALUE_1, VALUE_1},
+                {UPDATE_1, VALUE_3, VALUE_1},
+                {CLEAR, "", ""},
+                {CREATE_2, VALUE_2, VALUE_2},
+                {UPDATE_2, VALUE_4, VALUE_2},
+                {UPDATE_1, VALUE_3, VALUE_2},
+        };
+
+        evaluate("ImmutableSessionPersistedObjectDemo", data);
+    }
+
+    @Test
+    public void session_persisted_object_analyzer() throws InterruptedException
+    {
+        // special cased so that only UPDATE_2 marks as dirty, creates/deletes still transfer
+        String[][] data = {
+                {CREATE_1, VALUE_1, VALUE_1}, // created, session transferred
+                {UPDATE_1, VALUE_3, VALUE_1}, // update-1 doesn't transfer
+                {CLEAR, "", ""},
+                {CREATE_2, VALUE_2, VALUE_2}, // create-2, session transferred
+                {UPDATE_2, VALUE_4, VALUE_4}, // update-2, session transferred
+                {UPDATE_1, VALUE_3, VALUE_4}, // update-1, doesn't transfer
+        };
+
+        evaluate("SessionPersistedObjectAnalyzerDemo", data);
+    }
+
+    private void evaluate(String page, String[][] expect)
+    {
+        for (String[] strings : expect)
+        {
+            openOnServerA(page);
+
+            clickAndWait(strings[0]);
+
+            assertText("value", strings[1]);
+
+            openOnServerB(page);
+
+            assertText("value", strings[2]);
+        }
+
+        clickAndWait(CLEAR);
+        assertText("value", "");
+    }
+
+    private void openOnServerA(String page)
+    {
+        selenium.open("http://localhost:" + SERVER_A_PORT + "/" + page);
+        assertServerName(SERVER_A_NAME);
+    }
+
+    private void openOnServerB(String page)
+    {
+        selenium.open("http://localhost:" + SERVER_B_PORT + "/" + page);
+        assertServerName(SERVER_B_NAME);
+    }
+
+    private void assertServerName(String serverName)
+    {
+        assertTrue(selenium.isElementPresent("//h1[@id='serverName' and text()='" + serverName + "']"));
+    }
+
+    private void assertText(String locator, String expected)
+    {
+        assertEquals(expected, selenium.getText(locator));
+    }
+
+    private void clickAndWait(String s)
+    {
+        selenium.click(s);
+        selenium.waitForPageToLoad("5000");
+    }
+
+    private Jetty7Runner configureClusteredJetty(String name, int port) throws Exception
+    {
+        Jetty7Runner runner = new Jetty7Runner();
+
+        runner.configure("src/test/cluster", "", port, port + 100);
+
+        JDBCSessionIdManager idMgr = new JDBCSessionIdManager(runner.getServer());
+        idMgr.setWorkerName(name);
+        idMgr.setDriverInfo("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:clustertest");
+
+        Server server = runner.getServer();
+        server.setSessionIdManager(idMgr);
+
+        WebAppContext wac = (WebAppContext) server.getHandler();
+
+        JDBCSessionManager jdbcMgr = new JDBCSessionManager();
+        jdbcMgr.setIdManager(server.getSessionIdManager());
+
+        // force the session to be read from the database with no delay
+        // This is an incorrectly documented feature.
+        jdbcMgr.setSaveInterval(0);
+
+        wac.setSessionHandler(new SessionHandler(jdbcMgr));
+        wac.getServletContext().setInitParameter("cluster.name", name);
+        runner.start();
+        return runner;
+    }
+
+    private void createJettySessionsTable() throws ClassNotFoundException, SQLException
+    {
+        Class.forName("org.hsqldb.jdbcDriver");
+
+        Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:clustertest", "sa", "");
+        String sql = "create table JettySessions (" +
+                "rowId varchar(60), " +
+                "sessionId varchar(60)," +
+                "contextPath varchar(60)," +
+                "virtualHost varchar(60)," +
+                "lastNode varchar(60)," +
+                "accessTime bigint," +
+                "lastAccessTime bigint," +
+                "createTime bigint," +
+                "cookieTime bigint," +
+                "lastSavedTime bigint," +
+                "expiryTime bigint," +
+                "map longvarbinary" +
+                ");";
+        Statement statement = c.createStatement();
+        statement.execute(sql);
+        statement.close();
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,41 @@
+//  Copyright 2011 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.integration.cluster.base;
+
+import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.integration.cluster.data.SessionStateObject;
+
+public abstract class BaseSessionDemo<T extends SessionStateObject> {
+
+    @Property
+    @Persist
+    private SessionStateObject data;
+
+    abstract public T create(String value);
+
+    void onCreateValue(String value) {
+        data = create(value);
+    }
+
+    void onChangeValue(String value) {
+        data.setValue(value);
+    }
+
+    void onClear() {
+        data = null;
+    }
+
+}
\ No newline at end of file

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,45 @@
+// Copyright 2011 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.integration.cluster.data;
+
+public class AnalyzedSessionObject implements SessionStateObject
+{
+    private String value;
+
+    boolean dirty = false;
+
+    public AnalyzedSessionObject(String value)
+    {
+        this.value = value;
+    }
+
+    public boolean checkAndResetDirtyState()
+    {
+        boolean check = dirty;
+        dirty = false;
+        return check;
+    }
+
+    public String getValue()
+    {
+        return value;
+    }
+
+    public void setValue(String value)
+    {
+        this.value = value;
+        dirty = "value4".equals(value);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,34 @@
+//  Copyright 2011 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.integration.cluster.data;
+
+import org.apache.tapestry5.annotations.ImmutableSessionPersistedObject;
+
+@ImmutableSessionPersistedObject
+public class ImmutableByAnnotation implements SessionStateObject {
+    private String value;
+
+    public ImmutableByAnnotation(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,32 @@
+//  Copyright 2011 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.integration.cluster.data;
+
+public class MutablePojo implements SessionStateObject {
+
+    private String value;
+
+    public MutablePojo(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String name) {
+        this.value = name;
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,24 @@
+//  Copyright 2011 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.integration.cluster.data;
+
+import java.io.Serializable;
+
+public interface SessionStateObject extends Serializable {
+
+    String getValue();
+
+    void setValue(String v);
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,29 @@
+//  Copyright 2011 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.integration.cluster.pages;
+
+import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
+import org.apache.tapestry5.integration.cluster.data.ImmutableByAnnotation;
+
+/**
+ * User: josh_canfield
+ * Date: 6/24/11
+ */
+public class ImmutableSessionPersistedObjectDemo extends BaseSessionDemo<ImmutableByAnnotation> {
+    @Override
+    public ImmutableByAnnotation create(String value) {
+        return new ImmutableByAnnotation(value);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,26 @@
+//  Copyright 2011 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.integration.cluster.pages;
+
+import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
+import org.apache.tapestry5.integration.cluster.data.MutablePojo;
+
+public class PersistedMutablePojoDemo extends BaseSessionDemo<MutablePojo> {
+
+    @Override
+    public MutablePojo create(String value) {
+        return new MutablePojo(value);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,25 @@
+//  Copyright 2011 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.integration.cluster.pages;
+
+import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
+import org.apache.tapestry5.integration.cluster.data.AnalyzedSessionObject;
+
+public class SessionPersistedObjectAnalyzerDemo extends BaseSessionDemo<AnalyzedSessionObject> {
+    @Override
+    public AnalyzedSessionObject create(String value) {
+        return new AnalyzedSessionObject(value);
+    }
+}

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java Sat Jun 25 12:31:36 2011
@@ -0,0 +1,36 @@
+//  Copyright 2011 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.integration.cluster.services;
+
+import org.apache.tapestry5.integration.cluster.data.AnalyzedSessionObject;
+import org.apache.tapestry5.ioc.MappedConfiguration;
+import org.apache.tapestry5.ioc.annotations.Contribute;
+import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
+
+public class AppModule
+{
+
+    @Contribute(SessionPersistedObjectAnalyzer.class)
+    public static void analyzer(MappedConfiguration<Class, SessionPersistedObjectAnalyzer> configuration)
+    {
+        configuration.add(AnalyzedSessionObject.class, new SessionPersistedObjectAnalyzer<AnalyzedSessionObject>()
+        {
+            public boolean checkAndResetDirtyState(AnalyzedSessionObject sessionPersistedObject)
+            {
+                return sessionPersistedObject.checkAndResetDirtyState();
+            }
+        });
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java Sat Jun 25 12:31:36 2011
@@ -33,12 +33,13 @@ public class RequestImplTest extends Int
     public void get_session_doesnt_exist()
     {
         HttpServletRequest sr = mockHttpServletRequest();
+        SessionFactory sf = newMock(SessionFactory.class);
 
-        train_getSession(sr, false, null);
+        expect(sf.getSession(false)).andReturn(null);
 
         replay();
 
-        Request request = new RequestImpl(sr, CHARSET, null);
+        Request request = new RequestImpl(sr, CHARSET, sf);
 
         assertNull(request.getSession(false));
 
@@ -50,14 +51,15 @@ public class RequestImplTest extends Int
     {
         HttpServletRequest sr = mockHttpServletRequest();
         HttpSession ss = mockHttpSession();
+        SessionFactory sf = newMock(SessionFactory.class);
 
-        train_getSession(sr, true, ss);
+        expect(sf.getSession(true)).andReturn(new SessionImpl(sr, ss));
 
         train_getAttribute(ss, "foo", "bar");
 
         replay();
 
-        Request request = new RequestImpl(sr, CHARSET, null);
+        Request request = new RequestImpl(sr, CHARSET, sf);
         Session session = request.getSession(true);
 
         assertEquals(session.getAttribute("foo"), "bar");
@@ -202,11 +204,13 @@ public class RequestImplTest extends Int
         HttpSession hsession1 = mockHttpSession();
         HttpSession hsession2 = mockHttpSession();
 
-        train_getSession(sr, true, hsession1);
+        SessionFactory sf = newMock(SessionFactory.class);
+
+        expect(sf.getSession(true)).andReturn(new SessionImpl(sr,hsession1));
 
         replay();
 
-        Request request = new RequestImpl(sr, CHARSET, null);
+        Request request = new RequestImpl(sr, CHARSET, sf);
 
         Session session1 = request.getSession(true);
 
@@ -214,8 +218,11 @@ public class RequestImplTest extends Int
 
         hsession1.invalidate();
 
-        train_getSession(sr, false, hsession2);
-        train_getSession(sr, true, hsession2);
+        expect(sr.getSession(false)).andReturn(null);
+
+        SessionImpl session = new SessionImpl(sr, hsession2);
+        expect(sf.getSession(true)).andReturn(session);
+        expect(sf.getSession(true)).andReturn(session);
 
         replay();
 

Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java?rev=1139536&r1=1139535&r2=1139536&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java Sat Jun 25 12:31:36 2011
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2009, 2011 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.
@@ -18,6 +18,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
 
 import org.apache.tapestry5.internal.test.InternalBaseTestCase;
@@ -37,7 +38,7 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, null);
+        Session session = new SessionImpl(null, hs);
 
         assertEquals(session.getAttributeNames(), Arrays.asList("barney", "fred"));
 
@@ -54,7 +55,7 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, null);
+        Session session = new SessionImpl(null, hs);
 
         assertEquals(session.getAttributeNames("f"), Arrays.asList("fanny", "fred"));
 
@@ -70,7 +71,7 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, null);
+        Session session = new SessionImpl(null, hs);
 
         session.invalidate();
 
@@ -78,6 +79,39 @@ public class SessionImplTest extends Int
     }
 
     @Test
+    public void http_session_invalidate()
+    {
+        HttpSession hs = mockHttpSession();
+
+        HttpServletRequest hsr = mockHttpServletRequest();
+
+        train_getSession(hsr, false, hs);
+
+        replay();
+
+        Session session = new SessionImpl(hsr, hs);
+
+        assertFalse(session.isInvalidated());
+
+        verify();
+
+        train_getSession(hsr, false, null);
+
+        replay();
+
+        assertTrue(session.isInvalidated());
+
+        verify();
+
+        train_getSession(hsr, false, mockHttpSession());
+
+        replay();
+
+        assertTrue(session.isInvalidated());
+
+    }
+
+    @Test
     public void set_max_inactive()
     {
         HttpSession hs = mockHttpSession();
@@ -87,7 +121,7 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, null);
+        Session session = new SessionImpl(null, hs);
 
         session.setMaxInactiveInterval(seconds);
 
@@ -104,7 +138,7 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, null);
+        Session session = new SessionImpl(null, hs);
 
         assertEquals(session.getMaxInactiveInterval(), seconds);
 
@@ -115,6 +149,7 @@ public class SessionImplTest extends Int
     public void dirty_persisted_object_is_forced_to_update()
     {
         HttpSession hs = mockHttpSession();
+        HttpServletRequest hsr = mockHttpServletRequest();
         SessionPersistedObjectAnalyzer analyzer = newMock(SessionPersistedObjectAnalyzer.class);
         Object dirty = new Object();
 
@@ -122,15 +157,16 @@ public class SessionImplTest extends Int
 
         replay();
 
-        Session session = new SessionImpl(hs, analyzer);
+        Session session = new ClusteredSessionImpl(hsr, hs, analyzer);
 
         assertSame(session.getAttribute("dirty"), dirty);
 
         verify();
 
-        expect(analyzer.isDirty(dirty)).andReturn(true);
+        expect(analyzer.checkAndResetDirtyState(dirty)).andReturn(true);
+
+        train_getSession(hsr, false, hs);
 
-        hs.setAttribute("dirty", null);
         hs.setAttribute("dirty", dirty);
 
         replay();

Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml?rev=1139536&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml Sat Jun 25 12:31:36 2011
@@ -0,0 +1,21 @@
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
+<head>
+    <title>Immutable Annotation Demo</title>
+</head>
+
+<body>
+<h1 id="serverName">${symbol:cluster.name}</h1>
+
+<div id="value">${data?.value}</div>
+
+<t:eventlink event="clear">Clear</t:eventlink>
+
+<t:eventlink event="createValue" context="'value1'">create1</t:eventlink>
+<t:eventlink event="createValue" context="'value2'">create2</t:eventlink>
+
+<t:eventlink event="changeValue" context="'value3'">update1</t:eventlink>
+<t:eventlink event="changeValue" context="'value4'">update2</t:eventlink>
+
+</body>
+
+</html>
\ No newline at end of file



Re: svn commit: r1139536 - in /tapestry/tapestry5/trunk/tapestry-core: ./ src/main/java/org/apache/tapestry5/ src/main/java/org/apache/tapestry5/internal/services/ src/main/java/org/apache/tapestry5/services/ src/test/cluster/ src/test/cluster/WEB-INF/ src...

Posted by Ulrich Stärk <ul...@spielviel.de>.
Nice one. In the future please make sure to correctly state the issue number (you omitted the 5
after TAP) so that we have them linked from JIRA. This makes finding the corresponding code changes
for an issue a lot easier!

Uli

On 25.06.2011 14:31, joshcanfield@apache.org wrote:
> Author: joshcanfield
> Date: Sat Jun 25 12:31:36 2011
> New Revision: 1139536
>
> URL: http://svn.apache.org/viewvc?rev=1139536&view=rev
> Log:
> TAP-1489 - Created a SessionFactory which determines which implementation to provide to the request.
>  - Updated Session object to be able to report if it's been invalidated outside of tapestry
>  
> TAP-1355 - Removed code that set session attributes to null to force a HttpSessionBindingListener event. 
> - Created mini-cluster integration test to ensure proper session storage with different approaches.
> - Changed isDirty flags to atomic checkAndSet style to avoid missed updates
>
> Added:
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java
>       - copied, changed from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml
>       - copied, changed from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/
>     tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml
> Modified:
>     tapestry/tapestry5/trunk/tapestry-core/build.gradle
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java
>     tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.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/RequestImplTest.java
>     tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/build.gradle
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/build.gradle?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/build.gradle (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/build.gradle Sat Jun 25 12:31:36 2011
> @@ -23,6 +23,8 @@ dependencies {
>  
>    // Antlr3 tool path used with the antlr3 task
>    antlr3 "org.antlr:antlr:3.3"
> +
> +  testRuntime "org.hsqldb:hsqldb:1.8.0.10"
>  }
>  
>  // This may spin out as a plugin once we've got the details down pat
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/BaseOptimizedSessionPersistedObject.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2008, 2009 The Apache Software Foundation
> +// Copyright 2008, 2009, 2011 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,57 +14,33 @@
>  
>  package org.apache.tapestry5;
>  
> -import javax.servlet.http.HttpSessionBindingEvent;
> -import javax.servlet.http.HttpSessionBindingListener;
> +import java.util.concurrent.atomic.AtomicBoolean;
>  
>  /**
>   * Base implementation of
>   * {@link org.apache.tapestry5.OptimizedSessionPersistedObject}. Subclasses
> - * should invoke {@link #markDirty()} when internal state of the object changes.
> + * should invoke {@link #markDirty()} after the internal state of the object changes.
> + * <p>
> + * Due to the concurrent nature of session attributes it's important that markDirty occurs <strong>after</strong>
> + * the object has been changed. If the change occurs before the object has been mutated it's possible that another
> + * thread may re-store the object before the changes are actually made!
>   * <p>
> - * Note that (due to TAP5-834), the object will receive a spurious
> - * <code>valueUnbound()</code> notification when dirty. Tapestry sets dirty
> - * session attributes to null, then to the persisted object, to force a
> - * <code>valueBound()</code> notification, and that unfortunately also sends the
> - * <code>valueUnbound()</code>.
> - * 
>   * @since 5.1.1.0
>   */
> -public abstract class BaseOptimizedSessionPersistedObject implements
> -        OptimizedSessionPersistedObject, HttpSessionBindingListener
> +public abstract class BaseOptimizedSessionPersistedObject implements OptimizedSessionPersistedObject
>  {
> -    private transient boolean dirty;
> -
> -    public final boolean isSessionPersistedObjectDirty()
> -    {
> -        return dirty;
> -    }
> +    private transient AtomicBoolean dirty = new AtomicBoolean(false);
>  
> -    /**
> -     * Invoked by the servlet container when the value is stored (or re-stored)
> -     * as an attribute of the session. This
> -     * clears the dirty flag. Subclasses may override this method, but should
> -     * invoke this implementation.
> -     */
> -    public void valueBound(HttpSessionBindingEvent event)
> -    {
> -        dirty = false;
> -    }
> -
> -    /**
> -     * Does nothing.
> -     */
> -    public void valueUnbound(HttpSessionBindingEvent event)
> +    public final boolean checkAndResetDirtyMarker()
>      {
> +        return dirty.getAndSet(false);
>      }
>  
>      /**
> -     * Invoked by the subclass whenever the internal state of the object
> -     * changes. Typically, this is invoked from
> -     * mutator methods.
> +     * Invoked by the subclass after internal state of the object changes.
>       */
>      protected final void markDirty()
>      {
> -        dirty = true;
> +        dirty.set(true);
>      }
>  }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/OptimizedSessionPersistedObject.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2008 The Apache Software Foundation
> +// Copyright 2008, 2011 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.
> @@ -17,9 +17,11 @@ package org.apache.tapestry5;
>  /**
>   * An optional interface implemented by objects that are persisted in the {@link org.apache.tapestry5.services.Session}.
>   * At the end of each request, any objects read from the session are re-stored into the session, to ensure that
> - * in-memory changes are flushed to other servers in a cluster. Objects that implement this interface are expected to
> - * track when they are dirty (have pending changes), so that the save back into the session can be avoided when not
> - * necessary.
> + * in-memory changes are flushed to other persistent session stores (e.g. RDBMS, servers in a cluster, etc). Objects
> + * that implement this interface are expected to track when they are dirty (have pending changes), so that the save
> + * back into the session can be avoided when not necessary.
> + * <p>
> + * This method is accessed concurrently.
>   *
>   * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject
>   * @see org.apache.tapestry5.services.SessionPersistedObjectAnalyzer
> @@ -28,10 +30,7 @@ package org.apache.tapestry5;
>  public interface OptimizedSessionPersistedObject
>  {
>      /**
> -     * Returns true if the object has in-memory changes.  It is the object's responsibility to set its internal flag to
> -     * false, typically by implementing {@link javax.servlet.http.HttpSessionBindingListener}.
> -     *
> -     * @return
> +     * @return true if the object has in-memory changes since the last time this method was called.
>       */
> -    boolean isSessionPersistedObjectDirty();
> +    boolean checkAndResetDirtyMarker();
>  }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java Sat Jun 25 12:31:36 2011
> @@ -294,4 +294,16 @@ public class SymbolConstants
>       * @since 5.3.0
>       */
>      public static final String MINIFICATION_ENABLED = "tapestry.enable-minification";
> +
> +    /**
> +     * If "true" then at the end of each request the
> +     * {@link org.apache.tapestry5.services.SessionPersistedObjectAnalyzer} will be called on each session persisted
> +     * object that was accessed during the request.
> +     * <p>
> +     * This is provided as a performance enhancement for servers that do not use clustered sessions.
> +     *
> +     * @since 5.3.1
> +     */
> +    public static final String CLUSTERED_SESSIONS = "tapestry.clustered-sessions";
> +
>  }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/ValidationTrackerImpl.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2006, 2008, 2010 The Apache Software Foundation
> +// Copyright 2006, 2008, 2010, 2011 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.
> @@ -84,8 +84,6 @@ public final class ValidationTrackerImpl
>  
>      private void store(FieldTracker fieldTracker)
>      {
> -        markDirty();
> -
>          if (fieldTrackers == null)
>              fieldTrackers = CollectionFactory.newList();
>  
> @@ -98,15 +96,17 @@ public final class ValidationTrackerImpl
>              fieldTrackers.add(fieldTracker);
>              fieldToTracker.put(key, fieldTracker);
>          }
> +
> +        markDirty();
>      }
>  
>      public void clear()
>      {
> -        markDirty();
> -
>          extraErrors = null;
>          fieldTrackers = null;
>          fieldToTracker = null;
> +
> +        markDirty();
>      }
>  
>      public String getError(Field field)
> @@ -161,12 +161,12 @@ public final class ValidationTrackerImpl
>  
>      public void recordError(String errorMessage)
>      {
> -        markDirty();
> -
>          if (extraErrors == null)
>              extraErrors = CollectionFactory.newList();
>  
>          extraErrors.add(errorMessage);
> +
> +        markDirty();
>      }
>  
>      public void recordInput(Field field, String input)
>
> Copied: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java (from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java)
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java?p2=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java&p1=tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java&r1=1139143&r2=1139536&rev=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ClusteredSessionImpl.java Sat Jun 25 12:31:36 2011
> @@ -1,41 +1,33 @@
> -// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
> +//  Copyright 2011 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
> +//  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
> +//  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.
> +//  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.ioc.internal.util.CollectionFactory;
> -import org.apache.tapestry5.ioc.internal.util.InternalUtils;
> -import org.apache.tapestry5.services.Session;
>  import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
>  
> +import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpSession;
> -import java.util.Collections;
> -import java.util.Enumeration;
> -import java.util.List;
>  import java.util.Map;
>  
>  /**
> - * A thin wrapper around {@link HttpSession}.
> + * A thin wrapper around {@link javax.servlet.http.HttpSession}.
>   */
> -public class SessionImpl implements Session
> +public class ClusteredSessionImpl extends SessionImpl
>  {
>      private final SessionPersistedObjectAnalyzer analyzer;
>  
> -    private final HttpSession session;
> -
> -    private boolean invalidated = false;
> -
>      /**
>       * Cache of attribute objects read from, or written to, the real session.
>       * This is needed for end-of-request
> @@ -43,77 +35,42 @@ public class SessionImpl implements Sess
>       */
>      private final Map<String, Object> sessionAttributeCache = CollectionFactory.newMap();
>  
> -    public SessionImpl(HttpSession session, SessionPersistedObjectAnalyzer analyzer)
> +    public ClusteredSessionImpl(
> +            HttpServletRequest request,
> +            HttpSession session,
> +            SessionPersistedObjectAnalyzer analyzer)
>      {
> -        this.session = session;
> +        super(request, session);
>          this.analyzer = analyzer;
>      }
>  
> +    @Override
>      public Object getAttribute(String name)
>      {
> -        Object result = session.getAttribute(name);
> +        Object result = super.getAttribute(name);
>  
>          sessionAttributeCache.put(name, result);
>  
>          return result;
>      }
>  
> -    public List<String> getAttributeNames()
> -    {
> -        return InternalUtils.toList(session.getAttributeNames());
> -    }
> -
>      public void setAttribute(String name, Object value)
>      {
> -        session.setAttribute(name, value);
> +        super.setAttribute(name, value);
>  
>          sessionAttributeCache.put(name, value);
>      }
>  
> -    public List<String> getAttributeNames(String prefix)
> -    {
> -        List<String> result = CollectionFactory.newList();
> -
> -        Enumeration e = session.getAttributeNames();
> -        while (e.hasMoreElements())
> -        {
> -            String name = (String) e.nextElement();
> -
> -            if (name.startsWith(prefix)) result.add(name);
> -        }
> -
> -        Collections.sort(result);
> -
> -        return result;
> -    }
> -
> -    public int getMaxInactiveInterval()
> -    {
> -        return session.getMaxInactiveInterval();
> -    }
> -
>      public void invalidate()
>      {
> -        invalidated = true;
> -
> -        session.invalidate();
> +        super.invalidate();
>  
>          sessionAttributeCache.clear();
>      }
>  
> -    public boolean isInvalidated()
> -    {
> -        return invalidated;
> -    }
> -
> -    public void setMaxInactiveInterval(int seconds)
> -    {
> -        session.setMaxInactiveInterval(seconds);
> -    }
> -
>      public void restoreDirtyObjects()
>      {
> -        if (invalidated) return;
> +        if (isInvalidated()) return;
>  
>          if (sessionAttributeCache.isEmpty()) return;
>  
> @@ -125,15 +82,9 @@ public class SessionImpl implements Sess
>  
>              if (attributeValue == null) continue;
>  
> -            if (analyzer.isDirty(attributeValue))
> +            if (analyzer.checkAndResetDirtyState(attributeValue))
>              {
> -                // TAP5-834: Jetty & Tomcat work by object identity, will not update the attribute
> -                // and fire the session binding event unless there's a real change. So we set the
> -                // attribute to null and then to the new value and that should force the necessary
> -                // notification.
> -                
> -                session.setAttribute(attributeName, null);
> -                session.setAttribute(attributeName, attributeValue);
> +                super.setAttribute(attributeName, attributeValue);
>              }
>          }
>      }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DefaultSessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2008 The Apache Software Foundation
> +// Copyright 2008, 2011 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.
> @@ -28,14 +28,14 @@ public class DefaultSessionPersistedObje
>       * An object is dirty <em>unless</em> it has the {@link org.apache.tapestry5.annotations.ImmutableSessionPersistedObject}
>       * annotation.
>       *
> -     * @param object to analyze
> +     * @param sessionPersistedObject to analyze
>       * @return false if immutable, true otherwise
>       */
> -    public boolean isDirty(Object object)
> +    public boolean checkAndResetDirtyState(Object sessionPersistedObject)
>      {
> -        boolean immutable = object.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null;
> +        boolean immutable = sessionPersistedObject.getClass().getAnnotation(ImmutableSessionPersistedObject.class) != null;
>  
> -        // Imuutable objects are always clean, others are assumed dirty.
> +        // Immutable objects are always clean, others are assumed dirty.
>          // Go implement OptimizedSessionPersistedObject if you don't like it.
>  
>          return !immutable;
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/OptimizedSessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2008 The Apache Software Foundation
> +// Copyright 2008, 2011 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.
> @@ -19,8 +19,8 @@ import org.apache.tapestry5.services.Ses
>  
>  public class OptimizedSessionPersistedObjectAnalyzer implements SessionPersistedObjectAnalyzer<OptimizedSessionPersistedObject>
>  {
> -    public boolean isDirty(OptimizedSessionPersistedObject object)
> +    public boolean checkAndResetDirtyState(OptimizedSessionPersistedObject sessionPersistedObject)
>      {
> -        return object.isSessionPersistedObjectDirty();
> +        return sessionPersistedObject.checkAndResetDirtyMarker();
>      }
>  }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/RequestImpl.java Sat Jun 25 12:31:36 2011
> @@ -17,10 +17,8 @@ package org.apache.tapestry5.internal.se
>  import org.apache.tapestry5.ioc.internal.util.InternalUtils;
>  import org.apache.tapestry5.services.Request;
>  import org.apache.tapestry5.services.Session;
> -import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
>  
>  import javax.servlet.http.HttpServletRequest;
> -import javax.servlet.http.HttpSession;
>  import java.io.UnsupportedEncodingException;
>  import java.util.List;
>  import java.util.Locale;
> @@ -39,19 +37,20 @@ public class RequestImpl implements Requ
>  
>      private final String requestEncoding;
>  
> -    private final SessionPersistedObjectAnalyzer analyzer;
> +    private final SessionFactory sessionFactory;
>  
>      private boolean encodingSet;
>  
> -    HttpSession hsession;
> -
>      Session session;
>  
> -    public RequestImpl(HttpServletRequest request, String requestEncoding, SessionPersistedObjectAnalyzer analyzer)
> +    public RequestImpl(
> +            HttpServletRequest request,
> +            String requestEncoding,
> +            SessionFactory sessionFactory)
>      {
>          this.request = request;
>          this.requestEncoding = requestEncoding;
> -        this.analyzer = analyzer;
> +        this.sessionFactory = sessionFactory;
>      }
>  
>      public List<String> getParameterNames()
> @@ -105,27 +104,15 @@ public class RequestImpl implements Requ
>  
>      public Session getSession(boolean create)
>      {
> -        if (session != null)
> +        if (session != null && session.isInvalidated())
>          {
> -            // The easy case is when the session was invalidated through the Tapestry Session
> -            // object. The hard case is when the HttpSession was invalidated outside of Tapestry,
> -            // in which case, request.getSession() will return a new HttpSession instance (or null)
> -
> -            if (session.isInvalidated() || hsession != request.getSession(false))
> -            {
> -                session = null;
> -                hsession = null;
> -            }
> +            session = null;
>          }
>  
> -        if (session == null)
> +        if (session == null )
>          {
> -            hsession = request.getSession(create);
> -
> -            if (hsession != null)
> -            {
> -                session = new SessionImpl(hsession, analyzer);
> -            }
> +            // TAP5-1489 - Re-storage of session attributes at end of request should be configurable
> +            session = sessionFactory.getSession(create);
>          }
>  
>          return session;
> @@ -149,8 +136,7 @@ public class RequestImpl implements Requ
>          try
>          {
>              request.setCharacterEncoding(requestEncoding);
> -        }
> -        catch (UnsupportedEncodingException ex)
> +        } catch (UnsupportedEncodingException ex)
>          {
>              throw new RuntimeException(ex);
>          }
> @@ -198,7 +184,9 @@ public class RequestImpl implements Requ
>          return request.getLocalPort();
>      }
>  
> -    /** @since 5.2.5 */
> +    /**
> +     * @since 5.2.5
> +     */
>      public int getServerPort()
>      {
>          return request.getServerPort();
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactory.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,22 @@
> +//  Copyright 2011 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.services.Session;
> +
> +public interface SessionFactory
> +{
> +    Session getSession(boolean create);
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionFactoryImpl.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,59 @@
> +//  Copyright 2011 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.SymbolConstants;
> +import org.apache.tapestry5.ioc.annotations.Symbol;
> +import org.apache.tapestry5.services.Session;
> +import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
> +
> +import javax.servlet.http.HttpServletRequest;
> +import javax.servlet.http.HttpSession;
> +
> +/**
> + * User: josh_canfield
> + * Date: 6/25/11
> + */
> +public class SessionFactoryImpl implements SessionFactory
> +{
> +    private boolean clustered;
> +    private final SessionPersistedObjectAnalyzer analyzer;
> +    private final HttpServletRequest request;
> +
> +    public SessionFactoryImpl(
> +            @Symbol(SymbolConstants.CLUSTERED_SESSIONS)
> +            boolean clustered,
> +            SessionPersistedObjectAnalyzer analyzer,
> +            HttpServletRequest request)
> +    {
> +        this.clustered = clustered;
> +        this.analyzer = analyzer;
> +        this.request = request;
> +    }
> +
> +    public Session getSession(boolean create)
> +    {
> +        final HttpSession httpSession = request.getSession(create);
> +
> +        if (httpSession == null ) return null;
> +
> +        if (clustered)
> +        {
> +            return new ClusteredSessionImpl(request, httpSession, analyzer);
> +        }
> +
> +        return new SessionImpl(request, httpSession);
> +    }
> +}
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/SessionImpl.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
> +// Copyright 2006, 2007, 2008, 2009, 2011 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.
> @@ -19,6 +19,7 @@ import org.apache.tapestry5.ioc.internal
>  import org.apache.tapestry5.services.Session;
>  import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
>  
> +import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpSession;
>  import java.util.Collections;
>  import java.util.Enumeration;
> @@ -30,32 +31,20 @@ import java.util.Map;
>   */
>  public class SessionImpl implements Session
>  {
> -    private final SessionPersistedObjectAnalyzer analyzer;
> -
> +    private final HttpServletRequest request;
>      private final HttpSession session;
>  
>      private boolean invalidated = false;
>  
> -    /**
> -     * Cache of attribute objects read from, or written to, the real session.
> -     * This is needed for end-of-request
> -     * processing.
> -     */
> -    private final Map<String, Object> sessionAttributeCache = CollectionFactory.newMap();
> -
> -    public SessionImpl(HttpSession session, SessionPersistedObjectAnalyzer analyzer)
> +    public SessionImpl(HttpServletRequest request, HttpSession session)
>      {
> +        this.request = request;
>          this.session = session;
> -        this.analyzer = analyzer;
>      }
>  
>      public Object getAttribute(String name)
>      {
> -        Object result = session.getAttribute(name);
> -
> -        sessionAttributeCache.put(name, result);
> -
> -        return result;
> +        return session.getAttribute(name);
>      }
>  
>      public List<String> getAttributeNames()
> @@ -66,8 +55,6 @@ public class SessionImpl implements Sess
>      public void setAttribute(String name, Object value)
>      {
>          session.setAttribute(name, value);
> -
> -        sessionAttributeCache.put(name, value);
>      }
>  
>      public List<String> getAttributeNames(String prefix)
> @@ -97,12 +84,18 @@ public class SessionImpl implements Sess
>          invalidated = true;
>  
>          session.invalidate();
> -
> -        sessionAttributeCache.clear();
>      }
>  
>      public boolean isInvalidated()
>      {
> +        if (invalidated) return true;
> +
> +        // The easy case is when the session was invalidated through the Tapestry Session
> +        // object. The hard case is when the HttpSession was invalidated outside of Tapestry,
> +        // in which case, request.getSession() will return a new HttpSession instance (or null)
> +
> +        invalidated = request.getSession(false) != session;
> +
>          return invalidated;
>      }
>  
> @@ -113,28 +106,6 @@ public class SessionImpl implements Sess
>  
>      public void restoreDirtyObjects()
>      {
> -        if (invalidated) return;
> -
> -        if (sessionAttributeCache.isEmpty()) return;
> -
> -        for (Map.Entry<String, Object> entry : sessionAttributeCache.entrySet())
> -        {
> -            String attributeName = entry.getKey();
> -
> -            Object attributeValue = entry.getValue();
>  
> -            if (attributeValue == null) continue;
> -
> -            if (analyzer.isDirty(attributeValue))
> -            {
> -                // TAP5-834: Jetty & Tomcat work by object identity, will not update the attribute
> -                // and fire the session binding event unless there's a real change. So we set the
> -                // attribute to null and then to the new value and that should force the necessary
> -                // notification.
> -                
> -                session.setAttribute(attributeName, null);
> -                session.setAttribute(attributeName, attributeValue);
> -            }
> -        }
>      }
>  }
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/Session.java Sat Jun 25 12:31:36 2011
> @@ -72,8 +72,8 @@ public interface Session
>      void invalidate();
>  
>      /**
> -     * Checks to see if the session has been invalidated. Note: this only catches calls to {@link #invalidate()}, not
> -     * calls to {@link javax.servlet.http.HttpSession#invalidate()}.
> +     * Checks to see if the session has been invalidated. Note: since 5.3.1 this will also catch calls to
> +     * {@link javax.servlet.http.HttpSession#invalidate()}.
>       * 
>       * @since 5.1.0.0
>       */
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/SessionPersistedObjectAnalyzer.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2008 The Apache Software Foundation
> +// Copyright 2008, 2011 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.
> @@ -17,10 +17,15 @@ package org.apache.tapestry5.services;
>  import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration;
>  
>  /**
> - * Analyzes a session-persisted object, specifically to see if it is dirty or not.  The service implementation uses a
> - * mapped configuration to form a {@linkplain org.apache.tapestry5.ioc.services.StrategyBuilder strategy} based on
> - * object type. The service is injectable using the {@link org.apache.tapestry5.ioc.annotations.Primary} marker
> - * annotation.
> + * Analyzes a session-persisted object, specifically to see if it is dirty or not.
> + * <p/>
> + * This service is provided to support applications which store mutable session attributes where the
> + * session is replicated to a slower medium (e.g. RDMBS, Cluster, etc) this can help alleviate excessive writes
> + * to the session store while ensuring changes are propagated.
> + * <p/>
> + * The service implementation uses a mapped configuration to form a
> + * {@linkplain org.apache.tapestry5.ioc.services.StrategyBuilder strategy} based on object type. The service may be
> + * injected using the {@link org.apache.tapestry5.ioc.annotations.Primary} marker annotation.
>   *
>   * @see org.apache.tapestry5.annotations.ImmutableSessionPersistedObject
>   * @see org.apache.tapestry5.OptimizedSessionPersistedObject
> @@ -30,11 +35,15 @@ import org.apache.tapestry5.ioc.annotati
>  public interface SessionPersistedObjectAnalyzer<T>
>  {
>      /**
> -     * Passed an object (never null) to see if it is dirty or not. Dirty objects that are stored in the session are
> -     * re-stored into the session at the end of the request.
> +     * Atomically check and reset the dirty state of the session persisted object.
> +     * <p/>
> +     * The implementer should take consideration for the fact that session attributes are accessed concurrently. A
> +     * naive check/set algorithm may allow changes to go un-noticed.
>       *
> -     * @param object
> -     * @return true if object needs to be re-stored into the session
> +     * @param sessionPersistedObject the session attribute (never null)
> +     * @return true if the object needs to be re-stored into the session
> +     * @since 5.3.1
>       */
> -    boolean isDirty(T object);
> +    boolean checkAndResetDirtyState(T sessionPersistedObject);
> +
>  }
>
> 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=1139536&r1=1139535&r2=1139536&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 Sat Jun 25 12:31:36 2011
> @@ -355,14 +355,14 @@ public final class TapestryModule
>      {
>          private final RequestHandler handler;
>          private final String applicationCharset;
> -        private final SessionPersistedObjectAnalyzer analyzer;
> +        private final SessionFactory sessionFactory;
>  
>          public HttpServletRequestHandlerTerminator(RequestHandler handler, String applicationCharset,
> -                SessionPersistedObjectAnalyzer analyzer)
> +                SessionFactory sessionFactory)
>          {
>              this.handler = handler;
>              this.applicationCharset = applicationCharset;
> -            this.analyzer = analyzer;
> +            this.sessionFactory = sessionFactory;
>          }
>  
>          public boolean service(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
> @@ -370,7 +370,7 @@ public final class TapestryModule
>          {
>              requestGlobals.storeServletRequestResponse(servletRequest, servletResponse);
>  
> -            Request request = new RequestImpl(servletRequest, applicationCharset, analyzer);
> +            Request request = new RequestImpl(servletRequest, applicationCharset, sessionFactory);
>              Response response = new ResponseImpl(servletRequest, servletResponse);
>  
>              // TAP5-257: Make sure that the "initial guess" for request/response
> @@ -484,6 +484,7 @@ public final class TapestryModule
>          binder.bind(ContextPathEncoder.class, ContextPathEncoderImpl.class);
>          binder.bind(ApplicationStatePersistenceStrategy.class, SessionApplicationStatePersistenceStrategy.class)
>                  .withId("SessionApplicationStatePersistenceStrategy");
> +        binder.bind(SessionFactory.class, SessionFactoryImpl.class);
>          binder.bind(AssetPathConverter.class, IdentityAssetPathConverter.class);
>          binder.bind(NumericTranslatorSupport.class);
>          binder.bind(ClientDataEncoder.class, ClientDataEncoderImpl.class);
> @@ -1115,7 +1116,7 @@ public final class TapestryModule
>       * Adds coercions:
>       * <ul>
>       * <li>String to {@link SelectModel}
> -     * <li>Map to {@link oSelectModel}
> +     * <li>Map to {@link SelectModel}
>       * <li>Collection to {@link GridDataSource}
>       * <li>null to {@link GridDataSource}
>       * <li>List to {@link SelectModel}
> @@ -1516,11 +1517,10 @@ public final class TapestryModule
>      @Symbol(SymbolConstants.CHARSET)
>      String applicationCharset,
>  
> -    @Primary
> -    SessionPersistedObjectAnalyzer analyzer)
> +    SessionFactory sessionFactory)
>      {
>          HttpServletRequestHandler terminator = new HttpServletRequestHandlerTerminator(handler, applicationCharset,
> -                analyzer);
> +                sessionFactory);
>  
>          return pipelineBuilder.build(logger, HttpServletRequestHandler.class, HttpServletRequestFilter.class,
>                  configuration, terminator);
> @@ -2457,6 +2457,8 @@ public final class TapestryModule
>  
>          configuration.add(SymbolConstants.PRODUCTION_MODE, true);
>  
> +        configuration.add(SymbolConstants.CLUSTERED_SESSIONS, true);
> +
>          configuration.add(SymbolConstants.COMPRESS_WHITESPACE, true);
>  
>          configuration.add(MetaDataConstants.SECURE_PAGE, false);
> @@ -2704,7 +2706,7 @@ public final class TapestryModule
>      }
>  
>      /**
> -     * The master SessionPesistedObjectAnalyzer.
> +     * The master SessionPersistedObjectAnalyzer.
>       * 
>       * @since 5.1.0.0
>       */
> @@ -2730,7 +2732,7 @@ public final class TapestryModule
>  
>          SessionPersistedObjectAnalyzer<Object> immutable = new SessionPersistedObjectAnalyzer<Object>()
>          {
> -            public boolean isDirty(Object object)
> +            public boolean checkAndResetDirtyState(Object sessionPersistedObject)
>              {
>                  return false;
>              }
>
> Copied: tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml (from r1139143, tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml)
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml?p2=tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml&p1=tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml&r1=1139143&r2=1139536&rev=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/app5/WEB-INF/web.xml (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/cluster/WEB-INF/web.xml Sat Jun 25 12:31:36 2011
> @@ -3,10 +3,10 @@
>          PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
>          "http://java.sun.com/dtd/web-app_2_3.dtd">
>  <web-app>
> -  <display-name>Integration Test App 5 -- Skinning</display-name>
> +  <display-name>Integration Test Cluster</display-name>
>    <context-param>
>      <param-name>tapestry.app-package</param-name>
> -    <param-value>org.apache.tapestry5.integration.app5</param-value>
> +    <param-value>org.apache.tapestry5.integration.cluster</param-value>
>    </context-param>
>    <filter>
>      <filter-name>app</filter-name>
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/ClusterTests.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,248 @@
> +//  Copyright 2011 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.integration.cluster;
> +
> +import com.thoughtworks.selenium.DefaultSelenium;
> +import com.thoughtworks.selenium.Selenium;
> +import org.apache.tapestry5.test.Jetty7Runner;
> +import org.apache.tapestry5.test.TapestryTestConstants;
> +import org.eclipse.jetty.server.Server;
> +import org.eclipse.jetty.server.session.JDBCSessionIdManager;
> +import org.eclipse.jetty.server.session.JDBCSessionManager;
> +import org.eclipse.jetty.server.session.SessionHandler;
> +import org.eclipse.jetty.webapp.WebAppContext;
> +import org.openqa.selenium.server.RemoteControlConfiguration;
> +import org.openqa.selenium.server.SeleniumServer;
> +import org.testng.annotations.AfterClass;
> +import org.testng.annotations.BeforeClass;
> +import org.testng.annotations.Test;
> +import org.testng.xml.XmlTest;
> +
> +import java.sql.Connection;
> +import java.sql.DriverManager;
> +import java.sql.SQLException;
> +import java.sql.Statement;
> +
> +import static org.testng.AssertJUnit.assertEquals;
> +import static org.testng.AssertJUnit.assertTrue;
> +
> +/**
> + * User: josh_canfield Date: 6/24/11
> + */
> +public class ClusterTests
> +{
> +    private static final String FIREFOX_BROWSER_CMD = "*firefox";
> +    private static final int SERVER_A_PORT = 9091;
> +    private static final int SERVER_B_PORT = 9092;
> +    private static final String SERVER_A_NAME = "server_A";
> +    private static final String SERVER_B_NAME = "server_B";
> +
> +    private static final String CREATE_1 = "//a[contains(text(),'create1')]";
> +    private static final String VALUE_1 = "value1";
> +
> +    private static final String CREATE_2 = "//a[contains(text(),'create2')]";
> +    private static final String VALUE_2 = "value2";
> +
> +    private static final String UPDATE_1 = "//a[contains(text(),'update1')]";
> +    private static final String VALUE_3 = "value3";
> +
> +    private static final String UPDATE_2 = "//a[contains(text(),'update2')]";
> +    private static final String VALUE_4 = "value4";
> +
> +    private static final String CLEAR = "//a[contains(text(),'Clear')]";
> +
> +    Jetty7Runner serverA;
> +
> +    Jetty7Runner serverB;
> +
> +    SeleniumServer seleniumServer;
> +    Selenium selenium;
> +
> +    @BeforeClass
> +    void setupServers(XmlTest xmlTest) throws Exception
> +    {
> +        createJettySessionsTable();
> +
> +        serverA = configureClusteredJetty(SERVER_A_NAME, SERVER_A_PORT);
> +        serverB = configureClusteredJetty(SERVER_B_NAME, SERVER_B_PORT);
> +
> +        seleniumServer = new SeleniumServer();
> +        seleniumServer.start();
> +
> +        String browserStartCommand = xmlTest.getParameter(TapestryTestConstants.BROWSER_START_COMMAND_PARAMETER);
> +        browserStartCommand = browserStartCommand != null ? browserStartCommand : FIREFOX_BROWSER_CMD;
> +
> +        selenium = new DefaultSelenium(
> +                "localhost", RemoteControlConfiguration.DEFAULT_PORT,
> +                browserStartCommand, "http://localhost:9091/"
> +        );
> +        selenium.start();
> +    }
> +
> +    @AfterClass
> +    void stopServers()
> +    {
> +        serverA.stop();
> +        serverB.stop();
> +        selenium.stop();
> +        seleniumServer.stop();
> +    }
> +
> +    @Test
> +    public void mutable_pojo_as_session_state_is_always_shared()
> +    {
> +        // Expect all object changes to be transferred to the other server.
> +        String[][] click = {
> +                {CREATE_1, VALUE_1, VALUE_1},
> +                {CLEAR, "", ""},
> +                {CREATE_2, VALUE_2, VALUE_2},
> +                {UPDATE_1, VALUE_3, VALUE_3},
> +                {UPDATE_2, VALUE_4, VALUE_4}
> +        };
> +
> +        evaluate("PersistedMutablePojoDemo", click);
> +    }
> +
> +    @Test
> +    public void immutable_session_persisted_object() throws InterruptedException
> +    {
> +        // expect only create links to transfer over (We've told tapestry it's immutable)
> +        String[][] data = {
> +                {CREATE_1, VALUE_1, VALUE_1},
> +                {UPDATE_1, VALUE_3, VALUE_1},
> +                {CLEAR, "", ""},
> +                {CREATE_2, VALUE_2, VALUE_2},
> +                {UPDATE_2, VALUE_4, VALUE_2},
> +                {UPDATE_1, VALUE_3, VALUE_2},
> +        };
> +
> +        evaluate("ImmutableSessionPersistedObjectDemo", data);
> +    }
> +
> +    @Test
> +    public void session_persisted_object_analyzer() throws InterruptedException
> +    {
> +        // special cased so that only UPDATE_2 marks as dirty, creates/deletes still transfer
> +        String[][] data = {
> +                {CREATE_1, VALUE_1, VALUE_1}, // created, session transferred
> +                {UPDATE_1, VALUE_3, VALUE_1}, // update-1 doesn't transfer
> +                {CLEAR, "", ""},
> +                {CREATE_2, VALUE_2, VALUE_2}, // create-2, session transferred
> +                {UPDATE_2, VALUE_4, VALUE_4}, // update-2, session transferred
> +                {UPDATE_1, VALUE_3, VALUE_4}, // update-1, doesn't transfer
> +        };
> +
> +        evaluate("SessionPersistedObjectAnalyzerDemo", data);
> +    }
> +
> +    private void evaluate(String page, String[][] expect)
> +    {
> +        for (String[] strings : expect)
> +        {
> +            openOnServerA(page);
> +
> +            clickAndWait(strings[0]);
> +
> +            assertText("value", strings[1]);
> +
> +            openOnServerB(page);
> +
> +            assertText("value", strings[2]);
> +        }
> +
> +        clickAndWait(CLEAR);
> +        assertText("value", "");
> +    }
> +
> +    private void openOnServerA(String page)
> +    {
> +        selenium.open("http://localhost:" + SERVER_A_PORT + "/" + page);
> +        assertServerName(SERVER_A_NAME);
> +    }
> +
> +    private void openOnServerB(String page)
> +    {
> +        selenium.open("http://localhost:" + SERVER_B_PORT + "/" + page);
> +        assertServerName(SERVER_B_NAME);
> +    }
> +
> +    private void assertServerName(String serverName)
> +    {
> +        assertTrue(selenium.isElementPresent("//h1[@id='serverName' and text()='" + serverName + "']"));
> +    }
> +
> +    private void assertText(String locator, String expected)
> +    {
> +        assertEquals(expected, selenium.getText(locator));
> +    }
> +
> +    private void clickAndWait(String s)
> +    {
> +        selenium.click(s);
> +        selenium.waitForPageToLoad("5000");
> +    }
> +
> +    private Jetty7Runner configureClusteredJetty(String name, int port) throws Exception
> +    {
> +        Jetty7Runner runner = new Jetty7Runner();
> +
> +        runner.configure("src/test/cluster", "", port, port + 100);
> +
> +        JDBCSessionIdManager idMgr = new JDBCSessionIdManager(runner.getServer());
> +        idMgr.setWorkerName(name);
> +        idMgr.setDriverInfo("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:clustertest");
> +
> +        Server server = runner.getServer();
> +        server.setSessionIdManager(idMgr);
> +
> +        WebAppContext wac = (WebAppContext) server.getHandler();
> +
> +        JDBCSessionManager jdbcMgr = new JDBCSessionManager();
> +        jdbcMgr.setIdManager(server.getSessionIdManager());
> +
> +        // force the session to be read from the database with no delay
> +        // This is an incorrectly documented feature.
> +        jdbcMgr.setSaveInterval(0);
> +
> +        wac.setSessionHandler(new SessionHandler(jdbcMgr));
> +        wac.getServletContext().setInitParameter("cluster.name", name);
> +        runner.start();
> +        return runner;
> +    }
> +
> +    private void createJettySessionsTable() throws ClassNotFoundException, SQLException
> +    {
> +        Class.forName("org.hsqldb.jdbcDriver");
> +
> +        Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:clustertest", "sa", "");
> +        String sql = "create table JettySessions (" +
> +                "rowId varchar(60), " +
> +                "sessionId varchar(60)," +
> +                "contextPath varchar(60)," +
> +                "virtualHost varchar(60)," +
> +                "lastNode varchar(60)," +
> +                "accessTime bigint," +
> +                "lastAccessTime bigint," +
> +                "createTime bigint," +
> +                "cookieTime bigint," +
> +                "lastSavedTime bigint," +
> +                "expiryTime bigint," +
> +                "map longvarbinary" +
> +                ");";
> +        Statement statement = c.createStatement();
> +        statement.execute(sql);
> +        statement.close();
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,41 @@
> +//  Copyright 2011 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.integration.cluster.base;
> +
> +import org.apache.tapestry5.annotations.Persist;
> +import org.apache.tapestry5.annotations.Property;
> +import org.apache.tapestry5.integration.cluster.data.SessionStateObject;
> +
> +public abstract class BaseSessionDemo<T extends SessionStateObject> {
> +
> +    @Property
> +    @Persist
> +    private SessionStateObject data;
> +
> +    abstract public T create(String value);
> +
> +    void onCreateValue(String value) {
> +        data = create(value);
> +    }
> +
> +    void onChangeValue(String value) {
> +        data.setValue(value);
> +    }
> +
> +    void onClear() {
> +        data = null;
> +    }
> +
> +}
> \ No newline at end of file
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/AnalyzedSessionObject.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,45 @@
> +// Copyright 2011 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.integration.cluster.data;
> +
> +public class AnalyzedSessionObject implements SessionStateObject
> +{
> +    private String value;
> +
> +    boolean dirty = false;
> +
> +    public AnalyzedSessionObject(String value)
> +    {
> +        this.value = value;
> +    }
> +
> +    public boolean checkAndResetDirtyState()
> +    {
> +        boolean check = dirty;
> +        dirty = false;
> +        return check;
> +    }
> +
> +    public String getValue()
> +    {
> +        return value;
> +    }
> +
> +    public void setValue(String value)
> +    {
> +        this.value = value;
> +        dirty = "value4".equals(value);
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/ImmutableByAnnotation.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,34 @@
> +//  Copyright 2011 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.integration.cluster.data;
> +
> +import org.apache.tapestry5.annotations.ImmutableSessionPersistedObject;
> +
> +@ImmutableSessionPersistedObject
> +public class ImmutableByAnnotation implements SessionStateObject {
> +    private String value;
> +
> +    public ImmutableByAnnotation(String value) {
> +        this.value = value;
> +    }
> +
> +    public String getValue() {
> +        return value;
> +    }
> +
> +    public void setValue(String value) {
> +        this.value = value;
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/MutablePojo.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,32 @@
> +//  Copyright 2011 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.integration.cluster.data;
> +
> +public class MutablePojo implements SessionStateObject {
> +
> +    private String value;
> +
> +    public MutablePojo(String value) {
> +        this.value = value;
> +    }
> +
> +    public String getValue() {
> +        return value;
> +    }
> +
> +    public void setValue(String name) {
> +        this.value = name;
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/data/SessionStateObject.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,24 @@
> +//  Copyright 2011 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.integration.cluster.data;
> +
> +import java.io.Serializable;
> +
> +public interface SessionStateObject extends Serializable {
> +
> +    String getValue();
> +
> +    void setValue(String v);
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/ImmutableSessionPersistedObjectDemo.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,29 @@
> +//  Copyright 2011 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.integration.cluster.pages;
> +
> +import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
> +import org.apache.tapestry5.integration.cluster.data.ImmutableByAnnotation;
> +
> +/**
> + * User: josh_canfield
> + * Date: 6/24/11
> + */
> +public class ImmutableSessionPersistedObjectDemo extends BaseSessionDemo<ImmutableByAnnotation> {
> +    @Override
> +    public ImmutableByAnnotation create(String value) {
> +        return new ImmutableByAnnotation(value);
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/PersistedMutablePojoDemo.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,26 @@
> +//  Copyright 2011 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.integration.cluster.pages;
> +
> +import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
> +import org.apache.tapestry5.integration.cluster.data.MutablePojo;
> +
> +public class PersistedMutablePojoDemo extends BaseSessionDemo<MutablePojo> {
> +
> +    @Override
> +    public MutablePojo create(String value) {
> +        return new MutablePojo(value);
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/pages/SessionPersistedObjectAnalyzerDemo.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,25 @@
> +//  Copyright 2011 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.integration.cluster.pages;
> +
> +import org.apache.tapestry5.integration.cluster.base.BaseSessionDemo;
> +import org.apache.tapestry5.integration.cluster.data.AnalyzedSessionObject;
> +
> +public class SessionPersistedObjectAnalyzerDemo extends BaseSessionDemo<AnalyzedSessionObject> {
> +    @Override
> +    public AnalyzedSessionObject create(String value) {
> +        return new AnalyzedSessionObject(value);
> +    }
> +}
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/integration/cluster/services/AppModule.java Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,36 @@
> +//  Copyright 2011 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.integration.cluster.services;
> +
> +import org.apache.tapestry5.integration.cluster.data.AnalyzedSessionObject;
> +import org.apache.tapestry5.ioc.MappedConfiguration;
> +import org.apache.tapestry5.ioc.annotations.Contribute;
> +import org.apache.tapestry5.services.SessionPersistedObjectAnalyzer;
> +
> +public class AppModule
> +{
> +
> +    @Contribute(SessionPersistedObjectAnalyzer.class)
> +    public static void analyzer(MappedConfiguration<Class, SessionPersistedObjectAnalyzer> configuration)
> +    {
> +        configuration.add(AnalyzedSessionObject.class, new SessionPersistedObjectAnalyzer<AnalyzedSessionObject>()
> +        {
> +            public boolean checkAndResetDirtyState(AnalyzedSessionObject sessionPersistedObject)
> +            {
> +                return sessionPersistedObject.checkAndResetDirtyState();
> +            }
> +        });
> +    }
> +}
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/RequestImplTest.java Sat Jun 25 12:31:36 2011
> @@ -33,12 +33,13 @@ public class RequestImplTest extends Int
>      public void get_session_doesnt_exist()
>      {
>          HttpServletRequest sr = mockHttpServletRequest();
> +        SessionFactory sf = newMock(SessionFactory.class);
>  
> -        train_getSession(sr, false, null);
> +        expect(sf.getSession(false)).andReturn(null);
>  
>          replay();
>  
> -        Request request = new RequestImpl(sr, CHARSET, null);
> +        Request request = new RequestImpl(sr, CHARSET, sf);
>  
>          assertNull(request.getSession(false));
>  
> @@ -50,14 +51,15 @@ public class RequestImplTest extends Int
>      {
>          HttpServletRequest sr = mockHttpServletRequest();
>          HttpSession ss = mockHttpSession();
> +        SessionFactory sf = newMock(SessionFactory.class);
>  
> -        train_getSession(sr, true, ss);
> +        expect(sf.getSession(true)).andReturn(new SessionImpl(sr, ss));
>  
>          train_getAttribute(ss, "foo", "bar");
>  
>          replay();
>  
> -        Request request = new RequestImpl(sr, CHARSET, null);
> +        Request request = new RequestImpl(sr, CHARSET, sf);
>          Session session = request.getSession(true);
>  
>          assertEquals(session.getAttribute("foo"), "bar");
> @@ -202,11 +204,13 @@ public class RequestImplTest extends Int
>          HttpSession hsession1 = mockHttpSession();
>          HttpSession hsession2 = mockHttpSession();
>  
> -        train_getSession(sr, true, hsession1);
> +        SessionFactory sf = newMock(SessionFactory.class);
> +
> +        expect(sf.getSession(true)).andReturn(new SessionImpl(sr,hsession1));
>  
>          replay();
>  
> -        Request request = new RequestImpl(sr, CHARSET, null);
> +        Request request = new RequestImpl(sr, CHARSET, sf);
>  
>          Session session1 = request.getSession(true);
>  
> @@ -214,8 +218,11 @@ public class RequestImplTest extends Int
>  
>          hsession1.invalidate();
>  
> -        train_getSession(sr, false, hsession2);
> -        train_getSession(sr, true, hsession2);
> +        expect(sr.getSession(false)).andReturn(null);
> +
> +        SessionImpl session = new SessionImpl(sr, hsession2);
> +        expect(sf.getSession(true)).andReturn(session);
> +        expect(sf.getSession(true)).andReturn(session);
>  
>          replay();
>  
>
> Modified: tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java?rev=1139536&r1=1139535&r2=1139536&view=diff
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java (original)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java Sat Jun 25 12:31:36 2011
> @@ -1,4 +1,4 @@
> -// Copyright 2006, 2007, 2008, 2009 The Apache Software Foundation
> +// Copyright 2006, 2007, 2008, 2009, 2011 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.
> @@ -18,6 +18,7 @@ import java.util.Arrays;
>  import java.util.Collections;
>  import java.util.Enumeration;
>  
> +import javax.servlet.http.HttpServletRequest;
>  import javax.servlet.http.HttpSession;
>  
>  import org.apache.tapestry5.internal.test.InternalBaseTestCase;
> @@ -37,7 +38,7 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, null);
> +        Session session = new SessionImpl(null, hs);
>  
>          assertEquals(session.getAttributeNames(), Arrays.asList("barney", "fred"));
>  
> @@ -54,7 +55,7 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, null);
> +        Session session = new SessionImpl(null, hs);
>  
>          assertEquals(session.getAttributeNames("f"), Arrays.asList("fanny", "fred"));
>  
> @@ -70,7 +71,7 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, null);
> +        Session session = new SessionImpl(null, hs);
>  
>          session.invalidate();
>  
> @@ -78,6 +79,39 @@ public class SessionImplTest extends Int
>      }
>  
>      @Test
> +    public void http_session_invalidate()
> +    {
> +        HttpSession hs = mockHttpSession();
> +
> +        HttpServletRequest hsr = mockHttpServletRequest();
> +
> +        train_getSession(hsr, false, hs);
> +
> +        replay();
> +
> +        Session session = new SessionImpl(hsr, hs);
> +
> +        assertFalse(session.isInvalidated());
> +
> +        verify();
> +
> +        train_getSession(hsr, false, null);
> +
> +        replay();
> +
> +        assertTrue(session.isInvalidated());
> +
> +        verify();
> +
> +        train_getSession(hsr, false, mockHttpSession());
> +
> +        replay();
> +
> +        assertTrue(session.isInvalidated());
> +
> +    }
> +
> +    @Test
>      public void set_max_inactive()
>      {
>          HttpSession hs = mockHttpSession();
> @@ -87,7 +121,7 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, null);
> +        Session session = new SessionImpl(null, hs);
>  
>          session.setMaxInactiveInterval(seconds);
>  
> @@ -104,7 +138,7 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, null);
> +        Session session = new SessionImpl(null, hs);
>  
>          assertEquals(session.getMaxInactiveInterval(), seconds);
>  
> @@ -115,6 +149,7 @@ public class SessionImplTest extends Int
>      public void dirty_persisted_object_is_forced_to_update()
>      {
>          HttpSession hs = mockHttpSession();
> +        HttpServletRequest hsr = mockHttpServletRequest();
>          SessionPersistedObjectAnalyzer analyzer = newMock(SessionPersistedObjectAnalyzer.class);
>          Object dirty = new Object();
>  
> @@ -122,15 +157,16 @@ public class SessionImplTest extends Int
>  
>          replay();
>  
> -        Session session = new SessionImpl(hs, analyzer);
> +        Session session = new ClusteredSessionImpl(hsr, hs, analyzer);
>  
>          assertSame(session.getAttribute("dirty"), dirty);
>  
>          verify();
>  
> -        expect(analyzer.isDirty(dirty)).andReturn(true);
> +        expect(analyzer.checkAndResetDirtyState(dirty)).andReturn(true);
> +
> +        train_getSession(hsr, false, hs);
>  
> -        hs.setAttribute("dirty", null);
>          hs.setAttribute("dirty", dirty);
>  
>          replay();
>
> Added: tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml
> URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml?rev=1139536&view=auto
> ==============================================================================
> --- tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml (added)
> +++ tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/integration/cluster/base/BaseSessionDemo.tml Sat Jun 25 12:31:36 2011
> @@ -0,0 +1,21 @@
> +<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
> +<head>
> +    <title>Immutable Annotation Demo</title>
> +</head>
> +
> +<body>
> +<h1 id="serverName">${symbol:cluster.name}</h1>
> +
> +<div id="value">${data?.value}</div>
> +
> +<t:eventlink event="clear">Clear</t:eventlink>
> +
> +<t:eventlink event="createValue" context="'value1'">create1</t:eventlink>
> +<t:eventlink event="createValue" context="'value2'">create2</t:eventlink>
> +
> +<t:eventlink event="changeValue" context="'value3'">update1</t:eventlink>
> +<t:eventlink event="changeValue" context="'value4'">update2</t:eventlink>
> +
> +</body>
> +
> +</html>
> \ No newline at end of file
>
>

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