This is an automated email from the ASF dual-hosted git repository.

benw pushed a commit to branch javax
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/javax by this push:
     new 3f0fca6fb TAP5-2799: Session.LockMode introduced
3f0fca6fb is described below

commit 3f0fca6fbb0b14354157a10b385c2467c1e87979
Author: Ben Weidig <b...@netzgut.net>
AuthorDate: Mon Dec 9 14:04:27 2024 +0100

    TAP5-2799: Session.LockMode introduced
---
 .../tapestry5/internal/test/PageTesterSession.java |  41 +++++-
 .../internal/services/SessionImplTest.java         |  96 +++++++++++++++
 .../http/internal/services/SessionImpl.java        | 101 +++++++++++++--
 .../apache/tapestry5/http/services/Session.java    | 137 +++++++++++++++++++--
 4 files changed, 352 insertions(+), 23 deletions(-)

diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java
index 2a3b06a21..e7d044ef0 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007, 2008 The Apache Software Foundation
+// Copyright 2006, 2007, 2008, 2024 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.
@@ -27,11 +27,19 @@ public class PageTesterSession implements Session
 {
     private final Map<String, Object> attributes = CollectionFactory.newMap();
 
+    @Override
     public List<String> getAttributeNames()
     {
         return InternalUtils.sortedKeys(attributes);
     }
 
+    @Override
+    public List<String> getAttributeNames(Session.LockMode lockMode)
+    {
+        return getAttributeNames();
+    }
+
+    @Override
     public List<String> getAttributeNames(String prefix)
     {
         List<String> result = newList();
@@ -42,11 +50,25 @@ public class PageTesterSession implements Session
         return result;
     }
 
+    @Override
+    public List<String> getAttributeNames(String prefix, LockMode lockMode)
+    {
+        return getAttributeNames(prefix);
+    }
+
+    @Override
     public Object getAttribute(String name)
     {
         return attributes.get(name);
     }
 
+    @Override
+    public Object getAttribute(String name, LockMode lockMode)
+    {
+        return getAttribute(name);
+    }
+
+    @Override
     public void setAttribute(String name, Object value)
     {
         if (value == null)
@@ -59,12 +81,25 @@ public class PageTesterSession implements Session
         }
     }
 
+    @Override
+    public boolean containsAttribute(String name)
+    {
+        return attributes.containsKey(name);
+    }
+
+    @Override
+    public boolean containsAttribute(String name, Session.LockMode lockMode)
+    {
+        return containsAttribute(name);
+    }
+
     private void nyi(String name)
     {
         throw new IllegalStateException(String.format("%s.%s() is not yet 
implemented.", getClass()
                 .getName(), name));
     }
 
+    @Override
     public int getMaxInactiveInterval()
     {
         nyi("getMaxInativeInterval");
@@ -72,20 +107,24 @@ public class PageTesterSession implements Session
         return 0;
     }
 
+    @Override
     public void invalidate()
     {
         nyi("invalidate");
     }
 
+    @Override
     public boolean isInvalidated()
     {
         return false;
     }
 
+    @Override
     public void restoreDirtyObjects()
     {
     }
 
+    @Override
     public void setMaxInactiveInterval(int seconds)
     {
         nyi("setMaxInactiveInterval");
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
index 2f7cbd4ca..327fec3ea 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java
@@ -14,6 +14,11 @@
 
 package org.apache.tapestry5.internal.services;
 
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+
 import org.apache.tapestry5.http.internal.services.ClusteredSessionImpl;
 import org.apache.tapestry5.http.internal.services.SessionImpl;
 import org.apache.tapestry5.http.internal.services.SessionLock;
@@ -55,6 +60,25 @@ public class SessionImplTest extends InternalBaseTestCase
         verify();
     }
 
+    @Test
+    public void get_attribute_names_lock_mode()
+    {
+        Enumeration e = Collections.enumeration(Arrays.asList("fred", 
"barney"));
+        HttpSession hs = mockHttpSession();
+        SessionLock lock = mockLock();
+
+        lock.acquireWriteLock();
+        expect(hs.getAttributeNames()).andReturn(e);
+
+        replay();
+
+        Session session = new SessionImpl(null, hs, lock);
+
+        assertEquals(session.getAttributeNames(Session.LockMode.WRITE), 
Arrays.asList("barney", "fred"));
+
+        verify();
+    }
+
     @Test
     public void get_attribute_names_by_prefix()
     {
@@ -75,6 +99,71 @@ public class SessionImplTest extends InternalBaseTestCase
         verify();
     }
 
+    @Test
+    public void contains_attribute()
+    {
+        List<String> keys = Arrays.asList("fred", "barney");
+        HttpSession hs = mockHttpSession();
+        SessionLock lock = mockLock();
+
+        // We need one per assert, and Enumerations are exhausted on use
+        lock.acquireReadLock();
+        
expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys));
+        lock.acquireReadLock();
+        
expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys));
+        lock.acquireReadLock();
+        
expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys));
+        replay();
+
+        Session session = new SessionImpl(null, hs, lock);
+
+        assertTrue(session.containsAttribute("barney"));
+        assertTrue(session.containsAttribute("fred"));
+        assertFalse(session.containsAttribute("wilma"));
+
+        verify();
+    }
+
+    @Test
+    public void get_attribute_write_lock()
+    {
+        Enumeration e = Collections.enumeration(Arrays.asList("fred", 
"barney"));
+        HttpSession hs = mockHttpSession();
+        SessionLock lock = mockLock();
+
+        lock.acquireReadLock();
+        lock.acquireWriteLock();
+        expect(hs.getAttributeNames()).andReturn(e);
+        expect(hs.getAttribute("fred")).andReturn("1");
+
+        replay();
+
+        Session session = new SessionImpl(null, hs, lock);
+
+        assertEquals(session.getAttribute("fred"), "1");
+
+        verify();
+    }
+
+    @Test
+    public void get_attribute_no_lock_update()
+    {
+        Enumeration e = Collections.enumeration(Arrays.asList("fred", 
"barney"));
+        HttpSession hs = mockHttpSession();
+        SessionLock lock = mockLock();
+
+        lock.acquireReadLock();
+        expect(hs.getAttributeNames()).andReturn(e);
+
+        replay();
+
+        Session session = new SessionImpl(null, hs, lock);
+
+        assertEquals(session.getAttribute("wilma"), null);
+
+        verify();
+    }
+
     @Test
     public void invalidate()
     {
@@ -169,6 +258,13 @@ public class SessionImplTest extends InternalBaseTestCase
         Object dirty = new Object();
         SessionLock lock = mockLock();
 
+        // TAP5-2799: To reduce write locks, first, a read-lock attempt is done
+        // to check if the attribute exists, and only then, a write-lock is 
acquired.
+
+        lock.acquireReadLock();
+
+        
expect(hs.getAttributeNames()).andReturn(Collections.enumeration(Arrays.asList("dirty")));
+
         lock.acquireWriteLock();
 
         train_getAttribute(hs, "dirty", dirty);
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java
 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java
index 01047d51b..7248e1f6c 100644
--- 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java
@@ -14,16 +14,17 @@
 
 package org.apache.tapestry5.http.internal.services;
 
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Objects;
+
 import org.apache.tapestry5.commons.util.CollectionFactory;
 import org.apache.tapestry5.http.services.Session;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
-import org.apache.tapestry5.ioc.services.PerthreadManager;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
 
 /**
  * A thin wrapper around {@link HttpSession}.
@@ -45,37 +46,95 @@ public class SessionImpl implements Session
         this.lock = lock;
     }
 
+    @Override
     public Object getAttribute(String name)
     {
-        lock.acquireWriteLock();
+        return getAttribute(name, Session.LockMode.WRITE);
+    }
+
+    @Override
+    public Object getAttribute(String name, Session.LockMode lockMode)
+    {
+        Objects.requireNonNull(name, "name must be non-null");
+
+        // If a WRITE lock is requested, check first if the key exists
+        // to prevent a lock upgrade if not necessary.
+        if (lockMode == null || lockMode == Session.LockMode.WRITE)
+        {
+            if (!containsAttribute(name)) return null;
+        }
+
+        acquireLock(lockMode, Session.LockMode.WRITE);
 
         return session.getAttribute(name);
     }
 
+    @Override
     public List<String> getAttributeNames()
     {
-        lock.acquireReadLock();
+        return getAttributeNames(Session.LockMode.READ);
+    }
+
+    @Override
+    public List<String> getAttributeNames(Session.LockMode lockMode)
+    {
+        acquireLock(lockMode, Session.LockMode.READ);
 
         return InternalUtils.toList(session.getAttributeNames());
     }
 
+    @Override
     public void setAttribute(String name, Object value)
     {
+        Objects.requireNonNull(name, "name must be non-null");
+
         lock.acquireWriteLock();
 
         session.setAttribute(name, value);
     }
 
+    @Override
+    public boolean containsAttribute(String name)
+    {
+        return containsAttribute(name, Session.LockMode.READ);
+    }
+
+    @Override
+    public boolean containsAttribute(String name, Session.LockMode lockMode)
+    {
+        Objects.requireNonNull(name, "name must be non-null");
+
+        acquireLock(lockMode, Session.LockMode.READ);
+
+        Enumeration<String> e = session.getAttributeNames();
+        while (e.hasMoreElements())
+        {
+            String attrName = e.nextElement();
+            if (attrName.equals(name)) return true;
+        }
+
+        return false;
+    }
+
+    @Override
     public List<String> getAttributeNames(String prefix)
     {
-        lock.acquireReadLock();
+        return getAttributeNames(prefix, Session.LockMode.READ);
+    }
+
+    @Override
+    public List<String> getAttributeNames(String prefix, Session.LockMode 
lockMode)
+    {
+        Objects.requireNonNull(prefix, "prefix must be non-null");
+
+        acquireLock(lockMode, Session.LockMode.READ);
 
         List<String> result = CollectionFactory.newList();
 
-        Enumeration e = session.getAttributeNames();
+        Enumeration<String> e = session.getAttributeNames();
         while (e.hasMoreElements())
         {
-            String name = (String) e.nextElement();
+            String name = e.nextElement();
 
             if (name.startsWith(prefix)) result.add(name);
         }
@@ -85,11 +144,13 @@ public class SessionImpl implements Session
         return result;
     }
 
+    @Override
     public int getMaxInactiveInterval()
     {
         return session.getMaxInactiveInterval();
     }
 
+    @Override
     public void invalidate()
     {
         invalidated = true;
@@ -97,6 +158,7 @@ public class SessionImpl implements Session
         session.invalidate();
     }
 
+    @Override
     public boolean isInvalidated()
     {
         if (invalidated) return true;
@@ -110,13 +172,34 @@ public class SessionImpl implements Session
         return invalidated;
     }
 
+    @Override
     public void setMaxInactiveInterval(int seconds)
     {
         session.setMaxInactiveInterval(seconds);
     }
 
+    @Override
     public void restoreDirtyObjects()
     {
 
     }
+
+    private void acquireLock(Session.LockMode requestedMode, Session.LockMode 
defaultMode) {
+        if (requestedMode == null)
+        {
+            requestedMode = defaultMode;
+        }
+
+        switch (requestedMode)
+        {
+            case NONE:
+                break;
+            case READ:
+                this.lock.acquireReadLock();
+                break;
+            case WRITE:
+                this.lock.acquireWriteLock();
+                break;
+        }
+    }
 }
diff --git 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java
index 70019a1d7..507418c08 100644
--- 
a/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java
+++ 
b/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java
@@ -22,50 +22,161 @@ import 
org.apache.tapestry5.http.OptimizedSessionPersistedObject;
 import org.apache.tapestry5.http.annotations.ImmutableSessionPersistedObject;
 import 
org.apache.tapestry5.http.internal.services.OptimizedSessionPersistedObjectAnalyzer;
 
+
 /**
  * Generic version of {@link HttpSession}, used to bridge the gaps between the 
Servlet API and the Portlet API.
  */
 public interface Session
 {
+
+    /**
+     * The type of lock used to access atttributes in the {@link Session}.
+     * <p>
+     * The actual lock-type depends on if a lock is already held by the this 
thread.
+     *
+     * @since 5.9
+     */
+    public enum LockMode {
+        /**
+         * No lock is supposed to be acquired.
+         */
+        NONE,
+
+        /**
+         * Acquire a shared read-lock.
+         */
+        READ,
+
+        /**
+         * Acquire an exclusive write-lock.
+         */
+        WRITE
+    }
+
     /**
-     * Returns a list of the names of all attributes stored in the session. 
The names are returned sorted
-     * alphabetically.
+     * Returns a list of the names of all attributes stored in the session.
+     * <p>
+     * The names are returned sorted alphabetically.
+     * <p>
+     * By default, a {@code READ} lock is requested.
      */
     List<String> getAttributeNames();
 
     /**
-     * Returns a list of the names of all attributes stored in the session 
whose name has the provided prefix. The names
-     * are returned in alphabetical order.
+     * Returns a list of the names of all attributes stored in the session.
+     * <p>
+     * Uses the requested {@link LockMode} to acquire an appropiate lock.
+     *
+     * @param lockMode The requested minimum lock mode. If null, {@code READ} 
is used.
+     * @return Alphabetically sorted list of all attributes
+     *
+     * @since 5.9
+     */
+    List<String> getAttributeNames(LockMode lockMode);
+
+    /**
+     * Returns a list of the names of all attributes stored in the session 
whose name has the provided prefix.
+     * <p>
+     * By default, a {@code READ} lock is requested.
+     * 
+     * @param prefix The attribute prefix
+     * @throws NullPointerException if prefix is {@code null}
+     * @return Alphabetically sorted list of attributes matching the prefix
      */
     List<String> getAttributeNames(String prefix);
 
+    /**
+     * Returns a list of the names of all attributes stored in the session 
whose name has the
+     * provided prefix.
+     * <p>
+     * Uses the requested {@link LockMode} to acquire an appropriate lock.
+     *
+     * @param prefix The attribute prefix
+     * @throws NullPointerException if prefix is {@code null}
+     * @return Alphabetically sorted list of attributes matching the prefix
+     * 
+     * @since 5.9
+     */
+    List<String> getAttributeNames(String prefix, Session.LockMode lockMode);
+
     /**
      * Returns the value previously stored in the session.
+     * <p>
+     * By default, a {@code WRITE} lock is requested.
+     * 
+     * @param name The name of the attribute
+     * @throws NullPointerException if name is {@code null}
      */
     Object getAttribute(String name);
 
     /**
-     * Sets the value of an attribute. If the value is null, then the 
attribute is deleted.
+     * Returns the value previously stored in the session.
+     * <p>
+     * Uses the requested {@link LockMode} to acquire an appropriate lock.
+     * 
+     * @param name The name of the attribute
+     * @throws NullPointerException if name is {@code null}
+     *
+     * @since 5.9
+     */
+    Object getAttribute(String name, Session.LockMode lockMode);
+
+    /**
+     * Sets the value of an attribute. If the value is {@code null}, then the 
attribute is deleted.
+     *
+     * @param name The name of the attribute
+     * @param value The new value of the attribute; {@code null} deletes the 
attribute.
+     * @throws NullPointerException if name is {@code null}
      */
     void setAttribute(String name, Object value);
 
     /**
-     * Returns the maximum time interval, in seconds, that the servlet 
container will keep this session open between
-     * client accesses. After this interval, the servlet container will 
invalidate the session. The maximum time
-     * interval can be set with the setMaxInactiveInterval method. A negative 
time indicates the session should never
-     * timeout.
+     * Checks if the a value is stored in the session with the specified name.
+     * <p>
+     * By default, a {@code READ} lock is requested.
+     *
+     * @param name The name of the attribute
+     * @throws NullPointerException if name is {@code null}
+     *
+     * @since 5.9
+     */
+    boolean containsAttribute(String name);
+
+    /**
+     * Checks if the a value is stored in the session with the specified name.
+     * <p>
+     * Uses the requested {@link LockMode} to acquire an appropriate lock.
+     *
+     * @param name The name of the attribute
+     * @throws NullPointerException if name is {@code null}
+     *
+     * @since 5.9
+     */
+    boolean containsAttribute(String name, Session.LockMode lockMode);
+
+    /**
+     * Returns the maximum time interval, in seconds, that the servlet 
container will keep this
+     * session open between client accesses.
+     * After this interval, the servlet container will invalidate the session.
+     * <p>
+     * The maximum time interval can be set with the setMaxInactiveInterval 
method.
+     * <p>
+     * A negative time indicates the session should never timeout.
      */
     int getMaxInactiveInterval();
 
     /**
-     * Specifies the time, in seconds, between client requests before the 
servlet container will invalidate this
-     * session. A negative time indicates the session should never timeout.
+     * Specifies the time, in seconds, between client requests before the 
servlet container will
+     * invalidate this
+     * session.
+     * <p>
+     * A negative time indicates the session should never timeout.
      */
     void setMaxInactiveInterval(int seconds);
 
     /**
      * Invalidates this session then unbinds any objects bound to it.
-     * 
+     *
      * @throws IllegalStateException
      *             if this method is called on an already invalidated session
      */
@@ -85,7 +196,7 @@ public interface Session
      * session object is read and changed, those changes will be limited to a 
single server in the cluster, which can
      * cause confusing application failures in the event of a failover. Does 
nothing if there are no changes, or
      * the session has been invalidated.
-     * 
+     *
      * @see OptimizedSessionPersistedObject
      * @see OptimizedSessionPersistedObjectAnalyzer
      * @see ImmutableSessionPersistedObject

Reply via email to