You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by Luke Kirby <lk...@gmail.com> on 2005/09/14 23:29:21 UTC

[PATCH] immediately observe JMX-driven host alias changes

Heya,

I was surprised to discover that the Host object exposed via JMX
allows for addAlias/removeAlias operations but that these changes were
never communicated to the Mapper. As such, they weren't immediately
effective and were only really of use with the Admin application,
which can be used to write the new aliases of the host back to
server.xml, effective on restart.

This patch communicates JMX-driven host alias changes to the mapper so
that the alias is immediately available for use without any need for a
restart, much like JMX-driven host removals/additions already work.

The approach I've taken is to have the StandardHostMBean fire JMX
AttributeChangeNotifications after an add/removeAlias operation. I
changed MapperListener to observe these notifications and then reflect
the change in the the Mapper instance it serves (this required a
little more smarts in Mapper). I had to change
catalina/core/mbeans-descriptor.xml to reinstate StandardHostMBean as
the MBean to be used for StandardHost - I couldn't see where
StandardHostMBean was actually being used before.. any reason? I took
this approach because MapperListener was already JMX-based; I guess
one could choose to do something based more on internal events, but I
hope this suffices.

The patch follows my signature. Let me know if I've missed something
or this solution is unsatisfactory. Hope it works!

Thanks,

  Luke

Index: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/connector/MapperListener.java
===================================================================
RCS file: /home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/connector/MapperListener.java,v
retrieving revision 1.2
diff -u -r1.2 MapperListener.java
--- jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/connector/MapperListener.java	29
Jan 2005 19:42:27 -0000	1.2
+++ jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/connector/MapperListener.java	14
Sep 2005 18:03:31 -0000
@@ -24,6 +24,9 @@
 import javax.management.NotificationListener;
 import javax.management.ObjectInstance;
 import javax.management.ObjectName;
+import javax.management.AttributeChangeNotificationFilter;
+import javax.management.AttributeChangeNotification;
+import javax.management.ListenerNotFoundException;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -69,6 +72,13 @@
     private String domain="*";
     private String engine="*";
 
+    /**
+     * The filter for host alias attribute change notifications
+     */
+    private AttributeChangeNotificationFilter
hostAliasAttributeNotificationFilter =
+        new AttributeChangeNotificationFilter();
+
+
     // ----------------------------------------------------------- Constructors
 
 
@@ -77,6 +87,8 @@
      */
     public MapperListener(Mapper mapper) {
         this.mapper = mapper;
+        hostAliasAttributeNotificationFilter.disableAllAttributes();
+        hostAliasAttributeNotificationFilter.enableAttribute("aliases");
     }
 
 
@@ -159,6 +171,21 @@
             ObjectName objectName = new ObjectName(
                     "JMImplementation:type=MBeanServerDelegate");
             mBeanServer.removeNotificationListener(objectName, this);
+
+            // remove listeners from all host objects
+            Set hostMBeans = mBeanServer.queryMBeans(new
ObjectName(domain + ":type=Host,*"), null);
+            Iterator hostMBeanIt = hostMBeans.iterator();
+            while (hostMBeanIt.hasNext())
+            {
+                final ObjectInstance hostMBean =
(ObjectInstance)hostMBeanIt.next();
+                try
+                {
+                   
mBeanServer.removeNotificationListener(hostMBean.getObjectName(),
this);
+                } catch (Exception e) {
+                    log.warn("Error unregistering host listener", e);
+                }
+            }
+
         } catch (Exception e) {
             log.warn("Error unregistering MBeanServerDelegate", e);
         }
@@ -245,6 +272,25 @@
                     }
                 }
             }
+
+        } else if (notification instanceof AttributeChangeNotification) {
+
+            final AttributeChangeNotification attrChangeNotification
= (AttributeChangeNotification)notification;
+
+            if ("aliases".equals(attrChangeNotification.getAttributeName()))
+            {
+                final ObjectName hostName = (ObjectName)
attrChangeNotification.getSource();
+                try
+                {
+                    reconfigureAliases(
+                            hostName,
+                            (String[])attrChangeNotification.getNewValue());
+                }
+                catch (Throwable t)
+                {
+                    log.warn("Error updating aliases for " + hostName, t);
+                }
+            }
         }
 
     }
@@ -252,6 +298,12 @@
 
     // ------------------------------------------------------ Protected Methods
 
+    private void reconfigureAliases(ObjectName hostName, String[] newAliases)
+    {
+        final String name = hostName.getKeyProperty("host");
+        mapper.updateAliases(name, newAliases);
+    }
+
     private void registerEngine()
         throws Exception
     {
@@ -308,6 +360,19 @@
             String[] aliases = (String[])
                 mBeanServer.invoke(objectName, "findAliases", null, null);
             mapper.addHost(name, aliases, objectName);
+
+            // listen to the host for changes to its aliases
+            // try to remove the listener first to ensure that we
don't double-up
+            try
+            {
+                mBeanServer.removeNotificationListener(objectName, this);
+            }
+            catch (ListenerNotFoundException lnfEx)
+            {
+                // ignore unnecessary removal
+            }
+            mBeanServer.addNotificationListener(objectName, this,
hostAliasAttributeNotificationFilter, null);
+
             if(log.isDebugEnabled())
                 log.debug(sm.getString
                      ("mapperListener.registerHost", name, domain));
Index: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/core/mbeans-descriptors.xml
===================================================================
RCS file: /home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/core/mbeans-descriptors.xml,v
retrieving revision 1.42
diff -u -r1.42 mbeans-descriptors.xml
--- jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/core/mbeans-descriptors.xml	4
Feb 2005 23:39:59 -0000	1.42
+++ jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/core/mbeans-descriptors.xml	14
Sep 2005 18:24:57 -0000
@@ -370,6 +370,7 @@
          description="Standard Host Component"
          domain="Catalina"
          group="Host"
+         className="org.apache.catalina.mbeans.StandardHostMBean"
          type="org.apache.catalina.core.StandardHost">
     
     <attribute name="appBase"
Index: jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/mbeans/StandardHostMBean.java
===================================================================
RCS file: /home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/mbeans/StandardHostMBean.java,v
retrieving revision 1.3
diff -u -r1.3 StandardHostMBean.java
--- jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/mbeans/StandardHostMBean.java	27
Feb 2004 14:58:45 -0000	1.3
+++ jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/mbeans/StandardHostMBean.java	14
Sep 2005 17:46:42 -0000
@@ -17,6 +17,7 @@
 package org.apache.catalina.mbeans;
 
 
+import javax.management.Attribute;
 import javax.management.MBeanException;
 import javax.management.MBeanServer;
 import javax.management.RuntimeOperationsException;
@@ -81,7 +82,11 @@
         throws Exception {
 
         StandardHost host = (StandardHost) this.resource;
+        final String[] oldAliases = (String[])host.findAliases().clone();
         host.addAlias(alias);
+        sendAttributeChangeNotification(
+                new Attribute("aliases", oldAliases),
+                new Attribute("aliases", host.findAliases()));
 
     }
 
@@ -141,7 +146,11 @@
         throws Exception {
 
         StandardHost host = (StandardHost) this.resource;
+        final String[] oldAliases = (String[])host.findAliases().clone();
         host.removeAlias(alias);
+        sendAttributeChangeNotification(
+                new Attribute("aliases", oldAliases),
+                new Attribute("aliases", host.findAliases()));
 
     }
 
Index: jakarta-tomcat-connectors/util/java/org/apache/tomcat/util/http/mapper/Mapper.java
===================================================================
RCS file: /home/cvspublic/jakarta-tomcat-connectors/util/java/org/apache/tomcat/util/http/mapper/Mapper.java,v
retrieving revision 1.46
diff -u -r1.46 Mapper.java
--- jakarta-tomcat-connectors/util/java/org/apache/tomcat/util/http/mapper/Mapper.java	4
Jun 2005 17:34:23 -0000	1.46
+++ jakarta-tomcat-connectors/util/java/org/apache/tomcat/util/http/mapper/Mapper.java	14
Sep 2005 18:16:09 -0000
@@ -92,44 +92,142 @@
         newHost.name = name;
         newHost.contextList = contextList;
         newHost.object = host;
+        final Host activeHost;
         if (insertMap(hosts, newHosts, newHost)) {
             hosts = newHosts;
+            activeHost = newHost;
+        } else {
+            // insert failed because the host already exists; grab it
+            int hostPos = find(hosts, name);
+            if (hostPos >= 0) {
+                activeHost = hosts[hostPos];
+            } else {
+                // I can't imagine why this would happen..
+                activeHost = null;
+            }
         }
+
+        if (activeHost != null) {
+            addAliases(activeHost, aliases);
+        }
+    }
+
+    /**
+     * Add aliases to a host
+     * @param host the host
+     * @param aliases the aliases
+     */
+    private synchronized void addAliases(Host host, String[] aliases)
+    {
         for (int i = 0; i < aliases.length; i++) {
-            newHosts = new Host[hosts.length + 1];
-            newHost = new Host();
-            newHost.name = aliases[i];
-            newHost.contextList = contextList;
-            newHost.object = host;
-            if (insertMap(hosts, newHosts, newHost)) {
-                hosts = newHosts;
+            // a null alias may be supplied by updateAliases()
+            if (aliases[i] != null) {
+                Host[] newHosts = new Host[hosts.length + 1];
+                Host newHost = new Host();
+                newHost.name = aliases[i];
+                // share the alias host meta-data with the primary host
+                newHost.contextList = host.contextList;
+                newHost.object = host.object;
+                if (insertMap(hosts, newHosts, newHost)) {
+                    hosts = newHosts;
+                }
             }
         }
     }
 
-
     /**
-     * Remove a host from the mapper.
+     * Update the aliases for a host such that any aliases for the host
+     * that aren't in the update list are removed, and any that are in the
+     * update list but not the map are added.
      *
-     * @param name Virtual host name
+     * @param hostName the name of the host
+     * @param aliasUpdates the aliases for the host
      */
-    public synchronized void removeHost(String name) {
-        // Find and remove the old host
-        int pos = find(hosts, name);
+    public synchronized void updateAliases(String hostName, String[]
aliasUpdates)
+    {
+        int pos = find(hosts, hostName);
         if (pos < 0) {
             return;
         }
+
+        // we're going to set to null any existing aliases, so we'll
make a copy
+        String[] aliases = new String[aliasUpdates.length];
+        System.arraycopy(aliasUpdates, 0, aliases, 0, aliasUpdates.length);
+
+        Host host = hosts[pos];
+        Host[] originalHosts = (Host[])copyMap(hosts);
+        for (int i = 0; i < originalHosts.length; i++) {
+
+            if (originalHosts[i].object == host.object &&
originalHosts[i] != host) {
+
+                // we've found an alias entry for the given host
+                // go through each of the update aliases and see if
it's still required
+                boolean aliasShouldRemain = false;
+                for (int j = 0; j < aliases.length; j++) {
+                    if (originalHosts[i].name.equals(aliases[j])) {
+                        // this alias is a keeper - remove it from
the array so that
+                        // we know it's been accounted for
+                        aliases[j] = null;
+                        aliasShouldRemain = true;
+                        break;
+                    }
+                }
+
+                if (!aliasShouldRemain) {
+                    removeNamedHostOnly(originalHosts[i].name);
+                }
+            }
+        }
+
+        // any aliases we discovered that already existing have been nulled
+        // in the aliases array. Any remaining alias needs to be
added (the null
+        // entries will be ignored)
+        addAliases(host, aliases);
+
+    }
+
+    /**
+     * Remove a host but not any of the aliases associated with it
+     * @param name the host name
+     * @return the user host object associated with the removed host
+     */
+    private Object removeNamedHostOnly(String name)
+    {
+        int pos = find(hosts, name);
+        if (pos < 0) {
+            return null;
+        }
         Object host = hosts[pos].object;
         Host[] newHosts = new Host[hosts.length - 1];
         if (removeMap(hosts, newHosts, name)) {
             hosts = newHosts;
         }
-        // Remove all aliases (they will map to the same host object)
-        for (int i = 0; i < newHosts.length; i++) {
-            if (newHosts[i].object == host) {
-                Host[] newHosts2 = new Host[hosts.length - 1];
-                if (removeMap(hosts, newHosts2, newHosts[i].name)) {
-                    hosts = newHosts2;
+        return host;
+    }
+
+
+    /**
+     * Remove a host from the mapper and all the aliases associated with it
+     *
+     * @param name Virtual host name
+     */
+    public synchronized void removeHost(String name) {
+        // Find and remove the named host
+        Object host = removeNamedHostOnly(name);
+
+        if (host != null) {
+
+            // Remove all aliases (they will map to the same host object)
+
+            // Use a copy of the current hosts to avoid iteration through
+            // a mutating array
+            Host[] originalHosts = (Host[])copyMap(hosts);
+            for (int i = 0; i < originalHosts.length; i++) {
+                if (originalHosts[i].object == host) {
+                    Host[] newHosts = new Host[hosts.length - 1];
+                    if (removeMap(hosts, newHosts, originalHosts[i].name)) {
+                        hosts = newHosts;
+                    }
                 }
             }
         }
@@ -1182,6 +1280,12 @@
         return count;
     }
 
+    /**
+     * Copy a map
+     */
+    private static final MapElement[] copyMap(MapElement[] map) {
+        return (MapElement[]) map.clone();
+    }
 
     /**
      * Insert into the right place in a sorted MapElement array, and prevent

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