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

rmaucher 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 82b1b208e3 Rework non replicated attributes behavior following review
82b1b208e3 is described below

commit 82b1b208e3b07b2cc09592e0a1977421565e0879
Author: remm <[email protected]>
AuthorDate: Fri Oct 2 08:56:21 2026 +0200

    Rework non replicated attributes behavior following review
    
    Skip replication for all possible internal attributes.
    Expanded by OpenCode for handling of read only attributes.
---
 .../apache/catalina/core/ApplicationContext.java   | 38 +++++++++--
 .../catalina/ha/context/ReplicatedContext.java     | 44 ++++++++++---
 .../catalina/ha/context/TestReplicatedContext.java | 74 ++++++++++++++++++++++
 3 files changed, 144 insertions(+), 12 deletions(-)

diff --git a/java/org/apache/catalina/core/ApplicationContext.java 
b/java/org/apache/catalina/core/ApplicationContext.java
index 48a19e0bd6..bd8bb8b649 100644
--- a/java/org/apache/catalina/core/ApplicationContext.java
+++ b/java/org/apache/catalina/core/ApplicationContext.java
@@ -553,7 +553,7 @@ public class ApplicationContext implements ServletContext {
 
         // Remove the specified attribute
         // Check for read only attribute
-        if (readOnlyAttributes.containsKey(name)) {
+        if (isReadOnlyAttribute(name)) {
             return;
         }
         Object value = attributes.remove(name);
@@ -610,7 +610,7 @@ public class ApplicationContext implements ServletContext {
 
         // Add or replace the specified attribute
         // Check for read only attribute
-        if (readOnlyAttributes.containsKey(name)) {
+        if (isReadOnlyAttribute(name)) {
             return;
         }
 
@@ -1213,17 +1213,46 @@ public class ApplicationContext implements 
ServletContext {
 
 
     /**
-     * Set an attribute as read only.
+     * Set an attribute as read only. The attribute is only marked read only 
if it is currently stored, possibly in
+     * a store maintained by a subclass.
+     *
+     * @param name The attribute name
      */
     void setAttributeReadOnly(String name) {
 
-        if (attributes.containsKey(name)) {
+        if (isAttributeStored(name)) {
             readOnlyAttributes.put(name, name);
         }
 
     }
 
 
+    /**
+     * Check if an attribute with the given name is currently stored in this 
context, possibly in a store
+     * maintained by a subclass. Subclasses that keep some attributes outside 
the main attribute map should
+     * override this method to allow those attributes to be made read only.
+     *
+     * @param name The attribute name
+     *
+     * @return {@code true} if an attribute with the given name is present
+     */
+    protected boolean isAttributeStored(String name) {
+        return attributes.containsKey(name);
+    }
+
+
+    /**
+     * Check if the attribute with the given name has been made read only.
+     *
+     * @param name The attribute name
+     *
+     * @return {@code true} if the attribute has been made read only
+     */
+    protected boolean isReadOnlyAttribute(String name) {
+        return readOnlyAttributes.containsKey(name);
+    }
+
+
     /**
      * Sets whether new ServletContextListeners are allowed.
      *
@@ -1233,6 +1262,7 @@ public class ApplicationContext implements ServletContext 
{
         this.newServletContextListenerAllowed = allowed;
     }
 
+
     /**
      * Internal class used as thread-local storage when doing path mapping 
during dispatch.
      */
diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java 
b/java/org/apache/catalina/ha/context/ReplicatedContext.java
index 027453a209..33c5925cc6 100644
--- a/java/org/apache/catalina/ha/context/ReplicatedContext.java
+++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -171,10 +171,28 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
      */
     protected static class ReplApplContext extends ApplicationContext {
         /**
-         * Attribute name used by Jasper for its application context. It holds
-         * node specific state and must therefore never be replicated.
+         * Attribute name prefixes reserved for internal use: the namespaces 
reserved by the Servlet
+         * specification and Tomcat's own namespace. Attributes using these 
names likely contain node
+         * specific state and must therefore never be replicated.
          */
-        private static final String JSP_APP_CONTEXT_NAME = 
"org.apache.jasper.runtime.JspApplicationContextImpl";
+        private static final String[] NON_REPLICATED_ATTRIBUTE_PREFIXES = { 
"java.", "javax.", "jakarta.",
+                "org.apache." };
+
+        /**
+         * Check if an attribute with the given name is an internal attribute 
that must not be replicated.
+         *
+         * @param attributeName The attribute name
+         *
+         * @return {@code true} if the attribute name starts with one of the 
internal prefixes
+         */
+        private static boolean isNonReplicatedAttribute(String attributeName) {
+            for (String prefix : NON_REPLICATED_ATTRIBUTE_PREFIXES) {
+                if (attributeName.startsWith(prefix)) {
+                    return true;
+                }
+            }
+            return false;
+        }
 
         /**
          * Map for Tomcat-specific attributes that should not be replicated.
@@ -226,16 +244,23 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
             // the other nodes. Values that cannot be serialized are stored
             // locally by the replicated map.
             for (Map.Entry<String,Object> entry : tomcatAttributes.entrySet()) 
{
-                if (JSP_APP_CONTEXT_NAME.equals(entry.getKey())) {
-                    continue;
+                if (!isNonReplicatedAttribute(entry.getKey())) {
+                    map.put(entry.getKey(), entry.getValue());
+                    tomcatAttributes.remove(entry.getKey());
                 }
-                map.put(entry.getKey(), entry.getValue());
-                tomcatAttributes.remove(entry.getKey());
             }
         }
 
+        @Override
+        protected boolean isAttributeStored(String name) {
+            return super.isAttributeStored(name) || 
tomcatAttributes.containsKey(name);
+        }
+
         @Override
         public void removeAttribute(String name) {
+            if (isReadOnlyAttribute(name)) {
+                return;
+            }
             Object localValue = tomcatAttributes.remove(name);
             boolean inAttributeMap = attributes.containsKey(name);
             super.removeAttribute(name);
@@ -255,7 +280,10 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
                 removeAttribute(name);
                 return;
             }
-            if ((!getParent().getState().isAvailable()) || 
JSP_APP_CONTEXT_NAME.equals(name)) {
+            if ((!getParent().getState().isAvailable()) || 
isNonReplicatedAttribute(name)) {
+                if (isReadOnlyAttribute(name)) {
+                    return;
+                }
                 Object oldValue = tomcatAttributes.put(name, value);
                 // The attribute is stored locally, so the event has to be 
fired
                 // here rather than by the attribute map
diff --git a/test/org/apache/catalina/ha/context/TestReplicatedContext.java 
b/test/org/apache/catalina/ha/context/TestReplicatedContext.java
index 52d72ed896..5c4ab8d1b3 100644
--- a/test/org/apache/catalina/ha/context/TestReplicatedContext.java
+++ b/test/org/apache/catalina/ha/context/TestReplicatedContext.java
@@ -18,7 +18,10 @@ package org.apache.catalina.ha.context;
 
 import java.io.File;
 import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Map;
 
+import jakarta.servlet.ServletContext;
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServlet;
 import jakarta.servlet.http.HttpServletRequest;
@@ -30,6 +33,7 @@ import org.junit.Test;
 import org.apache.catalina.Context;
 import org.apache.catalina.Host;
 import org.apache.catalina.LifecycleException;
+import org.apache.catalina.core.StandardContext;
 import org.apache.catalina.core.StandardHost;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
@@ -69,4 +73,74 @@ public class TestReplicatedContext extends TomcatBaseTest {
             resp.getWriter().print("OK");
         }
     }
+
+    @Test
+    public void testInternalAttributesNotReplicated() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        Host host = tomcat.getHost();
+        if (host instanceof StandardHost) {
+            ((StandardHost) 
host).setContextClass(ReplicatedContext.class.getName());
+        }
+
+        File root = new File("test/webapp");
+        Context context = tomcat.addWebapp(host, "", root.getAbsolutePath());
+        tomcat.start();
+
+        ServletContext servletContext = context.getServletContext();
+
+        String[] names = { "java.test.attr", "javax.test.attr", 
"jakarta.test.attr", "org.apache.test.attr" };
+        for (String name : names) {
+            servletContext.setAttribute(name, "value");
+        }
+        servletContext.setAttribute("test.attr", "value");
+
+        ReplicatedContext.ReplApplContext replApplContext = 
getReplApplContext(context);
+        Map<String,Object> replicated = replApplContext.getAttributeMap();
+
+        for (String name : names) {
+            Assert.assertEquals("value", servletContext.getAttribute(name));
+            Assert.assertFalse("Attribute should not be replicated: " + name, 
replicated.containsKey(name));
+            Assert.assertTrue("Attribute should be stored locally: " + name,
+                    replApplContext.tomcatAttributes.containsKey(name));
+        }
+
+        Assert.assertEquals("value", servletContext.getAttribute("test.attr"));
+        Assert.assertTrue("Attribute should be replicated", 
replicated.containsKey("test.attr"));
+    }
+
+    @Test
+    public void testReadOnlyAttributeEnforcedLocally() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+        Host host = tomcat.getHost();
+        if (host instanceof StandardHost) {
+            ((StandardHost) 
host).setContextClass(ReplicatedContext.class.getName());
+        }
+
+        File root = new File("test/webapp");
+        Context context = tomcat.addWebapp(host, "", root.getAbsolutePath());
+        tomcat.start();
+
+        ServletContext servletContext = context.getServletContext();
+
+        // The temp dir is set (and made read only) during context start, 
while the
+        // attribute is still stored locally rather than in the replicated map
+        ReplicatedContext.ReplApplContext replApplContext = 
getReplApplContext(context);
+        Object tempDir = 
replApplContext.tomcatAttributes.get(ServletContext.TEMPDIR);
+        Assert.assertNotNull("Temp dir attribute should be stored locally", 
tempDir);
+        Assert.assertFalse("Temp dir attribute should not be replicated",
+                
replApplContext.getAttributeMap().containsKey(ServletContext.TEMPDIR));
+
+        // Attempts to replace or remove the read only attribute must be 
ignored
+        servletContext.setAttribute(ServletContext.TEMPDIR, "unexpected");
+        Assert.assertSame(tempDir, 
servletContext.getAttribute(ServletContext.TEMPDIR));
+
+        servletContext.removeAttribute(ServletContext.TEMPDIR);
+        Assert.assertSame(tempDir, 
servletContext.getAttribute(ServletContext.TEMPDIR));
+    }
+
+    private static ReplicatedContext.ReplApplContext 
getReplApplContext(Context context) throws Exception {
+        Field field = StandardContext.class.getDeclaredField("context");
+        field.setAccessible(true);
+        return (ReplicatedContext.ReplApplContext) field.get(context);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to