[jboss-cvs] JBossAS SVN: r76052 - in trunk/tomcat: src/main/org/jboss/web/tomcat/service and 9 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Mon Jul 21 01:54:56 EDT 2008


Author: bstansberry at jboss.com
Date: 2008-07-21 01:54:56 -0400 (Mon, 21 Jul 2008)
New Revision: 76052

Added:
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/BasicClusterListener.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Constants.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/DefaultJBossWebEventHandler.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/JBossWebEventHandler.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/LocalStrings.properties
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ModClusterService.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Utils.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseEventType.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseListener.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertisedServer.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/BalancerConfiguration.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/MCMPHandlerConfiguration.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/ModClusterConfig.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/NodeConfiguration.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/SSLConfiguration.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandler.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandlerImpl.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/HASingletonAwareResetRequestSource.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceDRMEntry.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceHASingletonElectionPolicy.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/BooleanGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponseFilter.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/InetAddressGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/IntegerGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/MCMPServerDiscoveryEvent.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ModClusterServiceStateGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/PeerMCMPDiscoveryStatus.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ResetRequestGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/StringGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ThrowableGroupRpcResponse.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/LoadBalanceFactorProvider.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/impl/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/impl/StaticLoadBalanceFactorProvider.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mbeans-descriptors.xml
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/AddressPort.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPHandler.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequest.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequestType.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServer.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServerState.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPUtils.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/ResetRequestSource.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/DefaultMCMPHandler.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSEKeyManager.java
   trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSESocketFactory.java
Modified:
   trunk/tomcat/build.xml
Log:
[JBAS-5659] Intra-clustering communication component for mod_cluster

Modified: trunk/tomcat/build.xml
===================================================================
--- trunk/tomcat/build.xml	2008-07-21 05:49:57 UTC (rev 76051)
+++ trunk/tomcat/build.xml	2008-07-21 05:54:56 UTC (rev 76052)
@@ -67,7 +67,8 @@
       <path refid="jboss.metadata.classpath"/>      
       <path refid="jboss.jboss.cl.classpath"/>      
       <path refid="jboss.jboss.deployers.classpath"/>      
-      <path refid="jboss.jboss.man.classpath"/>      
+      <path refid="jboss.jboss.man.classpath"/>           
+      <path refid="jboss.jboss.mdr.classpath"/>      
       <path refid="jboss.microcontainer.classpath"/>
       <path refid="jboss.jboss.vfs.classpath"/>
       <path refid="jboss.integration.classpath"/>
@@ -76,6 +77,7 @@
       <path refid="jboss.jboss.ha.server.api.classpath"/>
       <path refid="jboss.jboss.jpa.deployers.classpath" />
       <path refid="jboss.jboss.reflect.classpath" />
+      <path refid="net.jcip.classpath" />
     </path>
 
     <!-- ======= -->
@@ -102,6 +104,7 @@
       <path refid="jboss.jca.classpath"/>
       <path refid="jboss.test.classpath" />
       <path refid="jboss.iiop.classpath"/>
+      <path refid="jboss.cluster.classpath"/>
     </path>
 
     <!-- The combined thirdparty classpath -->

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/BasicClusterListener.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/BasicClusterListener.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/BasicClusterListener.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,231 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2008, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.web.tomcat.service.modcluster;
+
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerEvent;
+import org.apache.catalina.ContainerListener;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.util.StringManager;
+import org.jboss.logging.Logger;
+
+
+
+/**
+ * This listener communicates with a front end mod_cluster enabled proxy to
+ * automatically maintain the node configuration according to what is 
+ * deployed.
+ */
+public class BasicClusterListener
+    implements LifecycleListener, ContainerListener
+{
+    protected static Logger log = Logger.getLogger(BasicClusterListener.class);
+
+    /**
+     * The string manager for this package.
+     */
+    protected StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // -------------------------------------------------------------- Constants
+
+    
+    // ----------------------------------------------------------------- Fields    
+    
+    /**
+     * Initialization flag.
+     */
+    protected boolean init = false;
+    
+    protected JBossWebEventHandler eventHandler;
+    
+    // ----------------------------------------------------------- Constructors
+    
+    public BasicClusterListener(JBossWebEventHandler eventHandler)
+    {
+       this.eventHandler = eventHandler;       
+    }
+    
+    protected BasicClusterListener()
+    {       
+    }
+    
+    // ------------------------------------------------------------- Properties
+
+
+        
+    // ---------------------------------------------- LifecycleListener Methods
+
+
+    /**
+     * Acknowledge the occurrence of the specified event.
+     * Note: Will never be called when the listener is associated to a Server,
+     * since it is not a Container.
+     *
+     * @param event ContainerEvent that has occurred
+     */
+    public void containerEvent(ContainerEvent event) {
+
+        Container container = event.getContainer();
+        Object child = event.getData();
+        String type = event.getType();
+
+        if (type.equals(Container.ADD_CHILD_EVENT)) {
+            if (container instanceof Host) {
+                // Deploying a webapp
+                ((Lifecycle) child).addLifecycleListener(this);
+                eventHandler.addContext((Context) child);
+            } else if (container instanceof Engine) {
+                // Deploying a host
+                container.addContainerListener(this);
+            }
+        } else if (type.equals(Container.REMOVE_CHILD_EVENT)) {
+            if (container instanceof Host) {
+                // Undeploying a webapp
+                ((Lifecycle) child).removeLifecycleListener(this);
+                eventHandler.removeContext((Context) child);
+            } else if (container instanceof Engine) {
+                // Undeploying a host
+                container.removeContainerListener(this);
+            }
+        }
+
+    }
+
+
+    /**
+     * Primary entry point for startup and shutdown events.
+     *
+     * @param event The event that has occurred
+     */
+    public void lifecycleEvent(LifecycleEvent event) {
+
+        Object source = event.getLifecycle();
+
+        if (Lifecycle.START_EVENT.equals(event.getType())) {
+            if (source instanceof Context) {
+                // Start a webapp
+               eventHandler.startContext((Context) source);
+            } else {
+                return;
+            }
+        } else if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) {
+            if (source instanceof Server) {
+                
+                eventHandler.init();
+                
+                startServer((Server) source);
+                
+                init = true;
+            } else {
+                return;
+            }
+        } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
+            if (source instanceof Context) {
+                // Stop a webapp
+               eventHandler.stopContext((Context) source);
+            } else if (source instanceof Server) {
+               
+                stopServer((Server) source);
+                
+                eventHandler.shutdown();
+                
+                init = false;
+            } else {
+                return;
+            }
+        } else if (Lifecycle.PERIODIC_EVENT.equals(event.getType())) {
+            if (init && source instanceof Engine) {
+               eventHandler.status((Engine) source);
+            }
+        }
+
+    } 
+
+
+    /**
+     * Send commands to the front end server associated with the startup of the
+     * node.
+     */
+    protected void startServer(Server server) {
+                
+        // Register ourself as a listener for child services
+        Service[] services = server.findServices();
+        for (int i = 0; i < services.length; i++) {
+            services[i].getContainer().addContainerListener(this);
+
+            Engine engine = (Engine) services[i].getContainer();
+            ((Lifecycle) engine).addLifecycleListener(this);
+            
+            Container[] children = engine.findChildren();
+            for (int j = 0; j < children.length; j++) {
+                children[j].addContainerListener(this);
+                Container[] children2 = children[j].findChildren();
+                for (int k = 0; k < children2.length; k++) {
+                    ((Lifecycle) children2[k]).addLifecycleListener(this);
+                }
+            }
+        }
+        
+        eventHandler.startServer(server);
+    }
+    
+    
+    /**
+     * Send commands to the front end server associated with the shutdown of the
+     * node.
+     */
+    protected void stopServer(Server server) {
+
+        // Register ourself as a listener to child components
+        Service[] services = server.findServices();
+        for (int i = 0; i < services.length; i++) {
+            services[i].getContainer().removeContainerListener(this);
+            ((Lifecycle) services[i].getContainer()).removeLifecycleListener(this);
+            Container[] children = services[i].getContainer().findChildren();
+            for (int j = 0; j < children.length; j++) {
+                children[j].removeContainerListener(this);
+                Container[] children2 = children[j].findChildren();
+                for (int k = 0; k < children2.length; k++) {
+                    ((Lifecycle) children2[k]).removeLifecycleListener(this);
+                }
+            }
+        }
+        
+        eventHandler.stopServer(server);
+    }
+    
+    protected void setEventHandler(JBossWebEventHandler handler)
+    {
+       this.eventHandler = handler;
+    }
+    
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Constants.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Constants.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Constants.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,31 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2006, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+
+package org.jboss.web.tomcat.service.modcluster;
+
+
+public class Constants {
+
+    public static final String Package = "org.jboss.web.tomcat.service.cluster";
+
+}


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Constants.java
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/DefaultJBossWebEventHandler.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/DefaultJBossWebEventHandler.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/DefaultJBossWebEventHandler.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,268 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.util.StringManager;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.modcluster.config.BalancerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.config.NodeConfiguration;
+import org.jboss.web.tomcat.service.modcluster.load.LoadBalanceFactorProvider;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPUtils;
+
+/**
+ * Default implementation of {@link JBossWebEventHandler}.
+ * 
+ * @author Brian Stansberry
+ */
+public class DefaultJBossWebEventHandler implements JBossWebEventHandler
+{
+   protected static Logger log = Logger.getLogger(DefaultJBossWebEventHandler.class);
+
+   // -----------------------------------------------------------------  Fields
+   
+   /**
+    * The string manager for this package.
+    */
+   private StringManager sm = StringManager.getManager(Constants.Package);
+   
+   private final NodeConfiguration nodeConfiguration;
+   private final BalancerConfiguration balancerConfiguration;
+   private final MCMPHandler mcmpHandler;
+   private final LoadBalanceFactorProvider loadBalanceFactorProvider;
+   
+   private boolean init;
+   
+   // -----------------------------------------------------------  Constructors
+   
+   public DefaultJBossWebEventHandler(NodeConfiguration nodeConfiguration,
+                                      BalancerConfiguration balancerConfiguration,
+                                      MCMPHandler mcmpHandler,
+                                      LoadBalanceFactorProvider loadBalanceFactorProvider)
+   {
+      this.nodeConfiguration = nodeConfiguration;
+      this.balancerConfiguration = balancerConfiguration;
+      this.mcmpHandler = mcmpHandler;
+      this.loadBalanceFactorProvider = loadBalanceFactorProvider;
+   }
+   
+   // ---------------------------------------------------- JBossWebEventHandler
+
+   public synchronized void init()
+   {
+      mcmpHandler.init();
+      init = true;
+   }
+   
+   public synchronized void shutdown()
+   {
+      mcmpHandler.shutdown();
+      init = false;
+   }
+
+   /**
+    * Send commands to the front end server associated with the startup of the
+    * node.
+    */
+   public void startServer(Server server) {
+               
+       Service[] services = server.findServices();
+       for (int i = 0; i < services.length; i++) {
+           
+           Engine engine = (Engine) services[i].getContainer();
+           
+           config(engine);
+           Container[] children = engine.findChildren();
+           for (int j = 0; j < children.length; j++) {
+               Container[] children2 = children[j].findChildren();
+               for (int k = 0; k < children2.length; k++) {
+                   addContext((Context) children2[k]);
+               }
+           }
+       }
+   }
+   
+   
+   /**
+    * Send commands to the front end server associated with the shutdown of the
+    * node.
+    */
+   public void stopServer(Server server) {
+
+       Service[] services = server.findServices();
+       for (int i = 0; i < services.length; i++) {
+           removeAll((Engine) services[i].getContainer());
+           Container[] children = services[i].getContainer().findChildren();
+           for (int j = 0; j < children.length; j++) {
+               Container[] children2 = children[j].findChildren();
+               for (int k = 0; k < children2.length; k++) {
+                   removeContext((Context) children2[k]);
+               }
+           }
+       }
+   }
+   
+   public void config(Engine engine)
+   {      
+      checkInit(); 
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.config", engine.getName()));
+      }
+      
+      // If needed, create automagical JVM route (address + port + engineName)
+      try {
+         Utils.establishJvmRouteAndConnectorAddress(engine, this.mcmpHandler);
+      } catch (Exception e) {
+         mcmpHandler.markProxiesInError();
+         log.info(sm.getString("clusterListener.error.addressJvmRoute"), e);
+         return;
+      }
+     
+     MCMPRequest request = MCMPUtils.createConfigRequest(engine, getNodeConfiguration(), getBalancerConfiguration());
+     
+     // Send CONFIG request
+     mcmpHandler.sendRequest(request);
+      
+   }
+   
+   public void addContext(Context context)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.context.enable", context.getPath(), context.getParent().getName(), ((StandardContext) context).getState()));
+     }
+
+     // Send ENABLE-APP if state is started
+     if (context.isStarted()) {
+        MCMPRequest request = MCMPUtils.createEnableAppRequest(context);
+        mcmpHandler.sendRequest(request);
+     }
+   }
+
+   public void startContext(Context context)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.context.start", context.getPath(), context.getParent().getName()));
+     }
+
+     // Send ENABLE-APP
+     MCMPRequest request = MCMPUtils.createEnableAppRequest(context);
+     mcmpHandler.sendRequest(request);
+   }
+
+   public void stopContext(Context context)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.context.stop", context.getPath(), context.getParent().getName()));
+     }
+
+     // Send STOP-APP
+     MCMPRequest request = MCMPUtils.createStopAppRequest(context);
+     mcmpHandler.sendRequest(request);
+   }
+
+   public void removeContext(Context context)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.context.disable", context.getPath(), context.getParent().getName(), ((StandardContext) context).getState()));
+     }
+
+     // JVMRoute can be null here if nothing was ever initialized
+     if (Utils.getJvmRoute(context) != null) {
+        MCMPRequest request = MCMPUtils.createRemoveAppRequest(context);
+        mcmpHandler.sendRequest(request);
+     }
+   }
+
+   public void removeAll(Engine engine)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+         log.debug(sm.getString("clusterListener.stop", engine.getName()));
+     }
+
+     // JVMRoute can be null here if nothing was ever initialized
+     if (engine.getJvmRoute() != null) {
+        // Send REMOVE-APP * request
+        MCMPRequest request = MCMPUtils.createRemoveAllRequest(engine);
+        mcmpHandler.sendRequest(request);
+     }
+   }
+
+   public void status(Engine engine)
+   {
+      checkInit();
+      
+      if (log.isDebugEnabled()) {
+          log.debug(sm.getString("clusterListener.status", engine.getName()));
+      }
+      
+      mcmpHandler.status();
+      
+      // Send STATUS request
+      int lbf = getLoadBalanceFactorProvider().getLoadBalanceFactor();
+      MCMPRequest request = MCMPUtils.createStatusRequest(engine, lbf);
+      mcmpHandler.sendRequest(request);
+   }
+
+   // -----------------------------------------------------------------  Public
+   
+   public BalancerConfiguration getBalancerConfiguration()
+   {
+      return this.balancerConfiguration;
+   }
+
+   public NodeConfiguration getNodeConfiguration()
+   {
+      return this.nodeConfiguration;
+   }
+
+   public LoadBalanceFactorProvider getLoadBalanceFactorProvider()
+   {
+      return this.loadBalanceFactorProvider;
+   }
+   
+   // ----------------------------------------------------------------  Private
+   
+   private void checkInit()
+   {
+      if (!init)
+         throw new IllegalStateException("Not initialized; call init() first");
+   }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/JBossWebEventHandler.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/JBossWebEventHandler.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/JBossWebEventHandler.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,55 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.web.tomcat.service.modcluster;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Server;
+
+/**
+ * Handles events notifications coming from JBoss Web via a cluster listener.
+ * 
+ * @author Brian Stansberry
+ */
+public interface JBossWebEventHandler
+{
+   void init();
+   void shutdown();
+   
+   void addContext(Context context);
+
+   void startContext(Context context);
+
+   void stopContext(Context context);
+
+   void removeContext(Context context);
+   
+   void config(Engine engine);
+   
+   void removeAll(Engine engine);
+
+   void status(Engine engine);
+   
+   void startServer(Server server);
+   
+   void stopServer(Server server);
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/LocalStrings.properties
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/LocalStrings.properties	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/LocalStrings.properties	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,24 @@
+# Regular messages
+clusterListener.address=Detected local address [{0}]
+clusterListener.config=Sending configuration for engine [{0}]
+clusterListener.context.disable=Undeploy context [{0}] with state [{2}] to Host [{1}]
+clusterListener.context.enable=Deploy context [{0}] with state [{2}] to Host [{1}]
+clusterListener.context.start=Start context [{0}] in Host [{1}]
+clusterListener.context.stop=Stop context [{0}] in Host [{1}]
+clusterListener.jvmRoute=Engine [{0}] will use jvmRoute [{1}]
+clusterListener.request=Sending command [{0}] wildcard [{1}] to proxy [{2}]
+clusterListener.status=Check status for engine [{0}]
+clusterListener.stop=Stop all web applications for engine [{0}]
+
+# Error messages
+clusterListener.error.addressJvmRoute=Error connecting to proxy to determine Engine.JVMRoute or Connector.address
+clusterListener.error.invalidHost=Invalid host specified: {0}
+clusterListener.error.io=IO error sending command {0} to proxy {1}
+clusterListener.error.jmxRegister=Error during JMX registration
+clusterListener.error.jmxUnregister=Error during JMX unregistration
+clusterListener.error.nullAttribute=Value for attribute {0} cannot be null
+clusterListener.error.other=Error [{2}: {3}] sending command {0} to proxy {1}, configuration will be reset
+clusterListener.error.parse=Error parsing response header for command {0}
+clusterListener.error.startListener=Error starting advertise listener
+clusterListener.error.stopListener=Error stopping advertise listener
+clusterListener.error.syntax=Unrecoverable syntax error [{2}: {3}] sending command {0} to proxy {1}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ModClusterService.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ModClusterService.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ModClusterService.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,799 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Server;
+import org.apache.catalina.util.StringManager;
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.ha.singleton.HASingletonElectionPolicySimple;
+import org.jboss.ha.singleton.HASingletonElector;
+import org.jboss.ha.singleton.HASingletonSupport;
+import org.jboss.web.tomcat.service.modcluster.config.BalancerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.config.ModClusterConfig;
+import org.jboss.web.tomcat.service.modcluster.config.NodeConfiguration;
+import org.jboss.web.tomcat.service.modcluster.ha.ModClusterServiceDRMEntry;
+import org.jboss.web.tomcat.service.modcluster.ha.ModClusterServiceHASingletonElectionPolicy;
+import org.jboss.web.tomcat.service.modcluster.ha.ClusteredMCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.ha.ClusteredMCMPHandlerImpl;
+import org.jboss.web.tomcat.service.modcluster.ha.HASingletonAwareResetRequestSource;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.BooleanGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.ModClusterServiceStateGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.GroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.InetAddressGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.MCMPServerDiscoveryEvent;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.PeerMCMPDiscoveryStatus;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.StringGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.ThrowableGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.load.LoadBalanceFactorProvider;
+import org.jboss.web.tomcat.service.modcluster.mcmp.AddressPort;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServer;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPUtils;
+import org.jboss.web.tomcat.service.modcluster.mcmp.impl.DefaultMCMPHandler;
+
+/**
+ * A ModClusterService.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public class ModClusterService extends HASingletonSupport 
+   implements JBossWebEventHandler
+{
+   // -----------------------------------------------------------------  Fields
+   
+   /**
+    * The string manager for this package.
+    */
+   private StringManager sm = StringManager.getManager(Constants.Package);
+   
+   private final HASingletonAwareResetRequestSource resetRequestSource;
+   private final MCMPHandler localHandler;
+   private final ClusteredMCMPHandler clusteredHandler;
+   private final LoadBalanceFactorProvider loadManager;
+   private final RpcHandler rpcHandler = new RpcHandler();
+   private final JBossWebEventHandler eventHandlerDelegate;
+   private final ModClusterServiceHASingletonElectionPolicy electionPolicy;
+   private final String domain;
+   private final Map<ClusterNode, MCMPServerDiscoveryEvent> proxyChangeDigest = 
+      new HashMap<ClusterNode, MCMPServerDiscoveryEvent>();
+   
+   
+   private volatile int statusCount = 0;
+   private volatile int processStatusFrequency = 1;
+   private volatile int latestLoad;
+   private ModClusterServiceDRMEntry drmEntry;
+   
+   
+   /**
+    * Create a new ClusterCoordinator.
+    * 
+    * @param partition   the partition of which we are a member
+    * @param config      our configuration
+    * @param loadFactorProvider source for local load balance statistics
+    * @param singletonElector chooses the singleton master
+    */
+   public ModClusterService(HAPartition partition,
+                             ModClusterConfig config,
+                             LoadBalanceFactorProvider loadFactorProvider,
+                             HASingletonElector singletonElector)
+   {      
+      assert partition != null          : "partition is null";        
+      assert loadFactorProvider != null : "loadFactorProvider is null";         
+      assert config != null             : "config is null";
+      
+      setHAPartition(partition);
+      
+      this.resetRequestSource = new HASingletonAwareResetRequestSource(config, config, partition, ClusteredMCMPHandler.HA_SERVICE_NAME);
+      this.localHandler = new DefaultMCMPHandler(config, this.resetRequestSource, false);
+      this.clusteredHandler = new ClusteredMCMPHandlerImpl(this.localHandler, partition, ClusteredMCMPHandler.HA_SERVICE_NAME);
+      this.loadManager = loadFactorProvider;
+      this.eventHandlerDelegate = new DefaultJBossWebEventHandler(config, config, clusteredHandler, loadFactorProvider);
+      this.domain = config.getDomain();      
+      if (singletonElector == null)
+         singletonElector = new HASingletonElectionPolicySimple();
+      this.electionPolicy = new ModClusterServiceHASingletonElectionPolicy(singletonElector);
+      setElectionPolicy(this.electionPolicy);
+      
+      this.drmEntry = new ModClusterServiceDRMEntry(partition.getClusterNode(), null);
+   }
+   
+   /**
+    * Create a new ClusterCoordinator using the given component parts.
+    * Only intended for use by test suites that may wish to inject
+    * mock components.
+    * 
+    * @param partition
+    * @param nodeConfig
+    * @param balancerConfig
+    * @param localHandler
+    * @param resetRequestSource
+    * @param clusteredHandler
+    * @param loadManager
+    * @param singletonElector
+    */
+   protected ModClusterService(HAPartition partition,
+         NodeConfiguration nodeConfig,
+         BalancerConfiguration balancerConfig,
+         MCMPHandler localHandler,
+         HASingletonAwareResetRequestSource resetRequestSource,
+         ClusteredMCMPHandler clusteredHandler,
+         LoadBalanceFactorProvider loadManager,
+         HASingletonElector singletonElector)
+   {
+      assert partition != null        : "partition is null";
+      assert localHandler != null     : "mcmpHandler is null";         
+      assert loadManager != null      : "loadManager is null";   
+      assert resetRequestSource != null : "resetRequestSource is null";       
+      assert nodeConfig != null       : "nodeConfig is null"; 
+      assert balancerConfig != null   : "balancerConfig is null";
+      assert clusteredHandler != null : "clusteredHandler is null";
+      
+      setHAPartition(partition);
+      
+      this.localHandler = localHandler;
+      this.resetRequestSource = resetRequestSource;
+      this.clusteredHandler = clusteredHandler;
+      this.loadManager = loadManager;
+      this.eventHandlerDelegate = new DefaultJBossWebEventHandler(nodeConfig, balancerConfig, clusteredHandler, loadManager);
+      this.domain = nodeConfig.getDomain();  
+      
+      if (singletonElector == null)
+         singletonElector = new HASingletonElectionPolicySimple();
+      this.electionPolicy = new ModClusterServiceHASingletonElectionPolicy(singletonElector);
+      setElectionPolicy(this.electionPolicy);
+      
+      this.drmEntry = new ModClusterServiceDRMEntry(partition.getClusterNode(), null);
+   }
+   
+   // ---------------------------------------------------- JBossWebEventHandler
+
+   public void init()
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.init();
+   }
+
+   public void shutdown()
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.shutdown();      
+   }
+   
+   
+   public void startServer(Server server)
+   {
+      // Pass on ref to our server
+      this.resetRequestSource.setJbossWebServer(server);
+      
+      // Use the standard logic
+      this.eventHandlerDelegate.startServer(server); 
+   }
+
+   public void stopServer(Server server)
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.stopServer(server); 
+   }
+
+   public void config(Engine engine)
+   {          
+      // If needed, create automagical JVM route (address + port + engineName)
+      try {
+         Utils.establishJvmRouteAndConnectorAddress(engine, this.clusteredHandler);
+      } catch (Exception e) {
+         this.clusteredHandler.markProxiesInError();
+         log.info(sm.getString("clusterListener.error.addressJvmRoute"), e);
+         return;
+      }
+      
+      this.drmEntry.addJvmRoute(engine.getJvmRoute());
+      updateLocalDRM(drmEntry);
+      
+      // Use the standard logic
+      this.eventHandlerDelegate.config(engine);
+   }
+   
+   public void removeAll(Engine engine)
+   {       
+      // Use the standard logic
+      this.eventHandlerDelegate.removeAll(engine);
+      
+      this.drmEntry.removeJvmRoute(engine.getJvmRoute());
+      updateLocalDRM(drmEntry);      
+   }
+   
+   public void addContext(Context context)
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.addContext(context);       
+   }
+   
+   public void startContext(Context context)
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.startContext(context);       
+   }
+   
+   public void stopContext(Context context)
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.stopContext(context);        
+   }
+   
+   public void removeContext(Context context)
+   {
+      // Use the standard logic
+      this.eventHandlerDelegate.removeContext(context);        
+   }
+   
+   public void status(Engine engine)
+   {
+      this.latestLoad = loadManager.getLoadBalanceFactor();
+      
+      if (this.isMasterNode())
+      {
+         statusCount = (statusCount + 1) % processStatusFrequency;
+         if (statusCount == 0) 
+         {
+            // 1) Poll everybody for status
+            // 2) Deal with any dropped mcmp request situations
+            // 3) Update the LBFs on mod_cluster
+            // 4) Get the mod_cluster proxy lists in sync
+            updateClusterStatus();
+         }
+      }
+   }
+
+   
+   // ------------------------------------------------------------- Properties
+
+   public String getDomain()
+   {
+      return this.domain;
+   }
+   
+   public int getProcessStatusFrequency()
+   {
+      return this.processStatusFrequency;
+   }
+
+   public void setProcessStatusFrequency(int processStatusFrequency)
+   {
+      this.processStatusFrequency = processStatusFrequency;
+   }
+   
+
+   // -------------------------------------------------------  Public Overrides
+
+   @Override
+   public void _stopOldMaster()
+   {
+      this.stopOldMaster(this.getDomain());
+   }
+
+   @Override
+   public void startSingleton()
+   {
+      super.startSingleton();
+      
+      this.clusteredHandler.setMasterNode(true);
+      this.resetRequestSource.setMasterNode(true);
+      
+      // Ensure we do a full status on the next event
+      this.statusCount = processStatusFrequency - 1;
+   }
+
+   @Override
+   public void stopSingleton()
+   {
+      super.stopSingleton();
+      
+      this.clusteredHandler.setMasterNode(false); 
+      this.resetRequestSource.setMasterNode(false);
+   }   
+
+   // --------------------------------------------------------------  Protected
+
+   /**
+    * {@inheritDoc}
+    * 
+    * We override the superclass to pass our domain as a param to the 
+    * "_stopOldMaster" call.
+    */
+   @Override
+   protected void makeThisNodeMaster()
+   {
+      try
+      {
+         // stop the old master (if there is one) before starting the new one
+
+         // ovidiu 09/02/04 - temporary solution for Case 1843, use an asynchronous
+         // distributed call.
+         this.callAsyncMethodOnPartition("stopOldMaster", new Object[]{ getDomain() }, new Class[]{ String.class });
+
+         this.startNewMaster();
+      }
+      catch (Exception ex)
+      {
+         this.log.error("_stopOldMaster failed. New master singleton will not start.", ex);
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    * 
+    * @return an inner class that allows us to avoid exposing RPC methods as
+    *         public methods of this class
+    */
+   @Override
+   protected Object getRPCHandler()
+   {
+      return rpcHandler;
+   }   
+
+   /**
+    * {@inheritDoc}
+    * 
+    * @return a {@link ModClusterServiceDRMEntry}
+    */
+   @Override
+   protected Serializable getReplicant()
+   {
+      return drmEntry;
+   }
+   
+   // ----------------------------------------------------------------- Private
+
+   private void stopOldMaster(String domain)
+   {
+      if (safeEquals(domain, this.getDomain()))
+      {
+         super._stopOldMaster();
+      }
+      else
+      {
+         log.debug("ignoring call to _stopOldMaster for domain " + domain + 
+                   " as we are in domain " + this.getDomain());
+      }
+   }
+   
+   private boolean safeEquals(Object a, Object b)
+   {      
+      return (a == b || (a != null && a.equals(b)));
+   }
+   
+   private void updateClusterStatus()
+   {
+      this.localHandler.status();
+      Set<MCMPServerState> masterList = null;
+      Map<ClusterNode, MCMPServerDiscoveryEvent> latestEvents = null;
+      synchronized (proxyChangeDigest)
+      {
+         masterList = this.localHandler.getProxyStates();
+         latestEvents = new HashMap<ClusterNode, MCMPServerDiscoveryEvent>(proxyChangeDigest);
+      }
+      HAPartition partition = getHAPartition();      
+      List<ModClusterServiceDRMEntry> replicants = partition.getDistributedReplicantManager().lookupReplicants(getServiceHAName());
+      Map<ClusterNode, ModClusterServiceDRMEntry> nonresponsive = new HashMap<ClusterNode, ModClusterServiceDRMEntry>();
+      for (ModClusterServiceDRMEntry replicant : replicants)
+      {
+         nonresponsive.put(replicant.getPeer(), replicant);
+      }
+      nonresponsive.remove(partition.getClusterNode());
+      
+      // FIXME -- what about our own dropped discovery events if we just became master?
+      List responses = null;
+      try
+      {
+         responses = partition.callMethodOnCluster(getServiceHAName(), "getClusterCoordinatorState", new Object[]{ masterList }, new Class[]{ Set.class }, true);
+      }
+      catch (Exception e)
+      {
+         throw Utils.convertToUnchecked(e);
+      }
+      
+      // Gather up all the reset requests in one list
+      // FIXME -- what about our own dropped requests if we just became master?
+      List<MCMPRequest> resetRequests = new ArrayList<MCMPRequest>();
+      
+      // Gather all the load balance factors
+      Map<String, Integer> loadBalanceFactors = new HashMap<String, Integer>();
+      
+      // Gather the info on who knows about what proxies
+      Map<ClusterNode, PeerMCMPDiscoveryStatus> statuses = new HashMap<ClusterNode, PeerMCMPDiscoveryStatus>();
+      
+      boolean resync = false;
+      for (Object response : responses)
+      {
+         if (response instanceof ModClusterServiceStateGroupRpcResponse)
+         {
+            ModClusterServiceStateGroupRpcResponse mcssgrr = (ModClusterServiceStateGroupRpcResponse) response;
+            ClusterNode cn = mcssgrr.getSender();
+            
+            // Check for discovery events we haven't processed
+            MCMPServerDiscoveryEvent latestEvent = latestEvents.get(cn);
+            for (MCMPServerDiscoveryEvent toCheck : mcssgrr.getUnacknowledgedEvents())
+            {
+               if (latestEvent != null && latestEvent.getEventIndex() <= toCheck.getEventIndex())
+                  continue; // already processed it
+            
+               AddressPort ap = toCheck.getMCMPServer();
+               if (toCheck.isAddition())
+               {
+                  this.localHandler.addProxy(ap.address, ap.port);
+               }
+               else
+               {
+                  this.localHandler.removeProxy(ap.address, ap.port);
+               }
+               resync = true;
+            }
+            
+            if (!resync) // don't bother if we are going to start over
+            {
+               statuses.put(cn, new PeerMCMPDiscoveryStatus(cn, mcssgrr.getStates(), latestEvent));
+               
+               resetRequests.addAll(mcssgrr.getResetRequests());
+               
+               ModClusterServiceDRMEntry removed = nonresponsive.remove(cn);
+               if (removed != null)
+               {
+                  Integer lbf = new Integer(mcssgrr.getLoadBalanceFactor());
+                  for (String jvmRoute : removed.getJvmRoutes())
+                  {
+                     loadBalanceFactors.put(jvmRoute, lbf);
+                  }
+               }
+            }
+         }
+         else if (response instanceof ThrowableGroupRpcResponse)
+         {
+            ThrowableGroupRpcResponse tgrr = (ThrowableGroupRpcResponse) response;
+            ClusterNode cn = tgrr.getSender();
+            
+            log.warn("Call to getClusterCoordinatorState received throwable from " + cn, tgrr.getValue());
+            
+            // Don't remove from nonresponsive list and we'll pass back an error 
+            // status (null server list) to this peer
+         }
+         else if (response instanceof Throwable)
+         {
+            log.warn("Call to getClusterCoordinatorState received throwable from unknown server", (Throwable) response);
+         }
+         else
+         {
+            log.error("Call to getClusterCoordinatorState received unexpected response : " + response);
+         }
+      }
+      
+      if (resync)
+      {
+         // We picked up previously unknown discovery events; start over
+         updateClusterStatus();
+         return;
+      }
+      
+      // Add error-state objects for non-responsive peers      
+      Integer lbf = new Integer(0);
+      for (Map.Entry<ClusterNode, ModClusterServiceDRMEntry> entry : nonresponsive.entrySet())
+      {
+         ClusterNode cn = entry.getKey();
+         statuses.put(entry.getKey(), new PeerMCMPDiscoveryStatus(cn, null, latestEvents.get(cn)));
+         
+         for (String jvmRoute : entry.getValue().getJvmRoutes())
+         {
+            loadBalanceFactors.put(jvmRoute, lbf);
+         }
+      }
+      
+      // FIXME handle crashed members, gone from DRM
+      
+      // Advise the proxies of any reset requests
+      for (MCMPRequest resetRequest : resetRequests)
+      {
+         this.localHandler.sendRequest(resetRequest);
+      }
+      
+      // Pass along the LBF values
+      for (Map.Entry<String, Integer> entry : loadBalanceFactors.entrySet())
+      {
+         MCMPRequest req = MCMPUtils.createStatusRequest(entry.getKey(), entry.getValue().intValue());
+         this.localHandler.sendRequest(req);
+      }
+      
+      // Advise the members the process is done and that they should update DRM
+      notifyClusterStatusComplete(masterList, statuses);      
+   }
+
+   private void notifyClusterStatusComplete(Set<MCMPServerState> masterList, 
+                           Map<ClusterNode, PeerMCMPDiscoveryStatus> statuses)
+   {
+      HAPartition partition = getHAPartition();
+      
+      // Determine who should update DRM first -- us or the rest of the nodes
+      Set<ModClusterServiceDRMEntry> allStatuses = new HashSet<ModClusterServiceDRMEntry>(statuses.values());
+      ModClusterServiceDRMEntry ourCurrentStatus = (ModClusterServiceDRMEntry) partition.getDistributedReplicantManager().lookupLocalReplicant(getServiceHAName());
+      allStatuses.add(ourCurrentStatus);
+      
+      boolean othersFirst = this.electionPolicy.narrowCandidateList(allStatuses).contains(partition.getClusterNode());
+      ModClusterServiceDRMEntry ourNewStatus = new ModClusterServiceDRMEntry(partition.getClusterNode(), masterList);
+      boolean updated = (ourNewStatus.equals(ourCurrentStatus) == false);
+      
+      if (othersFirst)
+      {
+         updateRemoteDRMs(statuses);
+         
+         if (updated)
+         {
+            updateLocalDRM(ourNewStatus);
+         }         
+      }
+      else
+      {
+         if (updated)
+         {
+            updateLocalDRM(ourNewStatus);
+         }
+         
+         updateRemoteDRMs(statuses);
+      }
+   }
+
+   private void updateRemoteDRMs(Map<ClusterNode, PeerMCMPDiscoveryStatus> statuses)
+   {
+      try
+      {
+         getHAPartition().callMethodOnCluster(getServiceHAName(), "updateDRM", new Object[]{ statuses }, new Class[]{ Map.class }, true);
+      }
+      catch (Exception e)
+      {
+         log.error("Caught exception advising cluster to update DRM", e);
+      }
+   }
+
+   private void updateLocalDRM(ModClusterServiceDRMEntry ourNewStatus) throws Error
+   {
+      try
+      {
+         getHAPartition().getDistributedReplicantManager().add(getServiceHAName(), ourNewStatus);
+      }
+      catch (Exception e)
+      {
+         throw Utils.convertToUnchecked(e);
+      }
+   }
+   
+
+   // ---------------------------------------------------------- Inner classes
+   
+   /**
+    * This is the object that gets invoked on via reflection by HAPartition.
+    */
+   private class RpcHandler
+   {
+      private final ModClusterService coord = ModClusterService.this;
+      private final GroupRpcResponse SUCCESS = 
+         new GroupRpcResponse(this.coord.getHAPartition().getClusterNode());
+      
+      
+      public void stopOldMaster(String domain)
+      {
+         this.coord.stopOldMaster(domain);
+      }
+
+      public InetAddressGroupRpcResponse getLocalAddress() throws IOException
+      {
+         if (this.coord.isMasterNode())      
+         {
+            return new InetAddressGroupRpcResponse(this.coord.getHAPartition().getClusterNode(), 
+                                                   this.coord.localHandler.getLocalAddress());
+         }
+         else
+         {         
+            return null;
+         }
+      }
+      
+      public GroupRpcResponse mcmpServerDiscoveryEvent(MCMPServerDiscoveryEvent event)
+      {
+         if (this.coord.isMasterNode())      
+         {
+            synchronized (proxyChangeDigest)
+            {
+               AddressPort ap = event.getMCMPServer();
+               if (event.isAddition())
+               {
+                  this.coord.localHandler.addProxy(ap.address, ap.port);
+               }
+               else
+               {
+                  this.coord.localHandler.removeProxy(ap.address, ap.port);
+               }
+               proxyChangeDigest.put(event.getSender(), event);
+               return new GroupRpcResponse(getHAPartition().getClusterNode());
+            }            
+         }
+         else
+         {         
+            return null;
+         }           
+      }
+      
+      public ModClusterServiceStateGroupRpcResponse getClusterCoordinatorState(Set<MCMPServer> masterList)
+      {
+         if (this.coord.isMasterNode() == false)      
+         {
+            Set<MCMPServerState> ourStates = this.coord.clusteredHandler.updateServersFromMasterNode(masterList);
+            
+            List<MCMPRequest> resetRequests = null;
+            boolean needReset = this.coord.clusteredHandler.getNeedsResetTransmission();
+            if (needReset)
+            {
+               resetRequests = this.coord.resetRequestSource.getLocalResetRequests();
+            }
+            
+            ModClusterServiceStateGroupRpcResponse response = 
+               new ModClusterServiceStateGroupRpcResponse(getHAPartition().getClusterNode(), 
+                                                           this.coord.latestLoad,
+                                                           ourStates,
+                                                           this.coord.clusteredHandler.getPendingDiscoveryEvents(),
+                                                           resetRequests);
+            
+            if (needReset)
+            {
+               this.coord.clusteredHandler.recordResetTransmission();
+            }
+            
+            return response;
+         }
+         else
+         {         
+            // TODO is this the correct response here?
+            return null;
+         }   
+         
+      }
+      
+      public void updateDRM(Map<ClusterNode, PeerMCMPDiscoveryStatus> statuses)
+      {
+         HAPartition partition = this.coord.getHAPartition();
+         ClusterNode cn = partition.getClusterNode();
+         PeerMCMPDiscoveryStatus newStatus = statuses.get(cn);
+         if (newStatus != null)
+         {
+            // Notify our handler that discovery events have been processed
+            this.coord.clusteredHandler.discoveryEventsReceived(newStatus.getLatestDiscoveryEvent());
+            
+            // Notify our handler that any reset requests have been processed
+            this.coord.clusteredHandler.recordResetSuccess();
+            
+            DistributedReplicantManager drm = partition.getDistributedReplicantManager();
+            String haName = this.coord.getServiceHAName();
+            ModClusterServiceDRMEntry oldStatus = 
+               (ModClusterServiceDRMEntry) drm.lookupLocalReplicant(haName);
+            if (newStatus.equals(oldStatus) == false)
+            {
+               try
+               {
+                  drm.add(haName, new ModClusterServiceDRMEntry(cn, newStatus.getMCMPServerStates()));
+               }
+               catch (Exception e)
+               {
+                  this.coord.log.error("Error updating DRM", e);
+               }
+            }
+         }
+      }
+
+      public StringGroupRpcResponse getProxyConfiguration()
+      {
+         if (this.coord.isMasterNode())      
+         {
+            return new StringGroupRpcResponse(getHAPartition().getClusterNode(), this.coord.localHandler.getProxyConfiguration());
+         }
+         else
+         {         
+            return null;
+         }
+      }
+
+      public BooleanGroupRpcResponse isProxyHealthOK()
+      {
+         if (this.coord.isMasterNode())      
+         {
+            return new BooleanGroupRpcResponse(getHAPartition().getClusterNode(), this.coord.localHandler.isProxyHealthOK());
+         }
+         else
+         {         
+            return null;
+         }
+      }
+
+      public GroupRpcResponse markProxiesInError()
+      {
+         if (this.coord.isMasterNode())      
+         {
+            throw new UnsupportedOperationException("TODO implement me");
+         }
+         else
+         {         
+            return null;
+         }
+      }
+
+      public GroupRpcResponse refresh()
+      {
+         if (this.coord.isMasterNode())      
+         {
+            this.coord.localHandler.refresh();
+            return SUCCESS;
+         }
+         else
+         {         
+            return null;
+         }
+      }
+
+      public GroupRpcResponse reset()
+      {
+         if (this.coord.isMasterNode())      
+         {
+            this.coord.localHandler.reset();
+            return SUCCESS;
+         }
+         else
+         {         
+            return null;
+         }
+      }
+
+      public GroupRpcResponse sendRequest(MCMPRequest request)
+      {
+         if (this.coord.isMasterNode())      
+         {
+            this.coord.localHandler.sendRequest(request);
+            return SUCCESS;
+         }
+         else
+         {         
+            return null;
+         }
+      }
+   }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Utils.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Utils.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/Utils.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,152 @@
+package org.jboss.web.tomcat.service.modcluster;
+
+import java.io.IOException;
+import java.net.InetAddress;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Host;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.util.StringManager;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+
+public class Utils
+{
+   private static final Logger log = Logger.getLogger(Utils.class);
+   private static final StringManager sm = StringManager.getManager(Constants.Package);
+   
+   /**
+    * Find the most likely connector the proxy server should connect to, or
+    * accept connections from.
+    * 
+    * @param connectors
+    * @return
+    */
+   public static Connector findProxyConnector(Connector[] connectors)
+   {
+       int pos = 0;
+       int maxThreads = 0;
+       for (int i = 0; i < connectors.length; i++) {
+           if (connectors[i].getProtocol().startsWith("AJP")) {
+               // Return any AJP connector found
+               return connectors[i];
+           }
+           if (Boolean.TRUE.equals(IntrospectionUtils.getProperty(connectors[i].getProtocolHandler(), "reverseConnection"))) {
+               return connectors[i];
+           }
+           Integer mt = (Integer) IntrospectionUtils.getProperty(connectors[i].getProtocolHandler(), "maxThreads");
+           if (mt.intValue() > maxThreads) {
+               maxThreads = mt.intValue();
+               pos = i;
+           }
+       }
+       // If no AJP connector and no reverse, return the connector with the most threads
+       return connectors[pos];
+   }
+
+   /**
+    * Return the address on which the connector is bound.
+    * 
+    * @param connector
+    * @return
+    */
+   public static String getAddress(Connector connector)
+   {
+       InetAddress inetAddress = 
+           (InetAddress) IntrospectionUtils.getProperty(connector.getProtocolHandler(), "address");
+       if (inetAddress == null) {
+           // Should not happen
+           return "127.0.0.1";
+       } else {
+           return inetAddress.getHostAddress();
+       }
+   }
+
+   /**
+    * Return the JvmRoute for the specified context.
+    * 
+    * @param context
+    * @return
+    */
+   public static String getJvmRoute(Context context)
+   {
+       return ((Engine) context.getParent().getParent()).getJvmRoute();
+   }
+
+   /**
+    * Return the host and its alias list with which the context is associated.
+    * 
+    * @param context
+    * @return
+    */
+   public static String getHost(Context context)
+   {
+       StringBuffer result = new StringBuffer();
+       Host host = (Host) context.getParent();
+       result.append(host.getName());
+       String[] aliases = host.findAliases();
+       for (int i = 0; i < aliases.length; i++) {
+           result.append(',');
+           result.append(aliases[i]);
+       }
+       return result.toString();
+   }
+
+   public static void establishJvmRouteAndConnectorAddress(Engine engine, MCMPHandler mcmpHandler) throws IOException
+   {
+      Connector connector = findProxyConnector(engine.getService().findConnectors());
+      InetAddress localAddress = 
+          (InetAddress) IntrospectionUtils.getProperty(connector.getProtocolHandler(), "address");
+      if ((engine.getJvmRoute() == null || localAddress == null) && mcmpHandler.getProxyStates().size() > 0) {
+          // Automagical JVM route (address + port + engineName)          
+           if (localAddress == null) {
+               localAddress = mcmpHandler.getLocalAddress();
+               if (localAddress != null) {
+                   IntrospectionUtils.setProperty(connector.getProtocolHandler(), "address", localAddress.getHostAddress());
+               } else {
+                   // Should not happen
+                   IntrospectionUtils.setProperty(connector.getProtocolHandler(), "address", "127.0.0.1");
+               }
+               log.info(sm.getString("clusterListener.address", localAddress.getHostAddress()));
+           }
+           if (engine.getJvmRoute() == null) {
+               String hostName = null;
+               if (localAddress != null) {
+                   hostName = localAddress.getHostName();
+               } else {
+                   // Fallback
+                   hostName = "127.0.0.1";
+               }
+               String jvmRoute = hostName + ":" + connector.getPort() + ":" + engine.getName();
+               engine.setJvmRoute(jvmRoute);
+               log.info(sm.getString("clusterListener.jvmRoute", engine.getName(), jvmRoute));
+           }
+      }
+   }
+   
+   /**
+    * Analyzes the type of the given Throwable, handing it back if it is a
+    * RuntimeException, wrapping it in a RuntimeException if it is a checked
+    * exception, or throwing it if it is an Error
+    * 
+    * @param t the throwable
+    * @return a RuntimeException based on t
+    * @throws Error if t is an Error
+    */
+   public static RuntimeException convertToUnchecked(Throwable t) throws Error
+   {
+      if (t instanceof Error)
+         throw (Error) t;
+      else if (t instanceof RuntimeException)
+         return (RuntimeException) t;
+      else
+         return new RuntimeException(t.getMessage(), t);
+   }
+   
+   private Utils()
+   {
+   }
+
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseEventType.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseEventType.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseEventType.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,62 @@
+/*
+ *
+ *  Copyright(c) 2008 Red Hat Middleware, LLC,
+ *  and individual contributors as indicated by the @authors tag.
+ *  See the copyright.txt in the distribution for a
+ *  full listing of individual contributors.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library in the file COPYING.LIB;
+ *  if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ */
+
+package org.jboss.web.tomcat.service.modcluster.advertise;
+
+/**
+ * Set what type of event the AdvertiseEvent signals.
+ * @param type The type of event.  One of:
+ * <PRE>
+ * ON_NEW_SERVER     --  New AdvertisedServer detected
+ * ON_STATUS_CHANGE  --  AdvertisedServer server changed status
+ * </PRE>
+ */
+public enum AdvertiseEventType
+{
+    /** New AdvertisedServer detected */
+    ON_NEW_SERVER(    0),
+    /** AdvertisedServer server changed status */
+    ON_STATUS_CHANGE( 1);
+
+    private int value;
+    private AdvertiseEventType(int v)
+    {
+        value = v;
+    }
+
+    public int valueOf()
+    {
+        return value;
+    }
+
+    public static AdvertiseEventType valueOf(int value)
+    {
+        for (AdvertiseEventType e : values()) {
+            if (e.value == value)
+                return e;
+        }
+        throw new IllegalArgumentException("Invalid initializer: " + value);
+    }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseListener.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseListener.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertiseListener.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,448 @@
+/*
+ *
+ *  Copyright(c) 2008 Red Hat Middleware, LLC,
+ *  and individual contributors as indicated by the @authors tag.
+ *  See the copyright.txt in the distribution for a
+ *  full listing of individual contributors.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library in the file COPYING.LIB;
+ *  if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ */
+
+package org.jboss.web.tomcat.service.modcluster.advertise;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+
+
+/** AdvertiseListener
+ * Listens for Advertise messages from mod_cluster
+ *
+ * @author Mladen Turk
+ *
+ */
+public class AdvertiseListener
+{
+    /** Default port for listening Advertise messages.
+     */
+    public static int    DEFAULT_PORT      = 23364;
+    /** Default Multicast group address for listening Advertise messages.
+     */
+    public static String DEFAULT_GROUP     = "224.0.1.105";
+
+    private static String RFC_822_FMT      = "EEE, d MMM yyyy HH:mm:ss Z";
+    private int              advertisePort = DEFAULT_PORT;
+    private String           groupAddress  = DEFAULT_GROUP;
+    private MulticastSocket  ms;
+    private SimpleDateFormat df;
+    private boolean          listening   = true;
+    private boolean          initialized = false;
+    private boolean          running     = false;
+    private boolean          paused      = false;
+    private boolean          daemon      = true;
+    private byte []          secure      = new byte[16];
+    private String           securityKey = null;
+    private MessageDigest    md          = null;
+
+    private     HashMap<String, AdvertisedServer> servers;
+
+    private     MCMPHandler commHandler;
+    private     Thread          workerThread;
+
+
+    private static void digestString(MessageDigest md, String s)
+    {
+        int len   = s.length();
+        byte [] b = new byte[len];
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+            if (c < 127)
+                b[i] = (byte)c;
+            else
+                b[i] = '?';
+        }
+        md.update(b);
+    }
+
+    /** Create AdvertiseListener instance
+     * @param eventHandler The event handler that will be used for
+     * status and new server notifications.
+     */
+    public AdvertiseListener(MCMPHandler commHandler)
+    {
+        df = new SimpleDateFormat(RFC_822_FMT, Locale.US);
+        servers = new HashMap<String, AdvertisedServer>();
+        this.commHandler = commHandler;
+    }
+
+    /**
+     * The default is true - the control thread will be
+     * in daemon mode. If set to false, the control thread
+     * will not be daemon - and will keep the process alive.
+     */
+    public void setDaemon(boolean b)
+    {
+        daemon = b;
+    }
+
+    public boolean getDaemon()
+    {
+        return daemon;
+    }
+
+    /** Set Advertise security key
+     * @param key The key to use.<br/>
+     *      Security key must match the AdvertiseKey
+     *      on the advertised server.
+     */
+    public void     setSecurityKey(String key)
+        throws NoSuchAlgorithmException
+    {
+        securityKey = key;
+        if (md == null)
+            md = MessageDigest.getInstance("MD5");
+    }
+
+    /** Set Advertise port
+     * @param port The UDP port to use.
+     */
+    public void     setAdvertisePort(int port)
+    {
+        advertisePort = port;
+    }
+
+    public int      getAdvertisePort()
+    {
+        return advertisePort;
+    }
+
+    /** Set Advertise Multicaset group address
+     * @param address The IP or host address to use.
+     */
+    public void     setGroupAddress(String address)
+    {
+        groupAddress = address;
+    }
+
+    /** Get Advertise Multicaset group address
+     */
+    public String   getGroupAddress()
+    {
+        return groupAddress;
+    }
+
+    /** Get Collection of all AdvertisedServer instances.
+     */
+    public Collection<AdvertisedServer> getServers()
+    {
+        return servers.values();
+    }
+
+    /** Get AdvertiseServer server.
+     * @param name Server name to get.
+     */
+    public AdvertisedServer getServer(String name)
+    {
+        return servers.get(name);
+    }
+
+    /** Remove the AdvertisedServer from the collection.
+     * @param server Server to remove.
+     */
+    public void removeServer(AdvertisedServer server)
+    {
+        servers.remove(server);
+    }
+
+    private void init()
+        throws IOException
+    {
+        ms = new MulticastSocket(advertisePort);
+        ms.setTimeToLive(16);
+        ms.joinGroup(InetAddress.getByName(groupAddress));
+        initialized = true;
+    }
+
+    private void interruptDatagramReader()
+    {
+        if (!initialized)
+            return;
+        try {
+            // Restrict to localhost.
+            ms.setTimeToLive(0);
+            DatagramPacket dp = new DatagramPacket(secure, secure.length,
+                                            InetAddress.getByName(groupAddress),
+                                            advertisePort);
+            ms.send(dp);
+        } catch (IOException e) {
+            // Ignore
+        }
+    }
+
+    /** Start the Listener, creating listener thread.
+     */
+    public void start()
+        throws IOException
+    {
+        if (!initialized) {
+            init();
+        }
+        if (!running) {
+            SecureRandom random = new SecureRandom();
+            random.nextBytes(secure);
+            secure[0] = 0;
+            running = true;
+            paused  = false;
+            listening = true;
+            AdvertiseListenerWorker aw = new AdvertiseListenerWorker();
+            workerThread = new Thread(aw);
+            workerThread.setDaemon(daemon);
+            workerThread.start();
+        }
+    }
+
+    /**
+     * Pause the listener, which will make it stop accepting new advertise
+     * messages.
+     */
+    public void pause()
+    {
+        if (running && !paused) {
+            paused = true;
+            interruptDatagramReader();
+        }
+    }
+
+
+    /**
+     * Resume the listener, which will make it start accepting new advertise
+     * messages again.
+     */
+    public void resume()
+    {
+        if (running && paused) {
+            // Genererate new private secure
+            SecureRandom random = new SecureRandom();
+            random.nextBytes(secure);
+            secure[0] = 0;
+            paused = false;
+        }
+    }
+
+
+    /**
+     * Stop the endpoint. This will cause all processing threads to stop.
+     */
+    public void stop()
+    {
+        if (running) {
+            running = false;
+            interruptDatagramReader();
+            workerThread = null;
+        }
+    }
+
+
+    /**
+     * Deallocate listener and close sockets.
+     */
+    public void destroy()
+    throws IOException
+    {
+        if (running) {
+            stop();
+        }
+        if (initialized) {
+            ms.leaveGroup(InetAddress.getByName(groupAddress));
+            ms.close();
+            initialized = false;
+            ms = null;
+        }
+    }
+
+    private boolean verifyDigest(String digest, String server, String date)
+    {
+        if (md == null)
+            return true;
+        md.reset();
+        digestString(md, securityKey);
+        digestString(md, date);
+        digestString(md, server);
+        byte [] our = md.digest();
+        byte [] dst = new byte[digest.length() * 2];
+        for (int i = 0, j = 0; i < digest.length(); i++) {
+            char ch = digest.charAt(i);
+            dst[j++] = (byte)((ch >= 'A') ? ((ch & 0xdf) - 'A') + 10 : (ch - '0'));
+        }
+        return true;
+    }
+
+    /**
+     * True if listener is accepting the advetise messages.<br/>
+     * If false it means that listener is experiencing some
+     * network problems if running.
+     */
+    public boolean isListening()
+    {
+        return listening;
+    }
+
+
+    // ------------------------------------ AdvertiseListenerWorker Inner Class
+    private class AdvertiseListenerWorker implements Runnable
+    {
+
+        protected AdvertiseListenerWorker()
+        {
+            // Nothing
+        }
+        /**
+         * The background thread that listens for incoming Advertise packets
+         * and hands them off to an appropriate AdvertiseEvent handler.
+         */
+        public void run() {
+            byte[] buffer = new byte[512];
+            // Loop until we receive a shutdown command
+            while (running) {
+                // Loop if endpoint is paused
+                while (paused) {
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+                try {
+                    DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
+                    ms.receive(dp);
+                    if (!running)
+                        break;
+                    byte [] data = dp.getData();
+                    boolean intr = false;
+                    if (dp.getLength() == secure.length) {
+                        int i;
+                        for (i = 0; i < secure.length; i++) {
+                            if (data[i] != secure[i])
+                                break;
+                        }
+                        if (i == secure.length)
+                            intr = true;
+                    }
+                    if (intr)
+                        continue;
+                    String s = new String(data, 0, dp.getLength(), "8859_1");
+                    if (!s.startsWith("HTTP/1."))
+                        continue;
+
+                    String [] headers =  s.split("\r\n");
+                    String    date_str = null;
+                    Date      date   = null;
+                    int       status = 0;
+                    String    status_desc = null;
+                    String    digest      = null;
+                    String    server_name = null;
+                    AdvertisedServer server = null;
+                    boolean added = false;
+                    for (int i = 0; i < headers.length; i++) {
+                        if (i == 0) {
+                            String [] sline = headers[i].split(" ", 3);
+                            if (sline == null || sline.length != 3)
+                                break;
+                             status = Integer.parseInt(sline[1]);
+                             if (status < 100)
+                                break;
+                             status_desc = sline[2];
+                        }
+                        else {
+                            String [] hdrv = headers[i].split(": ", 2);
+                            if (hdrv == null || hdrv.length != 2)
+                                break;
+                            if (hdrv[0].equals("Date")) {
+                                date_str = hdrv[1];
+                                try {
+                                    date = df.parse(date_str);
+                                } catch (ParseException e) {
+                                    date = new Date();
+                                }
+                            }
+                            else if (hdrv[0].equals("Digest")) {
+                                digest = hdrv[1];
+                            }
+                            else if (hdrv[0].equals("Server")) {
+                                server_name = hdrv[1];
+                                server = servers.get(server_name);
+                                if (server == null) {
+                                    server = new AdvertisedServer(server_name);
+                                    added = true;
+                                }
+                            }
+                            else if (server != null) {
+                                server.setParameter(hdrv[0], hdrv[1]);
+                            }
+                        }
+                    }
+                    if (server != null && status > 0) {
+                        if (md != null) {
+                            /* We need a digest to match */
+                            if (!verifyDigest(digest, server_name, date_str)) {
+                                System.out.println("Digest mismatch");
+                                continue;
+                            }
+                        }
+                        server.setDate(date);
+                        boolean rc = server.setStatus(status, status_desc);
+                        if (added) {
+                            servers.put(server_name, server);
+                            // Call the new server callback
+                            //eventHandler.onEvent(AdvertiseEventType.ON_NEW_SERVER, server);
+                            String proxy = server.getParameter(AdvertisedServer.MANAGER_ADDRESS);
+                            if (proxy != null) {
+                                commHandler.addProxy(proxy);
+                            }
+                        }
+                        else if (rc) {
+                            // Call the status change callback
+                            //eventHandler.onEvent(AdvertiseEventType.ON_STATUS_CHANGE, server);
+                        }
+                    }
+                    listening = true;
+                } catch (IOException e) {
+                    // Do not blow the CPU in case of communication error
+                    listening = false;
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException x) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+    }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertisedServer.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertisedServer.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/advertise/AdvertisedServer.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,122 @@
+/*
+ *
+ *  Copyright(c) 2008 Red Hat Middleware, LLC,
+ *  and individual contributors as indicated by the @authors tag.
+ *  See the copyright.txt in the distribution for a
+ *  full listing of individual contributors.
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library in the file COPYING.LIB;
+ *  if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ *
+ */
+
+package org.jboss.web.tomcat.service.modcluster.advertise;
+
+import java.util.Date;
+import java.util.HashMap;
+
+/**
+ * Advertised server instance
+ *
+ * @author Mladen Turk
+ *
+ */
+public class AdvertisedServer
+{
+    private String server;
+    private Date   date;
+    private int    status;
+    private String status_desc;
+    private HashMap<String, String> headers;
+
+    /** Manager-Address header          */
+    public static String MANAGER_ADDRESS   = "X-Manager-Address";
+    /** Manager-Url header              */
+    public static String MANAGER_URL       = "X-Manager-Url";
+    /** Manager-Protocol header          */
+    public static String MANAGER_PROTOCOL  = "X-Manager-Protocol";
+    /** Manager-Version header          */
+    public static String MANAGER_VERSION   = "X-Manager-Version";
+    /** Manager-Host header             */
+    public static String MANAGER_HOST      = "X-Manager-Host";
+
+    protected AdvertisedServer(String server)
+    {
+        this.server = server;
+        headers     = new HashMap<String, String>();
+    } 
+
+    protected boolean setStatus(int status, String desc)
+    {
+        boolean rv = false;
+        status_desc = desc;
+        if (this.status == 0 ) {
+            // First time
+            this.status = status;                
+        }
+        else if (this.status != status) {
+            this.status = status;
+            rv = true;
+        }
+        return rv;
+    }
+    
+    /** Set the Date of the last Advertise message
+     */
+    protected void setDate(Date date)
+    {
+        this.date = date;
+    }
+
+    /** Set the Header
+     */
+    protected void setParameter(String name, String value)
+    {
+        headers.put(name, value);
+    }
+
+    /** Get Date of the last Advertise message
+     */
+    public Date getDate()
+    {
+        return date;
+    }
+
+    /** Get Status code of the last Advertise message
+     */
+    public int getStatusCode()
+    {
+        return status;
+    }
+
+    /** Get Status description of the last Advertise message
+     */
+    public String getStatusDescription()
+    {
+        return status_desc;
+    }
+
+    /** Get Advertise parameter
+     */
+    public String getParameter(String name)
+    {
+        return headers.get(name);
+    }
+
+    public String toString()
+    {
+        return server;
+    } 
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/BalancerConfiguration.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/BalancerConfiguration.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/BalancerConfiguration.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.config;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public interface BalancerConfiguration
+{
+   boolean getStickySession();
+
+   boolean getStickySessionRemove();
+
+   boolean getStickySessionForce();
+
+   int getWorkerTimeout();
+
+   int getMaxAttempts();
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/MCMPHandlerConfiguration.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/MCMPHandlerConfiguration.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/MCMPHandlerConfiguration.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.config;
+
+/**
+ * Configuration object for an {@link MCMPHandler}.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public interface MCMPHandlerConfiguration extends SSLConfiguration
+{   
+   String getProxyList();
+   
+   String getProxyURL();
+   
+   int getSocketTimeout();
+   
+   boolean isSsl();
+   
+   Boolean getAdvertise();
+   
+   String getAdvertiseGroupAddress();
+   
+   int getAdvertisePort();
+   
+   String getAdvertiseSecurityKey();   
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/ModClusterConfig.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/ModClusterConfig.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/ModClusterConfig.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,349 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.config;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+/**
+ * Java bean implementing the various configuration interfaces.
+ * 
+ * @author Brian Stansberry
+ */
+public class ModClusterConfig
+      implements
+         BalancerConfiguration,
+         MCMPHandlerConfiguration,
+         NodeConfiguration,
+         SSLConfiguration
+{
+   
+   // ----------------------------------------------- MCMPHandlerConfiguration
+
+   /**
+    * Receive advertisements from httpd proxies (default is to use advertisements
+    * if the proxyList is not set).
+    */
+   private Boolean advertise;
+   public Boolean getAdvertise() { return advertise; }
+   public void setAdvertise(Boolean advertise) { this.advertise = advertise; }
+
+
+   /**
+    * Advertise group.
+    */
+   private String advertiseGroupAddress = null;
+   public String getAdvertiseGroupAddress() { return advertiseGroupAddress; }
+   public void setAdvertiseGroupAddress(String advertiseGroupAddress) { this.advertiseGroupAddress = advertiseGroupAddress; }
+
+
+   /**
+    * Advertise port.
+    */
+   private int advertisePort = -1;
+   public int getAdvertisePort() { return advertisePort; }
+   public void setAdvertisePort(int advertisePort) { this.advertisePort = advertisePort; }
+
+
+   /**
+    * Advertise security key.
+    */
+   private String advertiseSecurityKey = null;
+   public String getAdvertiseSecurityKey() { return advertiseSecurityKey; }
+   public void setAdvertiseSecurityKey(String advertiseSecurityKey) { this.advertiseSecurityKey = advertiseSecurityKey; }
+
+
+   /**
+    * Proxy list, format "address:port,address:port".
+    */
+   private String proxyList = null;
+   public String getProxyList() { return proxyList; }
+   public void setProxyList(String proxyList) { this.proxyList = proxyList; }
+
+
+   /**
+    * URL prefix.
+    */
+   private String proxyURL = null;
+   public String getProxyURL() { return proxyURL; }
+   public void setProxyURL(String proxyURL) { this.proxyURL = proxyURL; }
+
+
+   /**
+    * Connection timeout for communication with the proxy.
+    */
+   private int socketTimeout = 20000;
+   public int getSocketTimeout() { return socketTimeout; }
+   public void setSocketTimeout(int socketTimeout) { this.socketTimeout = socketTimeout; }
+   
+   
+   // -----------------------------------------------------  SSLConfiguration
+
+   /**
+    * SSL client cert usage to connect to the proxy.
+    */
+   private boolean ssl = false;
+   public boolean isSsl() { return ssl; }
+   public void setSsl(boolean ssl) { this.ssl = ssl; }
+   
+   
+   /**
+    * SSL ciphers.
+    */
+   private String sslCiphers = null;
+   public String getSslCiphers() { return sslCiphers; }
+   public void setSslCiphers(String sslCiphers) { this.sslCiphers = sslCiphers; }
+   
+   
+   /**
+    * SSL protocol.
+    */
+   private String sslProtocol = "TLS";
+   public String getSslProtocol() { return sslProtocol; }
+   public void setSslProtocol(String sslProtocol) { this.sslProtocol = sslProtocol; }
+   
+   
+   /**
+    * Certificate encoding algorithm.
+    */
+   private String sslCertificateEncodingAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
+   public String getSslCertificateEncodingAlgorithm() { return sslCertificateEncodingAlgorithm; }
+   public void setSslCertificateEncodingAlgorithm(String sslCertificateEncodingAlgorithm) { this.sslCertificateEncodingAlgorithm = sslCertificateEncodingAlgorithm; }
+   
+   
+   /**
+    * SSL keystore.
+    */
+   private String sslKeyStore = System.getProperty("user.home") + "/.keystore";
+   public String getSslKeyStore() { return sslKeyStore; }
+   public void setSslKeyStore(String sslKeyStore) { this.sslKeyStore = sslKeyStore; }
+   
+   
+   /**
+    * SSL keystore password.
+    */
+   private String sslKeyStorePass = "changeit";
+   public String getSslKeyStorePass() { return sslKeyStorePass; }
+   public void setSslKeyStorePass(String sslKeyStorePass) { this.sslKeyStorePass = sslKeyStorePass; }
+   
+   
+   /**
+    * Keystore type.
+    */
+   private String sslKeyStoreType = "JKS";
+   public String getSslKeyStoreType() { return sslKeyStoreType; }
+   public void setSslKeyStoreType(String sslKeyStoreType) { this.sslKeyStoreType = sslKeyStoreType; }
+   
+   
+   /**
+    * Keystore provider.
+    */
+   private String sslKeyStoreProvider = null;
+   public String getSslKeyStoreProvider() { return sslKeyStoreProvider; }
+   public void setSslKeyStoreProvider(String sslKeyStoreProvider) { this.sslKeyStoreProvider = sslKeyStoreProvider; }
+   
+   
+   /**
+    * Truststore algorithm.
+    */
+   private String sslTrustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
+   public String getSslTrustAlgorithm() { return sslTrustAlgorithm; }
+   public void setSslTrustAlgorithm(String sslTrustAlgorithm) { this.sslTrustAlgorithm = sslTrustAlgorithm; }
+   
+   
+   /**
+    * Key alias.
+    */
+   private String sslKeyAlias = null;
+   public String getSslKeyAlias() { return sslKeyAlias; }
+   public void setSslKeyAlias(String sslKeyAlias) { this.sslKeyAlias = sslKeyAlias; }
+   
+   
+   /**
+    * Certificate revocation list.
+    */
+   private String sslCrlFile = null;
+   public String getSslCrlFile() { return sslCrlFile; }
+   public void setSslCrlFile(String sslCrlFile) { this.sslCrlFile = sslCrlFile; }
+   
+   
+   /**
+    * Trust max certificate length.
+    */
+   private int sslTrustMaxCertLength = 5;
+   public int getSslTrustMaxCertLength() { return sslTrustMaxCertLength; }
+   public void setSslTrustMaxCertLength(int sslTrustMaxCertLength) { this.sslTrustMaxCertLength = sslTrustMaxCertLength; }
+   
+   
+   /**
+    * Trust store file.
+    */
+   private String sslTrustStore = System.getProperty("javax.net.ssl.trustStore");
+   public String getSslTrustStore() { return sslTrustStore; }
+   public void setSslTrustStore(String sslTrustStore) { this.sslTrustStore = sslTrustStore; }
+   
+   
+   /**
+    * Trust store password.
+    */
+   private String sslTrustStorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
+   public String getSslTrustStorePassword() { return sslTrustStorePassword; }
+   public void setSslTrustStorePassword(String sslTrustStorePassword) { this.sslTrustStorePassword = sslTrustStorePassword; }
+   
+   
+   /**
+    * Trust store type.
+    */
+   private String sslTrustStoreType = System.getProperty("javax.net.ssl.trustStoreType");
+   public String getSslTrustStoreType() { return sslTrustStoreType; }
+   public void setSslTrustStoreType(String sslTrustStoreType) { this.sslTrustStoreType = sslTrustStoreType; }
+   
+   
+   /**
+    * Trust store provider.
+    */
+   private String sslTrustStoreProvider = System.getProperty("javax.net.ssl.trustStoreProvider");
+   public String getSslTrustStoreProvider() { return sslTrustStoreProvider; }
+   public void setSslTrustStoreProvider(String sslTrustStoreProvider) { this.sslTrustStoreProvider = sslTrustStoreProvider; }
+   
+
+   // -----------------------------------------------------  NodeConfiguration
+
+
+   /**
+    * Domain parameter, which allows tying a jvmRoute to a particular domain.
+    */
+   private String domain = null;
+   public String getDomain() { return domain; }
+   public void setDomain(String domain) { this.domain = domain; }
+
+
+   /**
+    * Allows controlling flushing of packets.
+    */
+   private boolean flushPackets = false;
+   public boolean getFlushPackets() { return flushPackets; }
+   public void setFlushPackets(boolean flushPackets) { this.flushPackets = flushPackets; }
+
+
+   /**
+    * Time to wait before flushing packets.
+    */
+   private int flushWait = -1;
+   public int getFlushWait() { return flushWait; }
+   public void setFlushWait(int flushWait) { this.flushWait = flushWait; }
+
+
+   /**
+    * Time to wait for a pong answer to a ping.
+    */
+   private int ping = -1;
+   public int getPing() { return ping; }
+   public void setPing(int ping) { this.ping = ping; }
+
+
+   /**
+    * Soft maximum inactive connection count.
+    */
+   private int smax = -1;
+   public int getSmax() { return smax; }
+   public void setSmax(int smax) { this.smax = smax; }
+
+
+   /**
+    * Maximum time on seconds for idle connections above smax.
+    */
+   private int ttl = -1;
+   public int getTtl() { return ttl; }
+   public void setTtl(int ttl) { this.ttl = ttl; }
+
+
+   /**
+    * Maximum time on seconds for idle connections the proxy will wait to connect to the node.
+    */
+   private int nodeTimeout = -1;
+   public int getNodeTimeout() { return nodeTimeout; }
+   public void setNodeTimeout(int nodeTimeout) { this.nodeTimeout = nodeTimeout; }
+
+
+   /**
+    * Name of the balancer.
+    */
+   private String balancer = null;
+   public String getBalancer() { return balancer; }
+   public void setBalancer(String balancer) { this.balancer = balancer; }
+
+   /**
+    * Our load balance factor
+    */
+   private int loadBalanceFactor = 1;
+   public int getLoadBalanceFactor() { return loadBalanceFactor; }
+   public void setLoadBalanceFactor(int loadBalanceFactor) 
+   {
+      assert loadBalanceFactor >= 0 : "loadBalanceFactor is negative";
+      this.loadBalanceFactor = loadBalanceFactor;
+   }
+
+
+   // -------------------------------------------------  BalancerConfiguration
+   
+   /**
+    * Enables sticky sessions.
+    */
+   private boolean stickySession = true;
+   public boolean getStickySession() { return stickySession; }
+   public void setStickySession(boolean stickySession) { this.stickySession = stickySession; }
+
+
+   /**
+    * Remove session when the request cannot be routed to the right node.
+    */
+   private boolean stickySessionRemove = false;
+   public boolean getStickySessionRemove() { return stickySessionRemove; }
+   public void setStickySessionRemove(boolean stickySessionRemove) { this.stickySessionRemove = stickySessionRemove; }
+
+
+   /**
+    * Return an error when the request cannot be routed to the right node.
+    */
+   private boolean stickySessionForce = true;
+   public boolean getStickySessionForce() { return stickySessionForce; }
+   public void setStickySessionForce(boolean stickySessionForce) { this.stickySessionForce = stickySessionForce; }
+
+
+   /**
+    * Timeout to wait for an available worker (default is no wait).
+    */
+   private int workerTimeout = -1;
+   public int getWorkerTimeout() { return workerTimeout; }
+   public void setWorkerTimeout(int workerTimeout) { this.workerTimeout = workerTimeout; }
+
+
+   /**
+    * Maximum number of attempts to send the request to the backend server.
+    */
+   private int maxAttempts = -1;
+   public int getMaxAttempts() { return maxAttempts; }
+   public void setMaxAttempts(int maxAttempts) { this.maxAttempts = maxAttempts; }    
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/NodeConfiguration.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/NodeConfiguration.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/NodeConfiguration.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,47 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.config;
+
+public interface NodeConfiguration
+{
+   int getSocketTimeout();
+
+   String getDomain();
+
+   boolean getFlushPackets();
+
+   int getFlushWait();
+
+   int getPing();
+
+   int getSmax();
+
+   int getTtl();
+
+   int getNodeTimeout();
+
+   String getBalancer();
+
+   int getLoadBalanceFactor();
+
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/SSLConfiguration.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/SSLConfiguration.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/config/SSLConfiguration.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.config;
+
+/**
+ * A SSLConfiguration.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public interface SSLConfiguration
+{
+   String getSslCiphers();
+   String getSslProtocol();
+   String getSslCertificateEncodingAlgorithm();
+   String getSslKeyStore();
+   String getSslKeyStorePass();
+   String getSslKeyStoreType();
+   String getSslKeyStoreProvider();
+   String getSslTrustAlgorithm();
+   String getSslKeyAlias();
+   String getSslCrlFile();
+   int getSslTrustMaxCertLength();
+   String getSslTrustStore();
+   String getSslTrustStorePassword();
+   String getSslTrustStoreType();
+   String getSslTrustStoreProvider();
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandler.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandler.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandler.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.MCMPServerDiscoveryEvent;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServer;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+
+/**
+ * @author Brian Stansberry
+ */
+public interface ClusteredMCMPHandler extends MCMPHandler
+{
+   public static final String HA_SERVICE_NAME = "ModCluster";
+   
+   List<MCMPServerDiscoveryEvent> getPendingDiscoveryEvents();
+   void discoveryEventsReceived(MCMPServerDiscoveryEvent lastReceived);
+   
+   Set<MCMPServerState> updateServersFromMasterNode(Set<MCMPServer> masterList);
+   
+   boolean getNeedsResetTransmission();
+   void recordResetTransmission();
+   void recordResetSuccess();
+   
+   boolean isMasterNode();
+   void setMasterNode(boolean master);
+   
+   String getHAServiceName();
+   void setHAServiceName(String serviceName);
+   
+   String getPartitionName();
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandlerImpl.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandlerImpl.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ClusteredMCMPHandlerImpl.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,537 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import net.jcip.annotations.GuardedBy;
+
+import org.apache.catalina.util.StringManager;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.modcluster.Constants;
+import org.jboss.web.tomcat.service.modcluster.Utils;
+import org.jboss.web.tomcat.service.modcluster.advertise.AdvertiseListener;
+import org.jboss.web.tomcat.service.modcluster.config.MCMPHandlerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.BooleanGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.GroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.GroupRpcResponseFilter;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.MCMPServerDiscoveryEvent;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.StringGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.ThrowableGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.mcmp.AddressPort;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServer;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPUtils;
+
+public class ClusteredMCMPHandlerImpl implements ClusteredMCMPHandler
+{
+   private static final Object[] NULL_ARGS = new Object[0];
+   private static final Class[] NULL_TYPES = new Class[0];
+   private static final Class[] MCMPREQ_TYPES = new Class[] { MCMPRequest.class };
+   private static final Class[] DISC_EVENT_TYPES = new Class[] { MCMPServerDiscoveryEvent.class, int.class };
+   
+   private static final Logger log = Logger.getLogger(ClusteredMCMPHandlerImpl.class);
+   
+   private final MCMPHandler localHandler;
+   private final HAPartition partition;
+   private String haServiceName;
+   private AdvertiseListener advertiseListener;
+   
+   private volatile boolean masterNode = false;
+   
+   @GuardedBy("errorState")
+   private final List<Boolean> errorState = new ArrayList<Boolean>();
+   
+   @GuardedBy("this")
+   private List<MCMPServerDiscoveryEvent> pendingDiscoveryEvents = new ArrayList<MCMPServerDiscoveryEvent>();
+   
+   private AtomicInteger discoveryEventIndex = new AtomicInteger();
+
+   /**
+    * The string manager for this package.
+    */
+   private final StringManager sm =
+       StringManager.getManager(Constants.Package);
+   
+   public ClusteredMCMPHandlerImpl(MCMPHandler localHandler, HAPartition partition, String haServiceName)
+   {
+      this.localHandler = localHandler;
+      this.partition = partition;
+      this.haServiceName = haServiceName;
+   }
+   
+   // ---------------------------------------------------  ClusteredMCMPHandler
+   
+   public boolean isMasterNode()
+   {
+      return this.masterNode;
+   }
+
+   public void setMasterNode(boolean masterNode)
+   {
+      this.masterNode = masterNode;
+   }
+
+   public String getHAServiceName()
+   {
+      return this.haServiceName;
+   }
+
+   public void setHAServiceName(String haServiceName)
+   {
+      this.haServiceName = haServiceName;
+   }
+   
+   public String getPartitionName()
+   {
+      return this.partition.getPartitionName();
+   }
+
+   public synchronized List<MCMPServerDiscoveryEvent> getPendingDiscoveryEvents()
+   {
+      return new ArrayList<MCMPServerDiscoveryEvent>(pendingDiscoveryEvents);
+   }
+   
+   public synchronized void discoveryEventsReceived(MCMPServerDiscoveryEvent lastReceived)
+   {
+      if (lastReceived != null)
+      {
+         for (Iterator<MCMPServerDiscoveryEvent> it = pendingDiscoveryEvents.iterator(); it.hasNext();)
+         {
+            MCMPServerDiscoveryEvent event = it.next();
+            if (event.getEventIndex() <= lastReceived.getEventIndex())
+            {
+               it.remove();
+            }
+            else
+            {
+               return;
+            }
+         }
+      }
+   }
+
+   public synchronized Set<MCMPServerState> updateServersFromMasterNode(Set<MCMPServer> masterList)
+   {
+      for (MCMPServer server : masterList)
+      {         
+         this.localHandler.addProxy(server.getAddress(), server.getPort());         
+      }
+      
+      for (MCMPServer server : this.localHandler.getProxyStates())
+      {
+         if (!masterList.contains(server))
+         {
+            this.localHandler.removeProxy(server.getAddress(), server.getPort()); 
+         }            
+      }
+      
+      this.localHandler.status();
+      
+      return this.localHandler.getProxyStates();
+   }
+   
+   public boolean getNeedsResetTransmission()
+   {
+      synchronized (errorState)
+      {         
+         return errorState.size() > 0 && (errorState.get(errorState.size() -1).booleanValue() == false);
+      }
+   }
+   
+   public void recordResetTransmission()
+   {
+      synchronized (errorState)
+      {
+         if (errorState.size() > 0)
+         {
+            errorState.set(0, Boolean.TRUE);
+         }
+      }
+   }
+   
+   public void recordResetSuccess()
+   {
+      synchronized (errorState)
+      {
+         if (errorState.size() > 0 || errorState.get(errorState.size() -1).booleanValue())
+         {
+            errorState.remove(0);            
+         }
+      }
+   }   
+   
+   // ------------------------------------------------------------  MCMPHandler
+
+   public MCMPHandlerConfiguration getConfiguration()
+   {
+      return this.localHandler.getConfiguration();
+   }
+
+   public void addProxy(String address)
+   {      
+      AddressPort ap = MCMPUtils.parseAddressPort(address);
+      addProxy(ap.address, ap.port);
+   }
+
+   public void addProxy(String host, int port)
+   {
+      InetAddress address = null;
+      try {
+         address = InetAddress.getByName(host);
+      } catch (Exception e) {
+         throw new IllegalArgumentException(e);
+      }
+      
+      addProxy(address, port);  
+   }
+
+   public synchronized void addProxy(InetAddress address, int port)
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.addProxy(address, port);
+      }
+      else
+      {
+         sendDiscoveryEventToPartition(address, port, true);
+      }      
+   }
+
+   public void establishProxy(MCMPServer server)
+   {
+      throw new UnsupportedOperationException("Should not be invoked on ClusteredMCMPHandler");       
+   }
+   
+   
+   /**
+    * Remove proxy.
+    */
+   public synchronized void removeProxy(String host, int port) 
+   {
+      InetAddress address = null;
+      try {
+         address = InetAddress.getByName(host);
+      } catch (Exception e) {
+         throw new IllegalArgumentException(e);
+      }
+      removeProxy(address, port);
+   }   
+   
+   /**
+    * Remove proxy.
+    */
+   public synchronized void removeProxy(InetAddress address, int port) 
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.removeProxy(address, port);
+      }
+      else
+      {
+         sendDiscoveryEventToPartition(address, port, false);
+      }  
+   }
+
+   public Set<MCMPServerState> getProxyStates()
+   {
+      return this.localHandler.getProxyStates();
+   }
+
+   public InetAddress getLocalAddress() throws IOException
+   {
+      return localHandler.getLocalAddress();
+   }
+
+   public String getProxyConfiguration()
+   {
+      if (isMasterNode())
+      {
+         return this.localHandler.getProxyConfiguration();
+      }
+      else
+      {
+         GroupRpcResponse result = invokeNoArgGroupRpc("getProxyConfiguration");
+         if (result instanceof StringGroupRpcResponse)
+         {
+            return ((StringGroupRpcResponse) result).getValue();
+         }
+         else
+         {
+            throw ((ThrowableGroupRpcResponse) result).getValueAsRuntimeException();
+         }
+      }
+   }
+
+   public void init()
+   {
+      startListener();
+   }
+
+   public boolean isProxyHealthOK()
+   {
+      if (isMasterNode())
+      {
+         return this.localHandler.isProxyHealthOK();
+      }
+      else
+      {
+         GroupRpcResponse result = invokeNoArgGroupRpc("isProxyHealthOK");
+         if (result instanceof BooleanGroupRpcResponse)
+         {
+            return ((BooleanGroupRpcResponse) result).getValue();
+         }
+         else
+         {
+            throw ((ThrowableGroupRpcResponse) result).getValueAsRuntimeException();
+         }
+      }
+   }
+
+   public void markProxiesInError()
+   {
+      recordRequestFailure();
+   }
+
+   public void refresh()
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.refresh();
+      }
+      else
+      {
+         GroupRpcResponse result = invokeNoArgGroupRpc("refresh");
+         if (result instanceof ThrowableGroupRpcResponse)
+         {
+            throw ((ThrowableGroupRpcResponse) result).getValueAsRuntimeException();
+         }
+      }
+   }
+
+   public void reset()
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.reset();
+      }
+      else
+      {
+         GroupRpcResponse result = invokeNoArgGroupRpc("reset");
+         if (result instanceof ThrowableGroupRpcResponse)
+         {
+            throw ((ThrowableGroupRpcResponse) result).getValueAsRuntimeException();
+         }
+      }
+   }
+
+   @SuppressWarnings("unchecked")
+   public void sendRequest(MCMPRequest request)
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.sendRequest(request);
+      }
+      else
+      {
+         GroupRpcResponse response = null;
+         try
+         {
+            List<Object> rsps = this.partition.callMethodOnCluster(this.haServiceName, "sendRequest", new Object[] { request }, MCMPREQ_TYPES, false, new GroupRpcResponseFilter());
+            response = extractGroupRpcResponse(rsps, "sendRequest");
+         }
+         catch (Exception e)
+         {
+            recordRequestFailure();
+            throw Utils.convertToUnchecked(e);
+         }
+         
+         if (response instanceof ThrowableGroupRpcResponse)
+         {
+            recordRequestFailure();
+            throw ((ThrowableGroupRpcResponse) response).getValueAsRuntimeException();
+         }
+      }
+   }
+
+   public void shutdown()
+   {
+      stopListener();        
+   }
+
+   public void status()
+   {
+      if (isMasterNode())
+      {
+         this.localHandler.status();
+      }
+      else
+      {
+         throw new UnsupportedOperationException("TODO implement me");
+      }
+   }
+   
+   // ----------------------------------------------------------------  Private
+   
+   @SuppressWarnings("unchecked")
+   private GroupRpcResponse invokeNoArgGroupRpc(String methodName)
+   {
+      try
+      {
+         List<Object> rsps = this.partition.callMethodOnCluster(this.haServiceName, methodName, NULL_ARGS, NULL_TYPES, false, new GroupRpcResponseFilter());
+         return extractGroupRpcResponse(rsps, methodName);
+      }
+      catch (Exception e)
+      {
+         throw Utils.convertToUnchecked(e);
+      }
+   }
+   
+   private GroupRpcResponse extractGroupRpcResponse(List<Object> responses, String methodName)
+   {
+      Throwable thrown = null;
+      for (Object obj : responses)
+      {
+         if (obj instanceof GroupRpcResponse)
+         {
+            return (GroupRpcResponse) obj;
+         }
+         else if (obj instanceof Throwable)
+         {
+            if (thrown == null) 
+            {
+               thrown = (Throwable) obj;
+            }
+         }
+         else 
+         {
+            log.warn("Unexpected response " + obj + "("  + obj.getClass() + ") to RPC " + methodName);
+         }
+      }
+      
+      if (thrown != null)
+         throw Utils.convertToUnchecked(thrown);
+         
+      throw new IllegalStateException("No valid response to RPC " + methodName);
+   }    
+   
+   /**
+    * Start the advertise listener.
+    */
+   private void startListener() 
+   {      
+      MCMPHandlerConfiguration handlerConfig = getConfiguration();
+      Boolean advertise = handlerConfig.getAdvertise();
+      if ((handlerConfig.getProxyList() == null && advertise == null)
+            || Boolean.TRUE.equals(advertise))
+      {      
+          this.advertiseListener = new AdvertiseListener(this);
+          if (handlerConfig.getAdvertiseGroupAddress() != null) {
+             this.advertiseListener.setGroupAddress(handlerConfig.getAdvertiseGroupAddress());
+          }
+          if (handlerConfig.getAdvertisePort() > 0) {
+             this.advertiseListener.setAdvertisePort(handlerConfig.getAdvertisePort());
+          }
+          try {
+              if (handlerConfig.getAdvertiseSecurityKey() != null) {
+                 this.advertiseListener.setSecurityKey(handlerConfig.getAdvertiseSecurityKey());
+              }
+              this.advertiseListener.start();
+          } catch (IOException e) {
+              log.error(sm.getString("clusterListener.error.startListener"), e);
+          } catch (NoSuchAlgorithmException e) {
+              // Should never happen
+              log.error(sm.getString("clusterListener.error.startListener"), e);
+          }
+      }
+   }
+   
+
+   /**
+    * Stop the advertise listener.
+    */
+   private void stopListener() 
+   {
+       if (this.advertiseListener != null) {
+           try {
+              this.advertiseListener.destroy();
+           } catch (IOException e) {
+               log.error(sm.getString("clusterListener.error.stopListener"), e);
+           }
+           this.advertiseListener = null;
+       }
+   }
+   
+   private synchronized void sendDiscoveryEventToPartition(InetAddress address, int port, boolean addition)
+   {
+      AddressPort ap = new AddressPort(address, port);
+      MCMPServerDiscoveryEvent event = new MCMPServerDiscoveryEvent(this.partition.getClusterNode(), ap, addition, discoveryEventIndex.incrementAndGet());
+      pendingDiscoveryEvents.add(event);      
+      
+      GroupRpcResponse response = null;
+      Throwable throwable = null;;
+      try
+      {         
+         List<Object> rsps = this.partition.callMethodOnCluster(this.haServiceName, "mcmpServerDiscoveryEvent", new Object[] { event }, DISC_EVENT_TYPES, false, new GroupRpcResponseFilter());
+         response = extractGroupRpcResponse(rsps, "mcmpServerDiscoveryEvent");
+      }
+      catch (Exception e)
+      {
+         throwable = e;
+      }
+      
+      if (response instanceof ThrowableGroupRpcResponse)
+      {
+         throwable = ((ThrowableGroupRpcResponse) response).getValue();
+      }
+      
+      if (throwable != null)
+      {
+         // Just log it; we'll retry later
+         log.error("Failure notifying master of " + (addition ? "added" : "removed") + 
+                   " proxy " + address.toString() + ":" + port, throwable);
+         
+      }      
+   }
+   
+   private void recordRequestFailure()
+   {
+      synchronized (errorState)
+      {
+         if (errorState.size() == 0 || errorState.get(errorState.size() -1).booleanValue())
+         {
+            errorState.add(Boolean.FALSE);
+         }
+      }
+   }
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/HASingletonAwareResetRequestSource.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/HASingletonAwareResetRequestSource.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/HASingletonAwareResetRequestSource.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,143 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.catalina.Server;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.modcluster.Utils;
+import org.jboss.web.tomcat.service.modcluster.config.BalancerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.config.NodeConfiguration;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.ResetRequestGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.ha.rpc.ThrowableGroupRpcResponse;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPUtils;
+import org.jboss.web.tomcat.service.modcluster.mcmp.ResetRequestSource;
+
+/**
+ * {@link ResetRequestSource} that provides different reset requests 
+ * depending on whether or not it believes it is running on the singleton 
+ * master.
+ * 
+ * @author Brian Stansberry
+ */
+public class HASingletonAwareResetRequestSource implements ResetRequestSource
+{
+   private static final Logger log = Logger.getLogger(HASingletonAwareResetRequestSource.class);
+   
+   private final NodeConfiguration nodeConfig;
+   private final BalancerConfiguration balancerConfig;
+   private final HAPartition partition;
+   private final String haServiceName;
+   private volatile boolean master;
+   private volatile Server jbossWebServer;
+   
+   public HASingletonAwareResetRequestSource(NodeConfiguration nodeConfig, BalancerConfiguration balancerConfig, HAPartition partition, String haServiceName)
+   {
+      this.nodeConfig = nodeConfig;
+      this.balancerConfig = balancerConfig;
+      this.partition = partition;
+      this.haServiceName = haServiceName;
+   }
+   
+   public List<MCMPRequest> getResetRequests()   
+   {
+      if (this.master)
+      {
+         List<MCMPRequest> resets = getLocalResetRequests();
+         addRemoteRequests(resets);
+         return resets;
+      }
+      else
+      {
+         return null;
+      }
+   }
+
+   public List<MCMPRequest> getLocalResetRequests()
+   {
+      if (this.jbossWebServer == null)
+      {
+         return new ArrayList<MCMPRequest>();
+      }
+      
+      return MCMPUtils.getResetRequests(this.jbossWebServer, this.nodeConfig, this.balancerConfig);
+   }
+
+   public boolean isMasterNode()
+   {
+      return this.master;
+   }
+
+   public void setMasterNode(boolean master)
+   {
+      this.master = master;
+   }
+
+   public void setJbossWebServer(Server jbossWebServer)
+   {
+      this.jbossWebServer = jbossWebServer;
+   }
+   
+   private void addRemoteRequests(List<MCMPRequest> resets)
+   {
+      List responses = null;
+      try
+      {
+         responses = this.partition.callMethodOnCluster(this.haServiceName, "getResetRequests", new Object[]{}, new Class[]{}, true);
+      }
+      catch (Exception e)
+      {
+         //FIXME what to do?
+         throw Utils.convertToUnchecked(e);
+      }
+      
+      for (Object response : responses)
+      {
+         if (response instanceof ResetRequestGroupRpcResponse)
+         {
+            resets.addAll(((ResetRequestGroupRpcResponse) response).getValue());
+         }
+         else if (response instanceof ThrowableGroupRpcResponse)
+         {
+            ThrowableGroupRpcResponse tgrr = (ThrowableGroupRpcResponse) response;
+            //FIXME what to do?
+            log.warn("Call to getResetRequests received throwable from " + tgrr.getSender(), tgrr.getValue());
+         }
+         else if (response instanceof Throwable)
+         {
+            log.warn("Call to getResetRequests received throwable from unknown server", (Throwable) response);
+         }
+         else
+         {
+            log.error("Call to getResetRequests received unexpected response : " + response);
+         }
+         
+      }
+      
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceDRMEntry.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceDRMEntry.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceDRMEntry.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,145 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha;
+
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+
+/**
+ * Represents the status of a given MCMP client's ability to communicate
+ * with MCMP servers.
+ * 
+ * @author Brian Stansberry
+ */
+public class ModClusterServiceDRMEntry implements Serializable, Comparable<ModClusterServiceDRMEntry>
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8275232749243297786L;
+   
+   private final ClusterNode peer;
+   private final Set<MCMPServerState> mcmpServerStates;
+   private final Integer healthyEstablishedCount;
+   private final Integer establishedCount;
+   private final Integer healthyCount;
+   private final Integer knownCount;
+   private final Set<String> jvmRoutes = new HashSet<String>();
+
+   public ModClusterServiceDRMEntry(ClusterNode peer, Set<MCMPServerState> mcmpServerStates)
+   {
+      assert peer != null : "peer is null";
+      
+      this.peer = peer;
+      this.mcmpServerStates = mcmpServerStates;
+      
+      int healthyEstablished = 0;
+      int knownEstablished = 0;
+      int healthy = 0;
+      int known = 0;
+      
+      if (this.mcmpServerStates != null)
+      {
+         for (MCMPServerState state : this.mcmpServerStates)
+         {
+            known++;
+            if (state.getState() == MCMPServerState.State.OK)
+            {
+               healthy++;
+               if (state.isEstablished())
+               {
+                  knownEstablished++;
+                  healthyEstablished++;
+               }
+            }
+            else if (state.isEstablished())
+            {
+               knownEstablished++;
+            }
+         }
+      }
+      
+      this.establishedCount = new Integer(knownEstablished);
+      this.healthyCount = new Integer(healthy);
+      this.healthyEstablishedCount = new Integer(healthyEstablished);
+      this.knownCount = new Integer(known);
+   }
+
+   public ClusterNode getPeer()
+   {
+      return this.peer;
+   }
+
+   public Set<MCMPServerState> getMCMPServerStates()
+   {
+      return this.mcmpServerStates;
+   }
+   
+   public Set<String> getJvmRoutes()
+   {
+      synchronized (jvmRoutes)
+      {
+         return new HashSet<String>(jvmRoutes);
+      }
+   }
+   
+   public void addJvmRoute(String jvmRoute)
+   {
+      synchronized (jvmRoutes)
+      {
+         jvmRoutes.add(jvmRoute);
+      }      
+   }
+   
+   public void removeJvmRoute(String jvmRoute)
+   {
+      synchronized (jvmRoutes)
+      {
+         jvmRoutes.remove(jvmRoute);
+      }      
+   }
+
+   public int compareTo(ModClusterServiceDRMEntry other)
+   {
+      int result = other.healthyEstablishedCount.compareTo(this.healthyEstablishedCount);
+      if (result == 0)
+      {
+         result = other.establishedCount.compareTo(this.establishedCount);
+         if (result == 0)
+         {
+            result = other.healthyCount.compareTo(this.healthyCount);
+            if (result == 0)
+            {
+               result = other.knownCount.compareTo(this.knownCount);
+            }
+         }
+      }
+      
+      return result;
+   }
+   
+   
+
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceHASingletonElectionPolicy.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceHASingletonElectionPolicy.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/ModClusterServiceHASingletonElectionPolicy.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,97 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.singleton.HASingletonElectionPolicyBase;
+import org.jboss.ha.singleton.HASingletonElector;
+
+/**
+ * @author Brian Stansberry
+ *
+ */
+public class ModClusterServiceHASingletonElectionPolicy extends HASingletonElectionPolicyBase
+{
+   private final HASingletonElector delegate;
+   
+   
+   public ModClusterServiceHASingletonElectionPolicy(HASingletonElector delegate)
+   {
+      this.delegate = delegate;
+   }
+   
+   @Override
+   protected ClusterNode elect(List<ClusterNode> candidates)
+   {
+      if (candidates.size() == 1)
+         return candidates.get(0);
+      return delegate.elect(candidates);
+   }
+   
+   @Override
+   @SuppressWarnings("unchecked")
+   protected List<ClusterNode> getCandidates()
+   {
+      List<ModClusterServiceDRMEntry> candidates = getHAPartition().getDistributedReplicantManager().lookupReplicants(getSingletonName());
+      return narrowCandidateList(candidates);
+   }
+
+   public List<ClusterNode> narrowCandidateList(Collection<ModClusterServiceDRMEntry> candidates)
+   {
+      List<ClusterNode> narrowed = new ArrayList<ClusterNode>();
+      ModClusterServiceDRMEntry champion = null;
+      
+      for (ModClusterServiceDRMEntry candidate : candidates)
+      {
+         if (champion == null)
+         {
+            champion = candidate;
+            narrowed.add(candidate.getPeer());
+         }
+         else
+         {
+            int compFactor = candidate.compareTo(champion);
+            if (compFactor < 0)
+            {
+               // New champ
+               narrowed.clear();
+               champion = candidate;
+               narrowed.add(candidate.getPeer());
+            }
+            else if (compFactor == 0)
+            {
+               // As good as our champ
+               narrowed.add(candidate.getPeer());
+            }            
+            // else candidate didn't make the cut; continue
+         }
+      }
+      
+      return narrowed;
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/BooleanGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/BooleanGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/BooleanGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,50 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+
+/**
+ * A {@link GroupRpcResponse} that wraps a boolean return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class BooleanGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final boolean value;
+   
+   public BooleanGroupRpcResponse(ClusterNode sender, boolean value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public boolean getValue()
+   {
+      return this.value;
+   }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.io.Serializable;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.ModClusterService;
+
+/**
+ * A response to a group RPC call made by {@link ModClusterService}.
+ * 
+ * @author Brian Stansberry
+ */
+public class GroupRpcResponse implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 7427355308214918459L;
+
+   private final ClusterNode sender;
+   
+   public GroupRpcResponse(ClusterNode sender)
+   {
+      this.sender = sender;
+   }
+
+   public ClusterNode getSender()
+   {
+      return this.sender;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponseFilter.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponseFilter.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/GroupRpcResponseFilter.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.ResponseFilter;
+
+/**
+ * A {@link ResponseFilter} that accepts any GroupRpcResponse and doesn't
+ * need any further responses after receiving the first.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class GroupRpcResponseFilter implements ResponseFilter
+{
+   private boolean stillNeed = true;
+   
+   public boolean isAcceptable(Object response, ClusterNode responder)
+   {
+      boolean acceptable = (response instanceof GroupRpcResponse);
+      if (acceptable) 
+         this.stillNeed = false;
+      
+      return acceptable;
+   }
+
+   public boolean needMoreResponses()
+   {      
+      return this.stillNeed;
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/InetAddressGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/InetAddressGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/InetAddressGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.net.InetAddress;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+
+/**
+ * A {@link GroupRpcResponse} that wraps an InetAddress return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class InetAddressGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final InetAddress value;
+   
+   public InetAddressGroupRpcResponse(ClusterNode sender, InetAddress value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public InetAddress getValue()
+   {
+      return this.value;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/IntegerGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/IntegerGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/IntegerGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+
+/**
+ * A {@link GroupRpcResponse} that wraps an int return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class IntegerGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final int value;
+   
+   public IntegerGroupRpcResponse(ClusterNode sender, int value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public int getValue()
+   {
+      return this.value;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/MCMPServerDiscoveryEvent.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/MCMPServerDiscoveryEvent.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/MCMPServerDiscoveryEvent.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,94 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.io.Serializable;
+
+import net.jcip.annotations.Immutable;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.mcmp.AddressPort;
+
+/**
+ * Event object indicating the discovery or requested removal of an
+ * {@link MCMPServer}.
+ * 
+ * @author Brian Stansberry
+ */
+ at Immutable
+public class MCMPServerDiscoveryEvent implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -4615651826967237065L;
+   
+   private final ClusterNode sender;
+   private final AddressPort mcmpServer;
+   private final boolean addition;
+   private final int eventIndex;
+   
+   public MCMPServerDiscoveryEvent(ClusterNode sender, AddressPort mcmpServer, boolean addition, int eventIndex)
+   {
+      assert sender != null : "sender is null";
+      assert mcmpServer != null : "mcmpServer is null";
+      
+      this.sender = sender;
+      this.mcmpServer = mcmpServer;
+      this.addition = addition;
+      this.eventIndex = eventIndex;
+   }
+   
+   /**
+    * Creates a new MCMPServerDiscoveryEvent with the same values as an existing
+    * one, but a new event index.  Used in resending events.
+    * 
+    * @param toRecreate
+    * @param newEventIndex
+    */
+   public MCMPServerDiscoveryEvent(MCMPServerDiscoveryEvent toRecreate, int newEventIndex)
+   {
+      this(toRecreate.getSender(), toRecreate.getMCMPServer(), toRecreate.isAddition(), newEventIndex);
+   }  
+   
+
+   public ClusterNode getSender()
+   {
+      return this.sender;
+   }
+
+   public AddressPort getMCMPServer()
+   {
+      return this.mcmpServer;
+   }
+
+   public boolean isAddition()
+   {
+      return this.addition;
+   }
+
+   public int getEventIndex()
+   {
+      return this.eventIndex;
+   }
+   
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ModClusterServiceStateGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ModClusterServiceStateGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ModClusterServiceStateGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.util.List;
+import java.util.Set;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+
+/**
+ * GroupRpcResponse that provides the overall status picture for a
+ * ModClusterService instance.
+ * 
+ * @author Brian Stansberry
+ */
+public class ModClusterServiceStateGroupRpcResponse extends GroupRpcResponse
+{   
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -6591593007825931165L;
+   
+   private final Set<MCMPServerState> states;
+   private final List<MCMPServerDiscoveryEvent> unacknowledgedEvents;
+   private final List<MCMPRequest> resetRequests;
+   private final int loadBalanceFactor;
+   
+   public ModClusterServiceStateGroupRpcResponse(ClusterNode sender,
+         int loadBalanceFactor,
+         Set<MCMPServerState> states,
+         List<MCMPServerDiscoveryEvent> unacknowledgedEvents,
+         List<MCMPRequest> resetRequests)
+   {
+      super(sender);
+      this.loadBalanceFactor = loadBalanceFactor;
+      this.states = states;
+      this.unacknowledgedEvents = unacknowledgedEvents;
+      this.resetRequests = resetRequests;
+   }
+
+   public Set<MCMPServerState> getStates()
+   {
+      return this.states;
+   }
+
+   public List<MCMPServerDiscoveryEvent> getUnacknowledgedEvents()
+   {
+      return this.unacknowledgedEvents;
+   }
+
+   public List<MCMPRequest> getResetRequests()
+   {
+      return this.resetRequests;
+   }
+
+   public int getLoadBalanceFactor()
+   {
+      return this.loadBalanceFactor;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/PeerMCMPDiscoveryStatus.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/PeerMCMPDiscoveryStatus.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/PeerMCMPDiscoveryStatus.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,66 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.util.Set;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.ha.ModClusterServiceDRMEntry;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+
+/**
+ * Extends {@link ModClusterServiceDRMEntry} to include information on
+ * the most recent discovery event from the peer that the current
+ * singleton master has included in the group-wide set of MCMP configurations.
+ * 
+ * @author Brian Stansberry
+ */
+public class PeerMCMPDiscoveryStatus extends ModClusterServiceDRMEntry
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 3497115763128334162L;
+   
+   private final MCMPServerDiscoveryEvent latestDiscoveryEvent;
+   
+   /**
+    * Create a new PeerMCMPCommStatus.
+    * 
+    * @param peer the id of the peer
+    * @param mcmpServerStates unmodifiable Set of MCMPServerState objects, or
+    *                         <code>null</code> if such a set of states could
+    *                         not be obtained for the peer.
+    * @param latestDiscoveryEvent most recent discovery event received from the peer
+    */
+   public PeerMCMPDiscoveryStatus(ClusterNode peer, Set<MCMPServerState> mcmpServerStates, 
+                             MCMPServerDiscoveryEvent latestDiscoveryEvent)
+   {
+      super(peer, mcmpServerStates);
+      this.latestDiscoveryEvent = latestDiscoveryEvent;
+   }
+
+   public MCMPServerDiscoveryEvent getLatestDiscoveryEvent()
+   {
+      return this.latestDiscoveryEvent;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ResetRequestGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ResetRequestGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ResetRequestGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,54 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import java.util.List;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+
+/**
+ * A {@link GroupRpcResponse} that wraps a List<MCMPRequest> return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class ResetRequestGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final List<MCMPRequest> value;
+   
+   public ResetRequestGroupRpcResponse(ClusterNode sender, List<MCMPRequest> value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public List<MCMPRequest> getValue()
+   {
+      return this.value;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/StringGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/StringGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/StringGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+
+/**
+ * A {@link GroupRpcResponse} that wraps a String return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class StringGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final String value;
+   
+   public StringGroupRpcResponse(ClusterNode sender, String value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public String getValue()
+   {
+      return this.value;
+   }
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ThrowableGroupRpcResponse.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ThrowableGroupRpcResponse.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/ha/rpc/ThrowableGroupRpcResponse.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.ha.rpc;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+
+/**
+ * A {@link GroupRpcResponse} that wraps a String return value.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public class ThrowableGroupRpcResponse extends GroupRpcResponse
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = 8932443264578153750L;
+   
+   private final Throwable value;
+   
+   public ThrowableGroupRpcResponse(ClusterNode sender, Throwable value)
+   {
+      super(sender);
+      this.value = value;
+   }
+
+   public Throwable getValue()
+   {
+      return this.value;
+   }
+   
+   public RuntimeException getValueAsRuntimeException()
+   {
+      if (value instanceof RuntimeException)
+         return (RuntimeException) value;
+      else
+      {
+         return new RuntimeException("Group RPC returned exception", value);
+      }
+   }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/LoadBalanceFactorProvider.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/LoadBalanceFactorProvider.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/LoadBalanceFactorProvider.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,34 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.load;
+
+/**
+ * Provides the load balance factor for a node.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public interface LoadBalanceFactorProvider
+{
+   int getLoadBalanceFactor();
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/impl/StaticLoadBalanceFactorProvider.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/impl/StaticLoadBalanceFactorProvider.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/load/impl/StaticLoadBalanceFactorProvider.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.load.impl;
+
+import org.jboss.web.tomcat.service.modcluster.load.LoadBalanceFactorProvider;
+
+/**
+ * A {@link LoadManagerImpl} that returns a static value.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public class StaticLoadBalanceFactorProvider implements LoadBalanceFactorProvider
+{
+   private int loadBalanceFactor = 1;
+
+   // ------------------------------------------------------------- LoadManager
+
+   public int getLoadBalanceFactor()
+   {
+      return this.loadBalanceFactor;
+   } 
+   
+   // -------------------------------------------------------------- Properties
+
+   public void setLoadBalanceFactor(int loadBalanceFactor)
+   {
+      this.loadBalanceFactor = loadBalanceFactor;
+   } 
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mbeans-descriptors.xml
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mbeans-descriptors.xml	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mbeans-descriptors.xml	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,138 @@
+<?xml version="1.0"?>
+<mbeans-descriptors>
+
+  <mbean name="ClusterListener"
+         description="Cluster listener for mod_cluster"
+         domain="Catalina"
+         group="Listener"
+         type="org.jboss.web.cluster.ClusterListener">
+
+    <attribute name="className"
+               description="Fully qualified class name of the managed object"
+               type="java.lang.String"
+               writeable="false"/>
+
+    <attribute name="containerName"
+               description="Object name of the container"
+               type="javax.management.ObjectName"/>
+
+    <attribute name="proxyConfiguration"
+               description="Get the mod_cluster configuration"
+               type="java.lang.String"
+               writeable="false"/>
+    
+    <attribute name="proxyList"
+               description="Comma delimited list of proxy servers"
+               type="java.lang.String"/>
+
+    <attribute name="socketTimeout"
+               description="Connection timeout for communication with the proxy"
+               type="int"/>
+
+    <attribute name="advertise"
+               description="Enable autodiscovery of httpd servers"
+               type="boolean"/>
+
+    <attribute name="advertiseGroupAddress"
+               description="Multicast address for discovery"
+               type="java.lang.String"/>
+
+    <attribute name="advertisePort"
+               description="Multicast port for discovery"
+               type="int"/>
+
+    <attribute name="advertiseSecurityKey"
+               description="Security key for discovery"
+               type="java.lang.String"/>
+
+    <attribute name="domain"
+               description="Domain parameter, which allows tying a jvmRoute to a particular domain"
+               type="java.lang.String"/>
+
+    <attribute name="flushPackets"
+               description="Allows controlling flushing of packets"
+               type="boolean"/>
+
+    <attribute name="flushWait"
+               description="Time in ms to wait before flushing packets"
+               type="int"/>
+
+    <attribute name="ping"
+               description="Time in s to wait for a pong answer to a ping"
+               type="int"/>
+
+    <attribute name="smax"
+               description="Maximum time on seconds for idle connections above smax"
+               type="int"/>
+
+    <attribute name="balancer"
+               description="Name of the balancer"
+               type="java.lang.String"/>
+
+    <attribute name="stickySession"
+               description="Enables sticky sessions"
+               type="boolean"/>
+
+    <attribute name="stickySessionRemove"
+               description="Remove session when the request cannot be routed to the right node"
+               type="boolean"/>
+
+    <attribute name="stickySessionForce"
+               description="Return an error when the request cannot be routed to the right node"
+               type="boolean"/>
+
+    <attribute name="workerTimeout"
+               description="Timeout to wait for an available worker (default is no wait)"
+               type="int"/>
+
+    <attribute name="maxAttempts"
+               description="Maximum number of attempts to send the request to the backend server"
+               type="int"/>
+
+    <operation name="refresh"
+               description="Refresh configuration"
+               impact="ACTION"
+               returnType="void"/>
+ 
+    <operation name="reset"
+               description="Move the node out of an error state"
+               impact="ACTION"
+               returnType="void"/>
+ 
+    <operation name="disable"
+               description="Disable all webapps for all engines"
+               impact="ACTION"
+               returnType="boolean"/>
+ 
+    <operation name="enable"
+               description="Enable all webapps for all engines"
+               impact="ACTION"
+               returnType="boolean"/>
+ 
+    <operation name="addProxy"
+               description="Add a proxy"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="host"
+                 description="Proxy address"
+                 type="java.lang.String"/>
+      <parameter name="port"
+                 description="Proxy port"
+                 type="int"/>
+    </operation>
+
+    <operation name="removeProxy"
+               description="Remove a proxy"
+               impact="ACTION"
+               returnType="void">
+      <parameter name="host"
+                 description="Proxy address"
+                 type="java.lang.String"/>
+      <parameter name="port"
+                 description="Proxy port"
+                 type="int"/>
+    </operation>
+
+  </mbean>
+
+</mbeans-descriptors>


Property changes on: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mbeans-descriptors.xml
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/AddressPort.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/AddressPort.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/AddressPort.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.net.InetAddress;
+
+/**
+ * Simple data object encapsulating an InetAddress and a port.
+ * 
+ * @author Brian Stansberry
+ */
+public class AddressPort
+{
+   public final InetAddress address;
+   public final int port;
+   
+   public AddressPort(InetAddress address, int port)
+   {
+      this.address = address;
+      this.port = port;
+   }
+   
+   public boolean equals(InetAddress address, int port)
+   {
+      if (this.port != port) {
+         return false;
+      }
+      if (address == null) {
+         return (this.address == null);
+      } 
+      else 
+      {
+         return (address.equals(this.address));
+      }      
+   }
+
+   @Override
+   public boolean equals(Object o) {
+      if (o instanceof AddressPort) {
+         AddressPort compare = (AddressPort) o;
+         return equals(compare.address, compare.port);
+      }
+      return false;
+   } 
+
+   @Override
+   public int hashCode()
+   {
+      int result = 17;
+      result += 23 * (this.address == null ? 0 : this.address.hashCode());
+      result += 23 * this.port;
+      return result;
+   }
+
+   @Override
+   public String toString()
+   {
+      return "AddressPort{" + address + ":" + port + "}";
+   }
+   
+}
\ No newline at end of file

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPHandler.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPHandler.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPHandler.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Set;
+
+import org.jboss.web.tomcat.service.modcluster.config.MCMPHandlerConfiguration;
+
+/**
+ * Handles communication via MCMP with the httpd side.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public interface MCMPHandler
+{  
+   MCMPHandlerConfiguration getConfiguration();
+   
+   void init();
+   void shutdown();
+   
+   void sendRequest(MCMPRequest request);
+   
+   void addProxy(String address);
+   void addProxy(String host, int port);
+   void addProxy(InetAddress address, int port);
+   void removeProxy(String host, int port);
+   void removeProxy(InetAddress address, int port);
+   void establishProxy(MCMPServer server);
+   Set<MCMPServerState> getProxyStates();
+   void reset();
+   void refresh();
+   void markProxiesInError();   
+   boolean isProxyHealthOK();
+   
+   InetAddress getLocalAddress() throws IOException;
+   String getProxyConfiguration();
+   
+   void status();
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequest.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequest.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequest.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,83 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates the parameters for a request over MCMP.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public class MCMPRequest
+{
+   private final MCMPRequestType requestType;
+   private final boolean wildcard;
+   private final Map<String, String> parameters;
+   
+   /**
+    * Create a new ModClusterRequest. 
+    */
+   public MCMPRequest(MCMPRequestType requestType, boolean wildcard, Map<String, String> parameters)
+   {
+      this.requestType = requestType;
+      this.wildcard = wildcard;
+      this.parameters = Collections.unmodifiableMap(new HashMap<String, String>(parameters));
+   }
+
+   public MCMPRequestType getRequestType()
+   {
+      return requestType;
+   }
+
+   public boolean isWildcard()
+   {
+      return wildcard;
+   }
+
+   public Map<String, String> getParameters()
+   {
+      return parameters;
+   }
+   
+   public String toString()
+   {
+      StringBuilder sb = new StringBuilder(getClass().getName());
+      sb.append("{requestType=");
+      sb.append(requestType);
+      sb.append(",wildcard=");
+      sb.append(wildcard);
+      sb.append(",parameters=");
+      synchronized (parameters)
+      {
+         sb.append(parameters);
+      }
+      sb.append("}");
+      
+      return sb.toString();
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequestType.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequestType.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPRequestType.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+/**
+ * Valid types of MCMP requests.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public enum MCMPRequestType 
+{
+   CONFIG("CONFIG", true),
+   ENABLE_APP("ENABLE-APP", false),
+   DISABLE_APP("DISABLE-APP", false),
+   STOP_APP("STOP-APP", false),
+   REMOVE_APP("REMOVE-APP", false),
+   STATUS("STATUS", false),
+   INFO("INFO", false),
+   DUMP("DUMP", false);
+   
+   private final String command;
+   private final boolean establishesServer;
+
+   private MCMPRequestType(String command, boolean establishesServer)
+   {
+      this.command = command;
+      this.establishesServer = establishesServer;
+   }
+   
+   public String getCommand()
+   {
+      return this.command;
+   }
+   
+   public boolean getEstablishesServer()
+   {
+      return this.establishesServer;
+   }
+   
+   public String toString()
+   {
+      return command;
+   }
+   
+   
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServer.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServer.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServer.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.net.InetAddress;
+
+/**
+ * Represents a native server that is running the <code>mod_cluster</code>
+ * module and proxying requests to JBoss Web. For example, an Apache httpd 
+ * instance. Such an instance represents the server in the Mod Cluster 
+ * Management Protocol, with an MCMPHandler acting as the client.
+ * 
+ * @author Brian Stansberry
+ */
+public interface MCMPServer
+{   
+   InetAddress getAddress();
+   
+   int getPort();
+   
+   boolean isEstablished();
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServerState.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServerState.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPServerState.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+
+/**
+ * Extends {@link MCMPServer} to provide information about the current
+ * state of communications with that server.
+ * 
+ * @author Brian Stansberry
+ */
+public interface MCMPServerState extends MCMPServer
+{
+   /** Possible communication states vis a vis the server */
+   public enum State { OK, ERROR, DOWN };
+   
+   State getState();
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPUtils.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPUtils.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/MCMPUtils.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,254 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Server;
+import org.apache.catalina.Service;
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.util.IntrospectionUtils;
+import org.jboss.web.tomcat.service.modcluster.Utils;
+import org.jboss.web.tomcat.service.modcluster.config.BalancerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.config.NodeConfiguration;
+
+/**
+ * Utility methods related to the Mod-Cluster Management Protocol.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public class MCMPUtils
+{
+   public static MCMPRequest createConfigRequest(Engine engine, NodeConfiguration nodeConfig, BalancerConfiguration balancerConfig)
+   {
+        Connector connector = Utils.findProxyConnector(engine.getService().findConnectors());
+        Map<String, String> parameters = new HashMap<String, String>();
+        parameters.put("JVMRoute", engine.getJvmRoute());
+        boolean reverseConnection = 
+            Boolean.TRUE.equals(IntrospectionUtils.getProperty(connector.getProtocolHandler(), "reverseConnection"));
+        boolean ssl = 
+            Boolean.TRUE.equals(IntrospectionUtils.getProperty(connector.getProtocolHandler(), "SSLEnabled"));
+        boolean ajp = ((String) IntrospectionUtils.getProperty(connector.getProtocolHandler(), "name")).startsWith("ajp-");
+
+        if (reverseConnection) {
+            parameters.put("Reversed", "true");
+        }
+        parameters.put("Host", Utils.getAddress(connector));
+        parameters.put("Port", "" + connector.getPort());
+        if (ajp) {
+            parameters.put("Type", "ajp");
+        } else if (ssl) {
+            parameters.put("Type", "https");
+        } else {
+            parameters.put("Type", "http");
+        }
+        
+        // Other configuration parameters
+        if (nodeConfig.getDomain() != null) {
+            parameters.put("Domain", nodeConfig.getDomain());
+        }
+        if (nodeConfig.getFlushPackets()) {
+            parameters.put("flushpackets", "On");
+        }
+        if (nodeConfig.getFlushWait() != -1) {
+            parameters.put("flushwait", "" + nodeConfig.getFlushWait());
+        }
+        if (nodeConfig.getPing() != -1) {
+            parameters.put("ping", "" + nodeConfig.getPing());
+        }
+        if (nodeConfig.getSmax() != -1) {
+            parameters.put("smax", "" + nodeConfig.getSmax());
+        }
+        if (nodeConfig.getTtl() != -1) {
+            parameters.put("ttl", "" + nodeConfig.getTtl());
+        }
+        if (nodeConfig.getNodeTimeout() != -1) {
+            parameters.put("Timeout", "" + nodeConfig.getNodeTimeout());
+        }
+        if (nodeConfig.getBalancer() != null) {
+            parameters.put("Balancer", nodeConfig.getBalancer());
+        }
+        if (!balancerConfig.getStickySession()) {
+            parameters.put("StickySession", "No");
+        }
+        if (!org.apache.catalina.Globals.SESSION_COOKIE_NAME.equals("JSESSIONID")) {
+            parameters.put("StickySessionCookie", org.apache.catalina.Globals.SESSION_COOKIE_NAME);
+        }
+        if (!org.apache.catalina.Globals.SESSION_PARAMETER_NAME.equals("jsessionid")) {
+            parameters.put("StickySessionPath", org.apache.catalina.Globals.SESSION_PARAMETER_NAME);
+        }
+        if (balancerConfig.getStickySessionRemove()) {
+            parameters.put("StickSessionRemove", "Yes");
+        }
+        if (!balancerConfig.getStickySessionForce()) {
+            parameters.put("StickySessionForce", "No");
+        }
+        if (balancerConfig.getWorkerTimeout() != -1) {
+            parameters.put("WaitWorker", "" + balancerConfig.getWorkerTimeout());
+        }
+        if (balancerConfig.getMaxAttempts() != -1) {
+            parameters.put("Maxattempts", "" + balancerConfig.getMaxAttempts());
+        }
+        
+        return new MCMPRequest(MCMPRequestType.CONFIG, false, parameters);
+   }
+   
+   public static MCMPRequest createEnableAppRequest(Context context)
+   {
+      return new MCMPRequest(MCMPRequestType.ENABLE_APP, false, createContextParameters(context));
+   }
+   
+   public static MCMPRequest createDisableAppRequest(Context context)
+   {
+      return new MCMPRequest(MCMPRequestType.DISABLE_APP, false, createContextParameters(context));
+   }
+   
+   public static MCMPRequest createStopAppRequest(Context context)
+   {
+      return new MCMPRequest(MCMPRequestType.STOP_APP, false, createContextParameters(context));
+   }
+   
+   public static MCMPRequest createRemoveAppRequest(Context context)
+   {
+      return new MCMPRequest(MCMPRequestType.REMOVE_APP, false, createContextParameters(context));
+   }
+   
+   public static MCMPRequest createStatusRequest(Engine engine, int lbf)
+   {
+      Map<String, String> parameters = new HashMap<String, String>();
+      parameters.put("JVMRoute", engine.getJvmRoute());
+      parameters.put("Load", String.valueOf(lbf));
+      return new MCMPRequest(MCMPRequestType.STATUS, false, parameters);
+   }
+   
+   public static MCMPRequest createStatusRequest(String jvmRoute, int lbf)
+   {
+      Map<String, String> parameters = new HashMap<String, String>();
+      parameters.put("JVMRoute", jvmRoute);
+      parameters.put("Load", String.valueOf(lbf));
+      return new MCMPRequest(MCMPRequestType.STATUS, false, parameters);
+   }
+   
+   public static MCMPRequest createEnableEngineRequest(Engine engine)
+   {
+      return new MCMPRequest(MCMPRequestType.ENABLE_APP, true, createEngineParameters(engine));
+   }
+   
+   public static MCMPRequest createDisableEngineRequest(Engine engine)
+   {
+      return new MCMPRequest(MCMPRequestType.DISABLE_APP, true, createEngineParameters(engine));
+   }
+   
+   public static MCMPRequest createRemoveAllRequest(Engine engine)
+   {
+      return new MCMPRequest(MCMPRequestType.REMOVE_APP, true, createEngineParameters(engine));
+   }
+   
+   /**
+    * Reset configuration for a particular proxy following an error.
+    */
+   public static List<MCMPRequest> getResetRequests(Server server, 
+         NodeConfiguration nodeConfig, BalancerConfiguration balancerConfig) {
+       
+       List<MCMPRequest> requests = new ArrayList<MCMPRequest>();
+       Service[] services = server.findServices();
+       for (int i = 0; i < services.length; i++) {
+           Engine engine = (Engine) services[i].getContainer();
+           if (engine.getJvmRoute() != null)
+           {
+              requests.add(MCMPUtils.createRemoveAllRequest(engine));
+           }
+           requests.add(MCMPUtils.createConfigRequest(engine, nodeConfig, balancerConfig));
+           Container[] children = engine.findChildren();
+           for (int j = 0; j < children.length; j++) {
+               Container[] children2 = children[j].findChildren();
+               for (int k = 0; k < children2.length; k++) {
+                   Context ctx = (Context) children2[k];
+                   if (ctx.isStarted())
+                   {
+                      requests.add(MCMPUtils.createEnableAppRequest(ctx));
+                   }
+               }
+           }
+       }
+       
+       return requests;        
+   }
+   
+   private static Map<String, String> createEngineParameters(Engine engine)
+   {
+      Map<String, String> parameters = new HashMap<String, String>();
+      parameters.put("JVMRoute", engine.getJvmRoute());
+      return parameters;
+   }
+    
+   private static Map<String, String> createContextParameters(Context context)
+   {
+      Map<String, String> parameters = new HashMap<String, String>();
+      parameters.put("JVMRoute", Utils.getJvmRoute(context));
+      parameters.put("Context", ("".equals(context.getPath())) ? "/" : context.getPath());
+      parameters.put("Alias", Utils.getHost(context));
+      return parameters;
+   }
+   
+   public static AddressPort parseAddressPort(String addressPort)
+   {
+      int pos = addressPort.indexOf(':');
+      String host = null;
+      int port = 0;
+      if (pos < 0) {
+          host = addressPort;
+      } else if (pos == 0) {
+          host = null;
+          port = Integer.parseInt(addressPort.substring(1));
+      } else {
+          host = addressPort.substring(0, pos);
+          port = Integer.parseInt(addressPort.substring(pos + 1));
+      }   
+      
+      InetAddress address = null;
+      try {
+         address = InetAddress.getByName(host);
+      } catch (Exception e) {
+         throw new IllegalArgumentException(e);
+      }   
+      
+      return new AddressPort(address, port);
+   }
+   
+   /**
+    * Disable external instantiation. 
+    */
+   private MCMPUtils()
+   {
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/ResetRequestSource.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/ResetRequestSource.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/ResetRequestSource.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,45 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp;
+
+import java.util.List;
+
+/**
+ * Source for a list of requests that should be sent to an httpd-side 
+ * mod_cluster instance when an {@link MCMPHandler} determines that
+ * the httpd-side state needs to be reset.
+ * 
+ * @author Brian Stansberry
+ *
+ */
+public interface ResetRequestSource
+{
+   /**
+    * Gets a list of requests that should be sent to an httpd-side 
+    * mod_cluster instance when an {@link MCMPHandler} determines that
+    * its state needs to be reset.
+    * 
+    * @return a list of requests. Will not return <code>null</code>.
+    */
+   List<MCMPRequest> getResetRequests();
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/DefaultMCMPHandler.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/DefaultMCMPHandler.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/DefaultMCMPHandler.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,990 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+
+package org.jboss.web.tomcat.service.modcluster.mcmp.impl;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.apache.catalina.util.StringManager;
+import org.apache.tomcat.util.buf.CharChunk;
+import org.apache.tomcat.util.buf.UEncoder;
+import org.jboss.logging.Logger;
+import org.jboss.web.tomcat.service.modcluster.Constants;
+import org.jboss.web.tomcat.service.modcluster.advertise.AdvertiseListener;
+import org.jboss.web.tomcat.service.modcluster.config.MCMPHandlerConfiguration;
+import org.jboss.web.tomcat.service.modcluster.mcmp.AddressPort;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPHandler;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequest;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPRequestType;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServer;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPServerState;
+import org.jboss.web.tomcat.service.modcluster.mcmp.MCMPUtils;
+import org.jboss.web.tomcat.service.modcluster.mcmp.ResetRequestSource;
+
+/**
+ * Default implementation of {@link MCMPHandler}.
+ * 
+ * @author Brian Stansberry
+ * @version $Revision$
+ */
+public class DefaultMCMPHandler implements MCMPHandler
+{
+   protected static Logger log = Logger.getLogger(DefaultMCMPHandler.class);
+
+   /**
+    * The string manager for this package.
+    */
+   protected StringManager sm =
+       StringManager.getManager(Constants.Package);
+
+
+   // -------------------------------------------------------------- Constants
+
+   
+   
+
+   
+   // ----------------------------------------------------------------- Fields
+   
+   /** Our configuration */
+   private final MCMPHandlerConfiguration config;  
+   
+   
+   /**
+    * Source for reset requests when we need to reset a proxy.
+    */
+   private final ResetRequestSource resetRequestSource;
+
+   /**
+    * URL encoder used to generate requests bodies.
+    */
+   private UEncoder encoder = new UEncoder();
+   
+   /**
+    * Proxies.
+    */
+   private volatile Proxy[] proxies = null;
+   
+   
+   /**
+    * Add proxy list.
+    */
+   private ArrayList<Proxy> addProxies = new ArrayList<Proxy>(); 
+   
+   
+   /**
+    * Remove proxy list.
+    */
+   private ArrayList<Proxy> removeProxies = new ArrayList<Proxy>();
+   
+   
+   /**
+    * Socket factory.
+    */
+   private JSSESocketFactory sslSocketFactory = null;
+   
+   private final boolean allowAdvertise;
+   
+   /**
+    * Advertise listener.
+    */
+   private AdvertiseListener listener = null;
+   
+   /** Initialization completion flag */
+   private boolean init;
+   
+   // -----------------------------------------------------------  Constructors
+   
+   public DefaultMCMPHandler(MCMPHandlerConfiguration config, ResetRequestSource source)
+   {
+      this(config, source, true);
+   }
+   
+   public DefaultMCMPHandler(MCMPHandlerConfiguration config, ResetRequestSource source, boolean allowAdvertise)
+   {
+      this.config = config;
+      this.resetRequestSource = source;
+      this.allowAdvertise = allowAdvertise;
+   }  
+   
+   
+   // -------------------------------------------------------------- Properties
+
+
+   
+   
+   // ------------------------------------------------------------  MCMPHandler
+   
+   public MCMPHandlerConfiguration getConfiguration()
+   {
+      return this.config;
+   }
+
+   public ResetRequestSource getResetRequestSource() 
+   { 
+      return this.resetRequestSource; 
+   }
+   
+   public synchronized void init()
+   {
+      if (this.init)
+         return;
+      
+      sslInit();
+      
+      if (this.config.getProxyList() == null) {
+         if (Boolean.FALSE.equals(this.config.getAdvertise()) == false) {
+            proxies = new Proxy[0];
+            startListener();
+         } else {
+            // Default to a httpd on localhost on the default port
+            proxies = new Proxy[1];
+            try
+            {
+               proxies[0] = new Proxy(this.sslSocketFactory, this.config.getSocketTimeout());
+            }
+            catch (UnknownHostException e)
+            {
+               proxies = new Proxy[0];
+               log.fatal("No proxy list or URL configured, advertise disabled " +
+               		"and no localhost available; connect connect to mod_cluster", e);
+            }
+         }
+      } else {
+         ArrayList<Proxy> proxyList = new ArrayList<Proxy>();
+         StringTokenizer tok = new StringTokenizer(this.config.getProxyList(), ",");
+         while (tok.hasMoreTokens()) {
+            String token = tok.nextToken().trim();
+            int pos = token.indexOf(':');
+            String address = null;
+            int port = Proxy.DEFAULT_PORT;
+            if (pos < 0) {
+               address = token;
+            } else if (pos == 0) {
+               address = null;
+               port = Integer.parseInt(token.substring(1));
+            } else {
+               address = token.substring(0, pos);
+               port = Integer.parseInt(token.substring(pos + 1));
+            }
+            InetAddress inetAddress = null;
+            try {
+               if (address != null) {
+                  inetAddress = InetAddress.getByName(address);
+               }
+            } catch (Exception e) {
+               log.error(sm.getString("clusterListener.error.invalidHost", address), e);
+               continue;
+            }
+            Proxy proxy = new Proxy(inetAddress, port, this.sslSocketFactory, this.config.getSocketTimeout());
+            proxyList.add(proxy);
+         }
+         proxies = proxyList.toArray(new Proxy[proxyList.size()]);
+         
+         if (Boolean.TRUE.equals(this.config.getAdvertise()))
+         {
+            startListener();
+         }
+      }
+      
+      this.init = true;
+      
+   }
+   
+   public synchronized void shutdown()
+   {  
+      if (!this.init)
+         return;
+      
+      stopListener();
+      
+      for (int i = 0; i < proxies.length; i++) {
+         synchronized (proxies[i])
+         {
+            proxies[i].closeConnection();
+         }
+      }
+      
+      this.sslSocketFactory = null;
+      
+      this.init = false;
+   }
+   
+   /**
+    * Add proxy.
+    */
+   public void addProxy(String address) {
+       
+       AddressPort ap = MCMPUtils.parseAddressPort(address);
+       addProxy(ap.address, ap.port);
+   }   
+   
+   /**
+    * Add proxy.
+    */
+   public synchronized void addProxy(String host, int port) 
+   {
+      InetAddress address = null;
+      try {
+         address = InetAddress.getByName(host);
+      } catch (Exception e) {
+         throw new IllegalArgumentException(e);
+      }
+      
+      addProxyInternal(address, port); 
+   }
+   
+   public synchronized void addProxy(InetAddress address, int port)
+   {       
+      addProxyInternal(address, port); 
+   }
+   
+   private synchronized Proxy addProxyInternal(InetAddress address, int port)
+   {      
+      for (Proxy candidate : proxies)
+      {
+         if (candidate.getAddress().equals(address) && candidate.getPort() == port)
+            return candidate;
+      }
+      for (Proxy candidate : addProxies)
+      {
+         if (candidate.getAddress().equals(address) && candidate.getPort() == port)
+            return candidate;
+      }
+      for (Proxy candidate : removeProxies)
+      {
+         if (candidate.getAddress().equals(address) && candidate.getPort() == port)
+            return candidate;
+      }
+      
+      Proxy proxy = new Proxy(address, port, this.sslSocketFactory, this.config.getSocketTimeout());
+      proxy.setState(Proxy.State.ERROR);
+      addProxies.add(proxy);   
+      return proxy;
+   }
+   
+   public synchronized void establishProxy(MCMPServer server)
+   {     
+      Proxy proxy = addProxyInternal(server.getAddress(), server.getPort());
+      proxy.setEstablished(server.isEstablished());
+   }
+   
+   
+   /**
+    * Remove proxy.
+    */
+   public synchronized void removeProxy(String host, int port) 
+   {
+      InetAddress address = null;
+      try {
+         address = InetAddress.getByName(host);
+      } catch (Exception e) {
+         throw new IllegalArgumentException(e);
+      }
+      removeProxy(address, port);
+   }   
+   
+   /**
+    * Remove proxy.
+    */
+   public synchronized void removeProxy(InetAddress address, int port) 
+   {
+      Proxy proxy = new Proxy(address, port, this.sslSocketFactory, this.config.getSocketTimeout());
+      removeProxies.add(proxy);
+   }
+   
+   public synchronized Set<MCMPServerState> getProxyStates()
+   {
+      Set<MCMPServerState> result = new HashSet<MCMPServerState>(proxies.length);
+      for (Proxy proxy : proxies)
+      {
+         result.add(proxy);
+      }
+      return result;
+   }
+
+   public synchronized boolean isProxyHealthOK()
+   {
+      boolean ok = true;
+      Proxy[] local = proxies;
+      for (Proxy proxy : local)
+      {
+         if (proxy.getState() != Proxy.State.OK)
+         {
+            ok = false;
+            break;
+         }
+      }
+      return ok;
+   }
+   
+   public synchronized void markProxiesInError()
+   {
+      Proxy[] local = proxies;
+      for (Proxy proxy : local)
+      {
+         synchronized (proxy)
+         {
+            if (proxy.getState() == Proxy.State.OK)
+            {
+               proxy.setState(Proxy.State.ERROR);
+            }
+         }
+      }      
+   }
+   
+   /**
+    * Retrieves the full proxy configuration. To be used through JMX or similar.
+    * 
+    *         response: HTTP/1.1 200 OK
+    *   response:
+    *   node: [1:1] JVMRoute: node1 Domain: [bla] Host: 127.0.0.1 Port: 8009 Type: ajp
+    *   host: 1 [] vhost: 1 node: 1
+    *   context: 1 [/] vhost: 1 node: 1 status: 1
+    *   context: 2 [/myapp] vhost: 1 node: 1 status: 1
+    *   context: 3 [/host-manager] vhost: 1 node: 1 status: 1
+    *   context: 4 [/docs] vhost: 1 node: 1 status: 1
+    *   context: 5 [/manager] vhost: 1 node: 1 status: 1
+    *
+    * @return the proxy confguration
+    */
+   public String getProxyConfiguration() {
+       HashMap<String, String> parameters = new HashMap<String, String>();
+       // Send DUMP * request
+       Proxy[] local = proxies;
+       StringBuffer result = new StringBuffer();
+       for (int i = 0; i < local.length; i++) {
+          
+           result.append("Proxy[").append(i).append("]:[").append(local[i].getAddress())
+                   .append(':').append(local[i].getPort()).append("]: \r\n");
+           synchronized (local[i])
+           {
+              result.append(sendRequest(new MCMPRequest(MCMPRequestType.DUMP, true, parameters), local[i]));
+           }
+           result.append("\r\n");
+       }
+       return result.toString();
+   }
+   
+   public InetAddress getLocalAddress() throws IOException
+   {
+      IOException firstException = null;
+      Proxy[] local = proxies;
+      for (Proxy proxy : local)
+      {
+         try
+         {
+            return proxy.getConnection().getInetAddress();
+         }
+         catch (IOException e)
+         {
+            // Cache the exception in case no other connection works, 
+            // but keep trying 
+            if (firstException == null)
+               firstException = e;
+         }
+      }
+      
+      if (firstException != null)
+         throw firstException;
+      
+      // We get here if there are no proxies
+      return null;
+   }
+   
+   
+   /**
+    * Reset a DOWN connection to the proxy up to ERROR, where the configuration will
+    * be refreshed. To be used through JMX or similar.
+    */
+   public void reset() {
+       Proxy[] local = proxies;
+       for (Proxy proxy : local) {
+          synchronized (proxy)
+          {
+           if (proxy.getState() == Proxy.State.DOWN) {
+               proxy.setState(Proxy.State.ERROR);
+           }
+          }
+       }
+   }
+   
+   
+   /**
+    * Refresh configuration. To be used through JMX or similar.
+    */
+   public void refresh() {
+       // Set as error, and the periodic event will refresh the configuration
+       Proxy[] local = proxies;
+       for (Proxy proxy : local) {
+          synchronized (proxy)
+          {
+           if (proxy.getState() == Proxy.State.OK) {
+              proxy.setState(Proxy.State.ERROR);
+           }
+          }
+       }
+   }   
+
+
+   /**
+    * Send a periodic status request. If in error state, the listener will attempt to refresh
+    * the configuration on the front end server.
+    * 
+    * @param engine
+    */
+   public void status() {
+       
+       // Check to add or remove proxies, and rebuild a new list if needed
+       synchronized (this) {
+           if (!addProxies.isEmpty() || !removeProxies.isEmpty()) {
+               ArrayList<Proxy> currentProxies = new ArrayList<Proxy>();
+               for (int i = 0; i < proxies.length; i++) {
+                   currentProxies.add(proxies[i]);
+               }
+               for (int i = 0; i < addProxies.size(); i++) {
+                   if (!currentProxies.contains(addProxies.get(i))) {
+                       currentProxies.add(addProxies.get(i));
+                   }
+               }
+               for (int i = 0; i < removeProxies.size(); i++) {
+                   if (currentProxies.contains(removeProxies.get(i))) {
+                       currentProxies.remove(removeProxies.get(i));
+                   }
+               }
+               addProxies.clear();
+               removeProxies.clear();
+               proxies = currentProxies.toArray(new Proxy[0]);
+               
+               // Reset all connections
+               for (Proxy proxy : proxies)
+               {                  
+                  proxy.closeConnection();
+               }
+           }
+       }
+       
+       // Attempt to reset any proxies in error
+       List<MCMPRequest> resetRequests = null;       
+       Proxy[] local = proxies;
+       for (Proxy proxy : local) {
+           synchronized (proxy)
+           {
+              if (proxy.getState() == Proxy.State.ERROR) {
+                 proxy.setState(Proxy.State.OK);
+                 if (resetRequests == null)
+                 {
+                    resetRequests = this.resetRequestSource.getResetRequests();
+                 }
+                 
+                 for (MCMPRequest request : resetRequests)
+                 {
+                    sendRequest(request, proxy);
+                 }
+              }
+           }
+       }
+       
+   }
+   
+   /**
+    * Send HTTP request, with the specified list of parameters. If an IO error occurs, the error state will
+    * be set. If the front end server reports an error, will mark as error Proxy.State. Other unexpected exceptions 
+    * will be thrown and the error state will be set.
+    * 
+    * @param command
+    * @param wildcard
+    * @param parameters
+    * @return the response body as a String; null if in error state or a normal error occurs
+    */
+   public void sendRequest(MCMPRequest request) {
+       Proxy[] local = proxies;
+       for (Proxy proxy : local)
+       {
+          sendRequest(request, proxy);
+       }
+   }
+
+   private String sendRequest(MCMPRequest request, Proxy proxy) {
+
+       BufferedReader reader = null;
+       BufferedWriter writer = null;
+       CharChunk body = null;
+       
+       String command = request.getRequestType().getCommand();
+       boolean wildcard = request.isWildcard();
+       Map<String, String> parameters = request.getParameters();
+       
+       // First, encode the POST body
+       try {
+           body = encoder.encodeURL("", 0, 0);
+           body.recycle();
+           Iterator<String> keys = parameters.keySet().iterator();
+           while (keys.hasNext()) {
+               String key = keys.next();
+               String value = parameters.get(key);
+               if (value == null) {
+                   throw new IllegalArgumentException(sm.getString("clusterListener.error.nullAttribute", key));
+               }
+               body = encoder.encodeURL(key, 0, key.length());
+               body.append('=');
+               if (value != null) {
+                   body = encoder.encodeURL(value, 0, value.length());
+               }
+               if (keys.hasNext()) {
+                   body.append('&');
+               }
+           }
+       } catch (IOException e) {
+           body.recycle();
+           // Error encoding URL, should not happen
+           throw new IllegalArgumentException(e);
+       }
+
+        // If there was an error, do nothing until the next periodic event, where the whole configuration
+        // will be refreshed
+        if (proxy.getState() != Proxy.State.OK) {
+            return null;
+        }
+        
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("clusterListener.request", command, wildcard, proxy));
+        }
+        
+        synchronized (proxy)
+        {
+        
+           try {
+   
+               // Then, connect to the proxy
+               proxy.getConnection();
+               writer = proxy.getConnectionWriter();
+               // Check connection to see if it is still alive (not really allowed, 
+               // but httpd deals with the extra CRLF)
+               try {
+                   writer.write("\r\n");
+                   writer.flush();
+               } catch (IOException e) {
+                   // Get a new connection; if it fails this second time, it is an error
+                   proxy.closeConnection();
+                   proxy.getConnection();
+                   writer = proxy.getConnectionWriter();
+               }
+   
+               // Generate and write request
+               String url = this.config.getProxyURL();
+               if (url == null) {
+                   url = (wildcard) ? "/*" : "/";
+               } else {
+                   if (url.endsWith("/") && wildcard) {
+                       url = url + "*";
+                   } else if (wildcard) {
+                       url = url + "/*";
+                   }
+               }
+               String requestLine = command + " " + url + " HTTP/1.0";
+               writer.write(requestLine);
+               writer.write("\r\n");
+               writer.write("Content-Length: " + body.getLength() + "\r\n");
+               writer.write("User-Agent: ClusterListener/1.0\r\n");
+               writer.write("Connection: Keep-Alive\r\n");
+               writer.write("\r\n");
+               writer.write(body.getBuffer(), body.getStart(), body.getLength());
+               writer.write("\r\n");
+               writer.flush();
+   
+               // Read the response to a string
+               reader = proxy.getConnectionReader();
+               // Read the first response line and skip the rest of the HTTP header
+               String responseStatus = reader.readLine();
+               // Parse the line, which is formed like HTTP/1.x YYY Message
+               int status = 500;
+               String version = "0";
+               String message = null;
+               String errorType = null;
+               int contentLength = 0;
+               if (responseStatus != null) {
+                   try {
+                       responseStatus = responseStatus.substring(responseStatus.indexOf(' ') + 1, responseStatus.indexOf(' ', responseStatus.indexOf(' ') + 1));
+                       status = Integer.parseInt(responseStatus);
+                       String header = reader.readLine();
+                       while (!"".equals(header)) {
+                           int colon = header.indexOf(':');
+                           String headerName = header.substring(0, colon).trim();
+                           String headerValue = header.substring(colon + 1).trim();
+                           if ("version".equalsIgnoreCase(headerName)) {
+                               version = headerValue;
+                           } else if ("type".equalsIgnoreCase(headerName)) {
+                               errorType = headerValue;
+                           } else if ("mess".equalsIgnoreCase(headerName)) {
+                               message = headerValue;
+                           } else if ("content-length".equalsIgnoreCase(headerName)) {
+                               contentLength = Integer.parseInt(headerValue);
+                           }
+                           header = reader.readLine();
+                       }
+                   } catch (Exception e) {
+                       log.info(sm.getString("clusterListener.error.parse", command), e);
+                   }
+               }
+   
+               // Mark as error if the front end server did not return 200; the configuration will
+               // be refreshed during the next periodic event 
+               if (status == 200) {
+                   
+                   // We know the request succeeded, so if appropriate
+                   // mark the proxy as established before any possible
+                   // later exception happens
+                   if (request.getRequestType().getEstablishesServer()) {
+                      proxy.setEstablished(true);
+                   }
+                   
+                   // Read the request body
+                   StringBuffer result = new StringBuffer();
+                   if (contentLength > 0) {
+                       int thisTime = contentLength;
+                       char[] buf = new char[512];
+                       while (contentLength > 0) {
+                           thisTime = (contentLength > buf.length) ? buf.length : contentLength;
+                           int n = reader.read(buf, 0, thisTime);
+                           if (n <= 0) {
+                               break;
+                           } else {
+                               result.append(buf, 0, n);
+                               contentLength -= n;
+                           }
+                       }
+                   }
+                   
+                   return result.toString();
+                   
+               } else {
+                   if ("SYNTAX".equals(errorType)) {
+                       // Syntax error means the protocol is incorrect, which cannot be automatically fixed
+                       proxy.setState(Proxy.State.DOWN);
+                       log.error(sm.getString("clusterListener.error.syntax", command, proxy, errorType, message));
+                   } else {
+                       proxy.setState(Proxy.State.ERROR);
+                       log.error(sm.getString("clusterListener.error.other", command, proxy, errorType, message));
+                   }
+               }
+   
+           } catch (IOException e) {
+               // Most likely this is a connection error with the proxy
+               proxy.setState(Proxy.State.ERROR);
+               log.info(sm.getString("clusterListener.error.io", command, proxy), e);
+           } finally {
+               // If there's an error of any sort, or if the proxy did not return 200, it is an error
+               if (proxy.getState() != Proxy.State.OK) {
+                   proxy.closeConnection();
+               }
+           }
+        }
+
+       return null;
+       
+   }
+
+   
+   /**
+    * SSL init.
+    */
+   protected void sslInit() {
+       if (this.config.isSsl()) {
+           sslSocketFactory = new JSSESocketFactory(this.config);
+       }
+   }    
+   
+   /**
+    * Start the advertise listener.
+    */
+   protected void startListener() 
+   {
+      if (this.allowAdvertise)
+      {
+          listener = new AdvertiseListener(this);
+          if (this.config.getAdvertiseGroupAddress() != null) {
+              listener.setGroupAddress(this.config.getAdvertiseGroupAddress());
+          }
+          if (this.config.getAdvertisePort() > 0) {
+              listener.setAdvertisePort(this.config.getAdvertisePort());
+          }
+          try {
+              if (this.config.getAdvertiseSecurityKey() != null) {
+                  listener.setSecurityKey(this.config.getAdvertiseSecurityKey());
+              }
+              listener.start();
+          } catch (IOException e) {
+              log.error(sm.getString("clusterListener.error.startListener"), e);
+          } catch (NoSuchAlgorithmException e) {
+              // Should never happen
+              log.error(sm.getString("clusterListener.error.startListener"), e);
+          }
+      }
+   }
+   
+
+   /**
+    * Stop the advertise listener.
+    */
+   protected void stopListener() {
+       if (listener != null) {
+           try {
+               listener.destroy();
+           } catch (IOException e) {
+               log.error(sm.getString("clusterListener.error.stopListener"), e);
+           }
+           listener = null;
+       }
+   }
+   
+   /**
+    * This class represents a front-end httpd server.
+    */
+   private static class Proxy implements MCMPServerState
+   {
+      public static final int DEFAULT_PORT = 8000;
+      
+      private final InetAddress address;
+      private final int port;
+      private volatile State state = State.OK;
+      private volatile boolean established;
+      private final JSSESocketFactory sslSocketFactory;
+      private final int socketTimeout;
+      private Socket connection;
+      private BufferedReader connectionReader;
+      private BufferedWriter connectionWriter;
+
+      /**
+       * The string manager for this package.
+       */
+      private final StringManager sm =
+          StringManager.getManager(Constants.Package);
+      
+      private Proxy(int socketTimeout) throws UnknownHostException
+      {
+         this(InetAddress.getLocalHost(), DEFAULT_PORT, null, socketTimeout);
+      }
+      
+      private Proxy(JSSESocketFactory sslSocketFactory, int socketTimeout) throws UnknownHostException
+      {
+         this(InetAddress.getLocalHost(), DEFAULT_PORT, sslSocketFactory, socketTimeout);
+      }
+      
+      private Proxy(InetAddress address, int socketTimeout)
+      {
+         this(address, DEFAULT_PORT, null, socketTimeout);
+      }
+      
+      private Proxy(InetAddress address, JSSESocketFactory sslSocketFactory, int socketTimeout)
+      {
+         this(address, DEFAULT_PORT, sslSocketFactory, socketTimeout);
+      }
+      
+      private Proxy(InetAddress address, int port, int socketTimeout)
+      {
+         this(address, port, null, socketTimeout);
+      }
+      
+      private Proxy(InetAddress address, int port, JSSESocketFactory sslSocketFactory, int socketTimeout)
+      {
+         if (address == null)
+         {
+            throw new IllegalArgumentException("address is null");
+         }
+         if (port <= 0)
+         {
+            throw new IllegalArgumentException("invalid port");
+         }
+         
+         this.address = address;
+         this.port = port;
+         this.sslSocketFactory = sslSocketFactory;
+         this.socketTimeout = socketTimeout;
+      }       
+      
+      // -------------------------------------------- MCMPServerConnectionState
+      
+      public synchronized State getState()
+      {
+         return this.state;
+      }       
+      
+      // ----------------------------------------------------------- MCMPServer
+      
+      public InetAddress getAddress()
+      {
+         return this.address;
+      }
+
+      public int getPort()
+      {
+         return this.port;
+      }
+      
+      public boolean isEstablished()
+      {
+         return this.established;
+      }
+      
+      // ------------------------------------------------------------ Overrides
+
+      public String toString() {
+         if (this.address == null) {
+            return ":" + this.port;
+         } else {
+            return this.address.getHostAddress() + ":" + this.port;
+         }
+      }
+
+      @Override
+      public boolean equals(Object o) {
+         if (o instanceof Proxy) {
+            Proxy compare = (Proxy) o;
+            if (this.port != compare.port) {
+               return false;
+            }
+            if (compare.address == null) {
+               if (this.address == null) {
+                  return true;
+               }
+            } else if ((compare.address.equals(this.address)) && this.port == compare.port) {
+               return true;
+            }
+         }
+         return false;
+      } 
+
+      @Override
+      public int hashCode()
+      {
+         int result = 17;
+         result += 23 * (this.address == null ? 0 : this.address.hashCode());
+         result += 23 * this.port;
+         return result;
+      }
+      
+      // -------------------------------------------------------------- Private
+
+      private synchronized void setState(State state)
+      {
+         if (state == null)
+         {
+            throw new IllegalArgumentException("state is null");
+         }
+         this.state = state;
+      }
+      
+      private synchronized void setEstablished(boolean established)
+      {
+         this.established = established;
+      }      
+      
+      /**
+       * Return a reader to the proxy.
+       */
+      private synchronized Socket getConnection()
+          throws IOException {
+          if (connection == null || connection.isClosed()) {
+              if (this.sslSocketFactory != null) {
+                  connection = this.sslSocketFactory.createSocket(this.address, this.port);
+              } else {
+                  connection = new Socket(this.address, this.port);
+              }
+              connection.setSoTimeout(this.socketTimeout);
+          }
+          return connection;
+      }
+      
+
+      /**
+       * Return a reader to the proxy.
+       */
+      private synchronized BufferedReader getConnectionReader()
+          throws IOException {
+          if (this.connectionReader == null) {
+              this.connectionReader = new BufferedReader(new InputStreamReader(this.connection.getInputStream()));
+          }
+          return this.connectionReader;
+      }
+      
+
+      /**
+       * Return a writer to the proxy.
+       */
+      private synchronized BufferedWriter getConnectionWriter()
+          throws IOException {
+          if (connectionWriter == null) {
+              connectionWriter = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
+          }
+          return connectionWriter;
+      }
+      
+
+      /**
+       * Close connection.
+       */
+      private synchronized void closeConnection() {
+          try {
+              if (connectionReader != null) {
+                  connectionReader.close();
+              }
+          } catch (IOException e) {
+              // Ignore
+          }
+          connectionReader = null;
+          try {
+              if (connectionWriter != null) {
+                  connectionWriter.close();
+              }
+          } catch (IOException e) {
+              // Ignore
+          }
+          connectionWriter = null;
+          try {
+              if (connection != null) {
+                  connection.close();
+              }
+          } catch (IOException e) {
+              // Ignore
+          }
+          connection = null;
+      }
+   }
+
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSEKeyManager.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSEKeyManager.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSEKeyManager.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,144 @@
+/*
+ *  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.jboss.web.tomcat.service.modcluster.mcmp.impl;
+
+import java.net.Socket;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.X509KeyManager;
+
+/**
+ * X509KeyManager which allows selection of a specific keypair and certificate
+ * chain (identified by their keystore alias name) to be used by the server to
+ * authenticate itself to SSL clients.
+ *
+ * @author Jan Luehe
+ */
+public final class JSSEKeyManager implements X509KeyManager {
+
+    private X509KeyManager delegate;
+    private String serverKeyAlias;
+
+    /**
+     * Constructor.
+     *
+     * @param mgr The X509KeyManager used as a delegate
+     * @param serverKeyAlias The alias name of the server's keypair and
+     * supporting certificate chain
+     */
+    public JSSEKeyManager(X509KeyManager mgr, String serverKeyAlias) {
+        this.delegate = mgr;
+        this.serverKeyAlias = serverKeyAlias;
+    }
+
+    /**
+     * Choose an alias to authenticate the client side of a secure socket,
+     * given the public key type and the list of certificate issuer authorities
+     * recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name(s), ordered with the
+     * most-preferred key type first
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     * @param socket The socket to be used for this connection. This parameter
+     * can be null, in which case this method will return the most generic
+     * alias to use
+     *
+     * @return The alias name for the desired key, or null if there are no
+     * matches
+     */
+    public String chooseClientAlias(String[] keyType, Principal[] issuers,
+                                    Socket socket) {
+        return delegate.chooseClientAlias(keyType, issuers, socket);
+    }
+
+    /**
+     * Returns this key manager's server key alias that was provided in the
+     * constructor.
+     *
+     * @param keyType The key algorithm type name (ignored)
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used (ignored)
+     * @param socket The socket to be used for this connection. This parameter
+     * can be null, in which case this method will return the most generic
+     * alias to use (ignored)
+     *
+     * @return Alias name for the desired key
+     */
+    public String chooseServerAlias(String keyType, Principal[] issuers,
+                                    Socket socket) {
+        return serverKeyAlias;
+    }
+
+    /**
+     * Returns the certificate chain associated with the given alias.
+     *
+     * @param alias The alias name
+     *
+     * @return Certificate chain (ordered with the user's certificate first
+     * and the root certificate authority last), or null if the alias can't be
+     * found
+     */
+    public X509Certificate[] getCertificateChain(String alias) {
+        return delegate.getCertificateChain(alias); 
+    }
+
+    /**
+     * Get the matching aliases for authenticating the client side of a secure
+     * socket, given the public key type and the list of certificate issuer
+     * authorities recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     *
+     * @return Array of the matching alias names, or null if there were no
+     * matches
+     */
+    public String[] getClientAliases(String keyType, Principal[] issuers) {
+        return delegate.getClientAliases(keyType, issuers);
+    }
+
+    /**
+     * Get the matching aliases for authenticating the server side of a secure
+     * socket, given the public key type and the list of certificate issuer
+     * authorities recognized by the peer (if any).
+     *
+     * @param keyType The key algorithm type name
+     * @param issuers The list of acceptable CA issuer subject names, or null
+     * if it does not matter which issuers are used
+     *
+     * @return Array of the matching alias names, or null if there were no
+     * matches
+     */
+    public String[] getServerAliases(String keyType, Principal[] issuers) {
+        return delegate.getServerAliases(keyType, issuers);
+    }
+
+    /**
+     * Returns the key associated with the given alias.
+     *
+     * @param alias The alias name
+     *
+     * @return The requested key, or null if the alias can't be found
+     */
+    public PrivateKey getPrivateKey(String alias) {
+        return delegate.getPrivateKey(alias);
+    }
+}

Added: trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSESocketFactory.java
===================================================================
--- trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSESocketFactory.java	                        (rev 0)
+++ trunk/tomcat/src/main/org/jboss/web/tomcat/service/modcluster/mcmp/impl/JSSESocketFactory.java	2008-07-21 05:54:56 UTC (rev 76052)
@@ -0,0 +1,552 @@
+/*
+ *  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.jboss.web.tomcat.service.modcluster.mcmp.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.security.cert.CRL;
+import java.security.cert.CRLException;
+import java.security.cert.CertPathParameters;
+import java.security.cert.CertStore;
+import java.security.cert.CertStoreParameters;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.CollectionCertStoreParameters;
+import java.security.cert.PKIXBuilderParameters;
+import java.security.cert.X509CertSelector;
+import java.util.Collection;
+import java.util.Vector;
+
+import javax.net.ssl.CertPathTrustManagerParameters;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.ManagerFactoryParameters;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509KeyManager;
+
+import org.apache.tomcat.util.res.StringManager;
+import org.jboss.web.tomcat.service.modcluster.config.SSLConfiguration;
+
+/*
+  1. Make the JSSE's jars available, either as an installed
+     extension (copy them into jre/lib/ext) or by adding
+     them to the Tomcat classpath.
+  2. keytool -genkey -alias tomcat -keyalg RSA
+     Use "changeit" as password ( this is the default we use )
+ */
+
+/**
+ * SSL server socket factory. It _requires_ a valid RSA key and
+ * JSSE. 
+ *
+ * @author Harish Prabandham
+ * @author Costin Manolache
+ * @author Stefan Freyr Stefansson
+ * @author EKR -- renamed to JSSESocketFactory
+ * @author Jan Luehe
+ * @author Bill Barker
+ */
+public class JSSESocketFactory {
+
+    private static StringManager sm =
+        StringManager.getManager("org.apache.tomcat.util.net.jsse.res");
+
+    static org.jboss.logging.Logger log =
+        org.jboss.logging.Logger.getLogger(JSSESocketFactory.class);
+
+    protected boolean initialized;
+    //protected String clientAuth = "false";
+    protected SSLSocketFactory sslProxy = null;
+    protected String[] enabledCiphers;
+    protected SSLConfiguration config = null;
+    
+    /**
+     * Flag to state that we require client authentication.
+     */
+    //protected boolean requireClientAuth = false;
+
+    /**
+     * Flag to state that we would like client authentication.
+     */
+    //protected boolean wantClientAuth    = false;
+
+
+    public JSSESocketFactory (SSLConfiguration config) {
+        this.config = config;
+    }
+
+    public Socket createSocket (InetAddress ifAddress, int port)
+        throws IOException
+    {   
+        if (!initialized) init();
+        Socket socket = sslProxy.createSocket(ifAddress, port);
+        initSocket(socket);
+        return socket;
+    }
+    
+    public void handshake(Socket sock) throws IOException {
+        ((SSLSocket)sock).startHandshake();
+    }
+
+    /*
+     * Determines the SSL cipher suites to be enabled.
+     *
+     * @param requestedCiphers Comma-separated list of requested ciphers
+     * @param supportedCiphers Array of supported ciphers
+     *
+     * @return Array of SSL cipher suites to be enabled, or null if none of the
+     * requested ciphers are supported
+     */
+    protected String[] getEnabledCiphers(String requestedCiphers,
+                                         String[] supportedCiphers) {
+
+        String[] enabledCiphers = null;
+
+        if (requestedCiphers != null) {
+            Vector<String> vec = null;
+            String cipher = requestedCiphers;
+            int index = requestedCiphers.indexOf(',');
+            if (index != -1) {
+                int fromIndex = 0;
+                while (index != -1) {
+                    cipher = requestedCiphers.substring(fromIndex, index).trim();
+                    if (cipher.length() > 0) {
+                        /*
+                         * Check to see if the requested cipher is among the
+                         * supported ciphers, i.e., may be enabled
+                         */
+                        for (int i=0; supportedCiphers != null
+                                     && i<supportedCiphers.length; i++) {
+                            if (supportedCiphers[i].equals(cipher)) {
+                                if (vec == null) {
+                                    vec = new Vector<String>();
+                                }
+                                vec.addElement(cipher);
+                                break;
+                            }
+                        }
+                    }
+                    fromIndex = index+1;
+                    index = requestedCiphers.indexOf(',', fromIndex);
+                } // while
+                cipher = requestedCiphers.substring(fromIndex);
+            }
+
+            if (cipher != null) {
+                cipher = cipher.trim();
+                if (cipher.length() > 0) {
+                    /*
+                     * Check to see if the requested cipher is among the
+                     * supported ciphers, i.e., may be enabled
+                     */
+                    for (int i=0; supportedCiphers != null
+                                 && i<supportedCiphers.length; i++) {
+                        if (supportedCiphers[i].equals(cipher)) {
+                            if (vec == null) {
+                                vec = new Vector<String>();
+                            }
+                            vec.addElement(cipher);
+                            break;
+                        }
+                    }
+                }
+            }           
+
+            if (vec != null) {
+                enabledCiphers = new String[vec.size()];
+                vec.copyInto(enabledCiphers);
+            }
+        } else {
+            enabledCiphers = sslProxy.getDefaultCipherSuites();
+        }
+
+        return enabledCiphers;
+    }
+     
+    /*
+     * Gets the SSL server's keystore.
+     */
+    protected KeyStore getKeystore(String type, String provider, String pass)
+            throws IOException {
+        return getStore(type, provider, config.getSslKeyStore(), pass);
+    }
+
+    /*
+     * Gets the SSL server's truststore.
+     */
+    protected KeyStore getTrustStore(String keystoreType,
+            String keystoreProvider) throws IOException {
+        KeyStore trustStore = null;
+
+        String truststorePassword = config.getSslTrustStorePassword();
+        if( truststorePassword == null ) {
+            truststorePassword = config.getSslKeyStorePass();
+        } else if (truststorePassword.equals("")) {
+            truststorePassword = null;
+        }
+        String truststoreType = config.getSslTrustStoreType();
+        if(truststoreType == null) {
+            truststoreType = keystoreType;
+        }
+        String truststoreProvider = config.getSslTrustStoreProvider();
+        if (truststoreProvider == null) {
+            truststoreProvider = keystoreProvider;
+        }
+
+        if (config.getSslTrustStore() != null){
+            trustStore = getStore(truststoreType, truststoreProvider,
+                    config.getSslTrustStore(), truststorePassword);
+        }
+
+        return trustStore;
+    }
+
+    /*
+     * Gets the key- or truststore with the specified type, path, and password.
+     */
+    private KeyStore getStore(String type, String provider, String path,
+            String pass) throws IOException {
+
+        KeyStore ks = null;
+        InputStream istream = null;
+        try {
+            if (provider == null) {
+                ks = KeyStore.getInstance(type);
+            } else {
+                ks = KeyStore.getInstance(type, provider);
+            }
+            if(!("PKCS11".equalsIgnoreCase(type) || "".equalsIgnoreCase(path))) {
+                File keyStoreFile = new File(path);
+                if (!keyStoreFile.isAbsolute()) {
+                    keyStoreFile = new File(System.getProperty("catalina.base"),
+                                            path);
+                }
+                istream = new FileInputStream(keyStoreFile);
+            }
+
+            if (pass == null)
+                ks.load(istream, null);
+            else
+                ks.load(istream, pass.toCharArray());
+        } catch (FileNotFoundException fnfe) {
+            log.error(sm.getString("jsse.keystore_load_failed", type, path,
+                    fnfe.getMessage()), fnfe);
+            throw fnfe;
+        } catch (IOException ioe) {
+            log.error(sm.getString("jsse.keystore_load_failed", type, path,
+                    ioe.getMessage()), ioe);
+            throw ioe;      
+        } catch(Exception ex) {
+            String msg = sm.getString("jsse.keystore_load_failed", type, path,
+                    ex.getMessage());
+            log.error(msg, ex);
+            throw new IOException(msg);
+        } finally {
+            if (istream != null) {
+                try {
+                    istream.close();
+                } catch (IOException ioe) {
+                    // Do nothing
+                }
+            }
+        }
+
+        return ks;
+    }
+
+    /**
+     * Reads the keystore and initializes the SSL socket factory.
+     */
+    void init() throws IOException {
+        try {
+
+            /**
+            String clientAuthStr = (String) attributes.get("clientauth");
+            if("true".equalsIgnoreCase(clientAuthStr) ||
+               "yes".equalsIgnoreCase(clientAuthStr)) {
+                requireClientAuth = true;
+            } else if("want".equalsIgnoreCase(clientAuthStr)) {
+                wantClientAuth = true;
+            }*/
+
+            // Create and init SSLContext
+            SSLContext context = SSLContext.getInstance(config.getSslProtocol()); 
+            context.init(getKeyManagers(config.getSslKeyStoreType(), 
+                    config.getSslKeyStoreProvider(),
+                    config.getSslCertificateEncodingAlgorithm(),
+                    config.getSslKeyAlias()),
+                    getTrustManagers(config.getSslKeyStoreType(), config.getSslKeyStoreProvider(),
+                            config.getSslTrustAlgorithm()),
+                    new SecureRandom());
+
+            // create proxy
+            sslProxy = context.getSocketFactory();
+
+            // Determine which cipher suites to enable
+            enabledCiphers = getEnabledCiphers(config.getSslCiphers(),
+                                               sslProxy.getSupportedCipherSuites());
+
+        } catch(Exception e) {
+            if( e instanceof IOException )
+                throw (IOException)e;
+            throw new IOException(e.getMessage());
+        }
+    }
+
+    /**
+     * Gets the initialized key managers.
+     */
+    protected KeyManager[] getKeyManagers(String keystoreType,
+                                          String keystoreProvider,
+                                          String algorithm,
+                                          String keyAlias)
+                throws Exception {
+
+        KeyManager[] kms = null;
+
+        KeyStore ks = getKeystore(keystoreType, keystoreProvider, config.getSslKeyStorePass());
+        if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
+            throw new IOException(sm.getString("jsse.alias_no_key_entry", keyAlias));
+        }
+
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
+        kmf.init(ks, config.getSslKeyStorePass().toCharArray());
+
+        kms = kmf.getKeyManagers();
+        if (keyAlias != null) {
+            if ("JKS".equals(keystoreType)) {
+                keyAlias = keyAlias.toLowerCase();
+            }
+            for(int i=0; i<kms.length; i++) {
+                kms[i] = new JSSEKeyManager((X509KeyManager)kms[i], keyAlias);
+            }
+        }
+
+        return kms;
+    }
+
+    /**
+     * Gets the intialized trust managers.
+     */
+    protected TrustManager[] getTrustManagers(String keystoreType,
+            String keystoreProvider, String algorithm)
+        throws Exception {        
+        TrustManager[] tms = null;
+        
+        KeyStore trustStore = getTrustStore(keystoreType, keystoreProvider);
+        if (trustStore != null) {
+            if (config.getSslCrlFile() == null) {
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+                tmf.init(trustStore);
+                tms = tmf.getTrustManagers();
+            } else {
+                TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
+                CertPathParameters params = getParameters(algorithm, config.getSslCrlFile(), trustStore);
+                ManagerFactoryParameters mfp = new CertPathTrustManagerParameters(params);
+                tmf.init(mfp);
+                tms = tmf.getTrustManagers();
+            }
+        }
+        
+        return tms;
+    }
+    
+    /**
+     * Return the initialization parameters for the TrustManager.
+     * Currently, only the default <code>PKIX</code> is supported.
+     * 
+     * @param algorithm The algorithm to get parameters for.
+     * @param crlf The path to the CRL file.
+     * @param trustStore The configured TrustStore.
+     * @return The parameters including the CRLs and TrustStore.
+     */
+    protected CertPathParameters getParameters(String algorithm, 
+                                                String crlf, 
+                                                KeyStore trustStore)
+        throws Exception {
+        CertPathParameters params = null;
+        if("PKIX".equalsIgnoreCase(algorithm)) {
+            PKIXBuilderParameters xparams = new PKIXBuilderParameters(trustStore, 
+                                                                     new X509CertSelector());
+            Collection<? extends CRL> crls = getCRLs(crlf);
+            CertStoreParameters csp = new CollectionCertStoreParameters(crls);
+            CertStore store = CertStore.getInstance("Collection", csp);
+            xparams.addCertStore(store);
+            xparams.setRevocationEnabled(true);
+            xparams.setMaxPathLength(config.getSslTrustMaxCertLength());
+
+            params = xparams;
+        } else {
+            throw new CRLException("CRLs not supported for type: "+algorithm);
+        }
+        return params;
+    }
+
+
+    /**
+     * Load the collection of CRLs.
+     * 
+     */
+    protected Collection<? extends CRL> getCRLs(String crlf) 
+        throws IOException, CRLException, CertificateException {
+
+        File crlFile = new File(crlf);
+        if( !crlFile.isAbsolute() ) {
+            crlFile = new File(System.getProperty("catalina.base"), crlf);
+        }
+        Collection<? extends CRL> crls = null;
+        InputStream is = null;
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            is = new FileInputStream(crlFile);
+            crls = cf.generateCRLs(is);
+        } catch(IOException iex) {
+            throw iex;
+        } catch(CRLException crle) {
+            throw crle;
+        } catch(CertificateException ce) {
+            throw ce;
+        } finally { 
+            if(is != null) {
+                try{
+                    is.close();
+                } catch(Exception ex) {
+                }
+            }
+        }
+        return crls;
+    }
+
+    /**
+     * Set the SSL protocol variants to be enabled.
+     * @param socket the SSLServerSocket.
+     * @param protocols the protocols to use.
+     */
+    protected void setEnabledProtocols(SSLSocket socket, String []protocols){
+        if (protocols != null) {
+            socket.setEnabledProtocols(protocols);
+        }
+    }
+
+    /**
+     * Determines the SSL protocol variants to be enabled.
+     *
+     * @param socket The socket to get supported list from.
+     * @param requestedProtocols Comma-separated list of requested SSL
+     * protocol variants
+     *
+     * @return Array of SSL protocol variants to be enabled, or null if none of
+     * the requested protocol variants are supported
+     */
+    protected String[] getEnabledProtocols(SSLSocket socket,
+                                           String requestedProtocols){
+        String[] supportedProtocols = socket.getSupportedProtocols();
+
+        String[] enabledProtocols = null;
+
+        if (requestedProtocols != null) {
+            Vector<String> vec = null;
+            String protocol = requestedProtocols;
+            int index = requestedProtocols.indexOf(',');
+            if (index != -1) {
+                int fromIndex = 0;
+                while (index != -1) {
+                    protocol = requestedProtocols.substring(fromIndex, index).trim();
+                    if (protocol.length() > 0) {
+                        /*
+                         * Check to see if the requested protocol is among the
+                         * supported protocols, i.e., may be enabled
+                         */
+                        for (int i=0; supportedProtocols != null
+                                     && i<supportedProtocols.length; i++) {
+                            if (supportedProtocols[i].equals(protocol)) {
+                                if (vec == null) {
+                                    vec = new Vector<String>();
+                                }
+                                vec.addElement(protocol);
+                                break;
+                            }
+                        }
+                    }
+                    fromIndex = index+1;
+                    index = requestedProtocols.indexOf(',', fromIndex);
+                } // while
+                protocol = requestedProtocols.substring(fromIndex);
+            }
+
+            if (protocol != null) {
+                protocol = protocol.trim();
+                if (protocol.length() > 0) {
+                    /*
+                     * Check to see if the requested protocol is among the
+                     * supported protocols, i.e., may be enabled
+                     */
+                    for (int i=0; supportedProtocols != null
+                                 && i<supportedProtocols.length; i++) {
+                        if (supportedProtocols[i].equals(protocol)) {
+                            if (vec == null) {
+                                vec = new Vector<String>();
+                            }
+                            vec.addElement(protocol);
+                            break;
+                        }
+                    }
+                }
+            }           
+
+            if (vec != null) {
+                enabledProtocols = new String[vec.size()];
+                vec.copyInto(enabledProtocols);
+            }
+        }
+
+        return enabledProtocols;
+    }
+
+    /**
+     * Configures the given SSL server socket with the requested cipher suites,
+     * protocol versions, and need for client authentication
+     */
+    private void initSocket(Socket ssocket) {
+
+        SSLSocket socket = (SSLSocket) ssocket;
+
+        if (enabledCiphers != null) {
+            socket.setEnabledCipherSuites(enabledCiphers);
+        }
+
+        setEnabledProtocols(socket, getEnabledProtocols(socket, 
+                                                         config.getSslProtocol()));
+
+        // we don't know if client auth is needed -
+        // after parsing the request we may re-handshake
+        //configureClientAuth(socket);
+    }
+
+}




More information about the jboss-cvs-commits mailing list