This is an automated email from the ASF dual-hosted git repository.
rmaucher pushed a commit to branch 11.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/11.0.x by this push:
new 157b8cbea5 Rework non replicated attributes behavior following review
157b8cbea5 is described below
commit 157b8cbea5dbf5864dc4bd60ce9753d31ac7e020
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]