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

markt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/main by this push:
     new efa93c42c8 Implement HttpSession.getAccessor
efa93c42c8 is described below

commit efa93c42c8d9bb40486321b9e9339137d06b1259
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Wed Jan 10 14:17:35 2024 +0000

    Implement HttpSession.getAccessor
    
    This will be included in the Servlet 6.1 API.
---
 java/jakarta/servlet/http/HttpSession.java         |  36 ++++++
 .../catalina/session/LocalStrings.properties       |   7 ++
 .../apache/catalina/session/StandardSession.java   |  10 ++
 .../catalina/session/StandardSessionAccessor.java  |  79 ++++++++++++
 .../catalina/session/StandardSessionFacade.java    |   6 +
 .../session/TestStandardSessionAccessor.java       | 138 +++++++++++++++++++++
 webapps/docs/changelog.xml                         |  11 ++
 7 files changed, 287 insertions(+)

diff --git a/java/jakarta/servlet/http/HttpSession.java 
b/java/jakarta/servlet/http/HttpSession.java
index ecadd1e9bf..ccd055c8c5 100644
--- a/java/jakarta/servlet/http/HttpSession.java
+++ b/java/jakarta/servlet/http/HttpSession.java
@@ -17,6 +17,7 @@
 package jakarta.servlet.http;
 
 import java.util.Enumeration;
+import java.util.function.Consumer;
 
 import jakarta.servlet.ServletContext;
 
@@ -51,6 +52,10 @@ import jakarta.servlet.ServletContext;
  * <p>
  * Session information is scoped only to the current web application ( 
<code>ServletContext</code>), so information
  * stored in one context will not be directly visible in another.
+ * <p>
+ * This object is <b>only</b> valid within the scope of the HTTP request from 
which it was obtained. Once the processing
+ * of that request returns to the container, this object must not be used. If 
there is a requirement to access the
+ * session outside of the scope of an HTTP request then this must be done via 
{@code #getAccessor()}.
  *
  * @see HttpSessionBindingListener
  */
@@ -194,4 +199,35 @@ public interface HttpSession {
      * @exception IllegalStateException if this method is called on an already 
invalidated session
      */
     boolean isNew();
+
+    /**
+     * Provides a mechanism for applications to interact with the {@code 
HttpSession} outside of the scope of an HTTP
+     * request.
+     */
+    interface Accessor {
+        /**
+         * Call to access the session with the same semantics as if the 
session was accessed during an HTTP request.
+         * <p>
+         * The effect of this call on the session is as if an HTTP request 
starts; {@link Consumer#accept(Object)} is
+         * called with the associated session object enabling the application 
to interact with the session; and, once
+         * that method returns, the HTTP request ends.
+         *
+         * @param sessionConsumer the application provided {@link Consumer} 
instance that will access the session
+         *
+         * @throws IllegalStateException if the session with the ID to which 
the {@link Accessor} is associated is no
+         *                                   longer valid
+         */
+        void access(Consumer<HttpSession> sessionConsumer);
+    }
+
+    /**
+     * Provides a mechanism for applications to interact with the {@code 
HttpSession} outside of the scope of an HTTP
+     * request.
+     *
+     * @return An {@link Accessor} instance linked to the current session ID 
(if the session ID is changed the
+     *             {@link Accessor} will no longer be able to access this 
session)
+     *
+     * @throws IllegalStateException if this method is called on an invalid 
session
+     */
+    Accessor getAccessor();
 }
diff --git a/java/org/apache/catalina/session/LocalStrings.properties 
b/java/org/apache/catalina/session/LocalStrings.properties
index eba297f8d9..6e8344ad02 100644
--- a/java/org/apache/catalina/session/LocalStrings.properties
+++ b/java/org/apache/catalina/session/LocalStrings.properties
@@ -73,6 +73,7 @@ standardManager.unloading.nosessions=No persisted sessions to 
unload
 
 standardSession.attributeEvent=Session attribute event listener threw exception
 standardSession.bindingEvent=Session binding event listener threw exception
+standardSession.getAccessor.ise=getAccessor: Session already invalidated
 standardSession.getAttribute.ise=getAttribute: Session already invalidated
 standardSession.getAttributeNames.ise=getAttributeNames: Session already 
invalidated
 standardSession.getCreationTime.ise=getCreationTime: Session already 
invalidated
@@ -92,3 +93,9 @@ standardSession.sessionEvent=Session event listener threw 
exception
 standardSession.setAttribute.iae=setAttribute: Non-serializable attribute [{0}]
 standardSession.setAttribute.ise=setAttribute: Session [{0}] has already been 
invalidated
 standardSession.setAttribute.namenull=setAttribute: name parameter cannot be 
null
+
+standardSessionAccessor.access.end=Unexpected error during the call to 
Session.endAccess()
+standardSessionAccessor.access.invalid=Unable to access the session [{0}] as 
the session has already been invalidated
+standardSessionAccessor.access.ioe=Unable to access the session [{0}] as an 
IOException occurred retrieving the session from the session manager
+standardSessionAccessor.nullId=Unable to create Accessor instance as session 
ID is null
+standardSessionAccessor.nullManager=Unable to create Accessor instance as 
session manager is null
\ No newline at end of file
diff --git a/java/org/apache/catalina/session/StandardSession.java 
b/java/org/apache/catalina/session/StandardSession.java
index 8b63a7a020..81803a9d1c 100644
--- a/java/org/apache/catalina/session/StandardSession.java
+++ b/java/org/apache/catalina/session/StandardSession.java
@@ -621,6 +621,16 @@ public class StandardSession implements HttpSession, 
Session, Serializable {
     }
 
 
+    @Override
+    public Accessor getAccessor() {
+       if (!isValidInternal()) {
+           throw new 
IllegalStateException(sm.getString("standardSession.getAccessor.ise"));
+       }
+
+       return new StandardSessionAccessor(getManager(), getId());
+    }
+
+
     // ------------------------------------------------- Session Public Methods
 
 
diff --git a/java/org/apache/catalina/session/StandardSessionAccessor.java 
b/java/org/apache/catalina/session/StandardSessionAccessor.java
new file mode 100644
index 0000000000..c0730e83a3
--- /dev/null
+++ b/java/org/apache/catalina/session/StandardSessionAccessor.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.session;
+
+import java.io.IOException;
+import java.util.function.Consumer;
+
+import jakarta.servlet.http.HttpSession;
+
+import org.apache.catalina.Manager;
+import org.apache.catalina.Session;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.res.StringManager;
+
+public class StandardSessionAccessor implements HttpSession.Accessor {
+
+    private static final Log log = 
LogFactory.getLog(StandardSessionAccessor.class);
+    private static final StringManager sm = 
StringManager.getManager(StandardSessionAccessor.class);
+
+    private final Manager manager;
+    private final String id;
+
+
+    public StandardSessionAccessor(Manager manager, String id) {
+        if (manager == null) {
+            throw new 
IllegalStateException(sm.getString("standardSessionAccessor.nullManager"));
+        }
+        if (id == null) {
+            throw new 
IllegalStateException(sm.getString("standardSessionAccessor.nullId"));
+        }
+        this.manager = manager;
+        this.id = id;
+    }
+
+
+    @Override
+    public void access(Consumer<HttpSession> sessionConsumer) {
+
+        Session session;
+        try {
+            session = manager.findSession(id);
+        } catch (IOException e) {
+            throw new 
IllegalStateException(sm.getString("standardSessionAccessor.access.ioe", id));
+        }
+
+        if (session == null || !session.isValid()) {
+            // Should never be null but include it here just in case
+            throw new 
IllegalStateException(sm.getString("standardSessionAccessor.access.invalid", 
id));
+        }
+
+        session.access();
+        try {
+            sessionConsumer.accept(session.getSession());
+        } finally {
+            try {
+                session.endAccess();
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                log.warn(sm.getString("standardSessionAccessor.access.end"), 
t);
+            }
+        }
+    }
+}
diff --git a/java/org/apache/catalina/session/StandardSessionFacade.java 
b/java/org/apache/catalina/session/StandardSessionFacade.java
index c0cc5c9f70..526c749c9e 100644
--- a/java/org/apache/catalina/session/StandardSessionFacade.java
+++ b/java/org/apache/catalina/session/StandardSessionFacade.java
@@ -120,4 +120,10 @@ public class StandardSessionFacade implements HttpSession {
     public boolean isNew() {
         return session.isNew();
     }
+
+
+    @Override
+    public Accessor getAccessor() {
+        return session.getAccessor();
+    }
 }
diff --git a/test/org/apache/catalina/session/TestStandardSessionAccessor.java 
b/test/org/apache/catalina/session/TestStandardSessionAccessor.java
new file mode 100644
index 0000000000..619a302d75
--- /dev/null
+++ b/test/org/apache/catalina/session/TestStandardSessionAccessor.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.session;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpSession;
+import jakarta.servlet.http.HttpSession.Accessor;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Session;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+
+public class TestStandardSessionAccessor extends TomcatBaseTest {
+
+    @Test
+    public void testLastAccess() throws Exception {
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+
+        // No file system docBase required
+        Context ctx = getProgrammaticRootContext();
+
+        CountDownLatch latch = new CountDownLatch(1);
+
+        Tomcat.addServlet(ctx, "accessor", new AccessorServlet(latch));
+        ctx.addServletMappingDecoded("/accessor", "accessor");
+
+        tomcat.start();
+
+        int rc = getUrl("http://localhost:"; + getPort() + "/accessor", null, 
null);
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+
+        // There should be a single session in the Manager at this point
+        Session[] sessions = ctx.getManager().findSessions();
+        Assert.assertNotNull(sessions);
+        Assert.assertEquals(1, sessions.length);
+
+        Session session = sessions[0];
+
+        // Check the current last accessed time
+        long lastAccessedA = session.getLastAccessedTime();
+
+        // Wait 1 second
+        Thread.sleep(1000);
+
+        // Release the latch
+        latch.countDown();
+
+        // The last accessed time should have increased - allow up to 10 
seconds for that to happen
+        int count = 0;
+        long lastAccessedB = session.getLastAccessedTime();
+        while (lastAccessedB == lastAccessedA && count < 200) {
+            Thread.sleep(50);
+            lastAccessedB = session.getLastAccessedTime();
+            count++;
+        }
+
+        Assert.assertTrue("Session last access time not updated", 
lastAccessedB > lastAccessedA);
+    }
+
+
+    public static class AccessorServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        private final CountDownLatch latch;
+
+        public AccessorServlet(CountDownLatch latch) {
+            this.latch = latch;
+        }
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
+            HttpSession httpSession = req.getSession();
+            Accessor acessor = httpSession.getAccessor();
+
+            Thread t = new Thread(new AccessorRunnable(acessor, latch));
+            t.start();
+        }
+    }
+
+
+    public static class AccessorRunnable implements Runnable {
+
+        private final Accessor accessor;
+        private final CountDownLatch latch;
+
+        public AccessorRunnable(Accessor accessor, CountDownLatch latch) {
+            this.accessor = accessor;
+            this.latch = latch;
+        }
+
+
+        @Override
+        public void run() {
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+            accessor.access(new SessionUpdater());
+        }
+    }
+
+
+    public static class SessionUpdater implements Consumer<HttpSession> {
+
+        @Override
+        public void accept(HttpSession httpSession) {
+            // NO-OP - process of accessing the session will update the last 
accessed time.
+        }
+    }
+}
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index eb12e3d2d4..5732a67b37 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -105,6 +105,17 @@
   issues do not "pop up" wrt. others).
 -->
 <section name="Tomcat 11.0.0-M17 (markt)" rtext="in development">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        Implement <code>HttpSession.getAccessor()</code> which provides a
+        mechanism for applications to interact with an <code>HttpSession</code>
+        outside the standard Servlet processing of an HTTP request. This is
+        expected to be especially useful with applications using the Jakarta
+        WebSocket API. (markt)
+      </add>
+    </changelog>
+  </subsection>
   <subsection name="Coyote">
     <changelog>
       <fix>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to