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 <[email protected]>
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: [email protected]
For additional commands, e-mail: [email protected]