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

commit be1f1d151d7221afe5ea8b2cf78d626b70f8a410
Author: opencode <[email protected]>
AuthorDate: Wed Sep 30 22:51:14 2026 +0200

    Fire ServletContextAttributeListener events for attributes stored in the 
local map of a ReplicatedContext by extracting the event firing logic of 
ApplicationContext into overridable hooks
---
 .../apache/catalina/core/ApplicationContext.java   | 31 +++++++++++++++++-----
 .../catalina/ha/context/ReplicatedContext.java     | 13 +++++++--
 2 files changed, 36 insertions(+), 8 deletions(-)

diff --git a/java/org/apache/catalina/core/ApplicationContext.java 
b/java/org/apache/catalina/core/ApplicationContext.java
index d499bbe0ab..48a19e0bd6 100644
--- a/java/org/apache/catalina/core/ApplicationContext.java
+++ b/java/org/apache/catalina/core/ApplicationContext.java
@@ -561,6 +561,17 @@ public class ApplicationContext implements ServletContext {
             return;
         }
 
+        fireContextAttributeRemoved(name, value);
+    }
+
+
+    /**
+     * Notify the application's <code>ServletContextAttributeListener</code>s 
of an attribute removal.
+     *
+     * @param name  The name of the attribute
+     * @param value The value the attribute had before it was removed
+     */
+    protected void fireContextAttributeRemoved(String name, Object value) {
         // Notify interested application event listeners
         Object[] listeners = context.getApplicationEventListeners();
         if (listeners == null || listeners.length == 0) {
@@ -606,17 +617,25 @@ public class ApplicationContext implements ServletContext 
{
         Object oldValue = attributes.put(name, value);
         boolean replaced = oldValue != null;
 
+        fireContextAttributeAddedOrReplaced(replaced, name, replaced ? 
oldValue : value);
+    }
+
+
+    /**
+     * Notify the application's <code>ServletContextAttributeListener</code>s 
of an attribute addition or replacement.
+     *
+     * @param replaced Whether the attribute was replaced rather than added
+     * @param name     The name of the attribute
+     * @param value    The value for the event: the previous value when the 
attribute was replaced, otherwise the new
+     *                     value
+     */
+    protected void fireContextAttributeAddedOrReplaced(boolean replaced, 
String name, Object value) {
         // Notify interested application event listeners
         Object[] listeners = context.getApplicationEventListeners();
         if (listeners == null || listeners.length == 0) {
             return;
         }
-        ServletContextAttributeEvent event;
-        if (replaced) {
-            event = new 
ServletContextAttributeEvent(context.getServletContext(), name, oldValue);
-        } else {
-            event = new 
ServletContextAttributeEvent(context.getServletContext(), name, value);
-        }
+        ServletContextAttributeEvent event = new 
ServletContextAttributeEvent(context.getServletContext(), name, value);
 
         for (Object obj : listeners) {
             if (!(obj instanceof ServletContextAttributeListener listener)) {
diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java 
b/java/org/apache/catalina/ha/context/ReplicatedContext.java
index d4c3012b33..4dda906f1a 100644
--- a/java/org/apache/catalina/ha/context/ReplicatedContext.java
+++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -235,8 +235,14 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
 
         @Override
         public void removeAttribute(String name) {
-            tomcatAttributes.remove(name);
+            Object localValue = tomcatAttributes.remove(name);
+            boolean inAttributeMap = attributes.containsKey(name);
             super.removeAttribute(name);
+            if (localValue != null && !inAttributeMap) {
+                // The attribute was only stored locally, so the removal event
+                // was not fired by the attribute map
+                fireContextAttributeRemoved(name, localValue);
+            }
         }
 
         @Override
@@ -249,7 +255,10 @@ public class ReplicatedContext extends StandardContext 
implements MapOwner {
                 return;
             }
             if ((!getParent().getState().isAvailable()) || 
JSP_APP_CONTEXT_NAME.equals(name)) {
-                tomcatAttributes.put(name, value);
+                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
+                fireContextAttributeAddedOrReplaced(oldValue != null, name, 
oldValue != null ? oldValue : value);
             } else {
                 super.setAttribute(name, value);
             }


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

Reply via email to