[jboss-cvs] JBossAS SVN: r105344 - in projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core: framework/server and 1 other directory.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Fri May 28 18:36:14 EDT 2010


Author: bstansberry at jboss.com
Date: 2010-05-28 18:36:13 -0400 (Fri, 28 May 2010)
New Revision: 105344

Added:
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ChannelInfo.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactory.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactoryMBean.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackConfigInfo.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackUtil.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AddressPort.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AsynchEventHandler.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeFactory.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeImpl.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/CoreGroupCommunicationService.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DelegatingStateTransferUpHandler.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImpl.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImplMBean.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/HAPartitionImpl.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxHandlerChannel.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxUpHandler.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/RspFilterAdapter.java
   projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/StateTransferFilter.java
Log:
Port classes from AS cluster/ module
Impl of HAPartition and superinterfaces based on MuxRpcDispatcher


Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ChannelInfo.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/ChannelInfo.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ChannelInfo.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ChannelInfo.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,111 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, 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.ha.core.channelfactory;
+
+import java.util.List;
+
+import javax.management.ObjectName;
+
+import org.jgroups.Address;
+import org.jgroups.Channel;
+import org.jgroups.View;
+import org.jgroups.conf.ProtocolData;
+
+/**
+ * Information describing an open JGroups Channel.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class ChannelInfo
+{
+   private final String id;
+   private final String stackName;
+   private final Channel channel;
+   private final ProtocolData[] protocolStackConfiguration;
+   private final ObjectName channelObjectName;
+   private final List<ObjectName> protocolObjectNames;
+   
+   public ChannelInfo(String id, String stackName, Channel channel, 
+         ProtocolData[] config, ObjectName channelObjectName, 
+         List<ObjectName> protocolObjectNames)
+   {
+      if (channel == null)
+      {
+         throw new IllegalArgumentException("null channel");
+      }
+      
+      this.id = id;
+      this.stackName = stackName;
+      this.channel = channel;
+      this.protocolStackConfiguration = config;
+      this.channelObjectName = channelObjectName;
+      this.protocolObjectNames = protocolObjectNames;
+   }
+   
+   public String getId()
+   {
+      return id;
+   }
+   
+   public String getClusterName()
+   {
+      return channel.getClusterName();
+   }
+   
+   public String getStackName()
+   {
+      return stackName;
+   }
+   
+   public Channel getChannel()
+   {
+      return channel;
+   }
+   
+   public ProtocolData[] getProtocolStackConfiguration()
+   {
+      return protocolStackConfiguration;
+   }
+   
+   public ObjectName getChannelObjectName()
+   {
+      return channelObjectName;
+   }
+   
+   public List<ObjectName> getProtocolObjectNames()
+   {
+      return protocolObjectNames;
+   } 
+   
+   public Address getLocalAddress()
+   {
+      return this.channel.getAddress();
+   }
+   
+   public View getCurrentView()
+   {
+      return this.channel.getView();
+   }
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactory.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/JChannelFactory.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactory.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactory.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,1724 @@
+/*
+ * 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.ha.core.channelfactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.rmi.dgc.VMID;
+import java.rmi.server.UID;
+import java.security.AccessController;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+import javax.management.MBeanRegistration;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.jboss.logging.Logger;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+import org.jgroups.Channel;
+import org.jgroups.ChannelException;
+import org.jgroups.ChannelListenerAdapter;
+import org.jgroups.Global;
+import org.jgroups.JChannel;
+import org.jgroups.conf.ConfiguratorFactory;
+import org.jgroups.conf.ProtocolData;
+import org.jgroups.conf.ProtocolParameter;
+import org.jgroups.conf.ProtocolStackConfigurator;
+import org.jgroups.jmx.JmxConfigurator;
+import org.jgroups.protocols.TP;
+import org.jgroups.stack.Protocol;
+import org.jgroups.stack.ProtocolStack;
+import org.jgroups.util.DefaultThreadFactory;
+import org.jgroups.util.LazyThreadFactory;
+import org.jgroups.util.ThreadDecorator;
+import org.jgroups.util.ThreadFactory;
+import org.jgroups.util.ThreadManager;
+import org.jgroups.util.Util;
+import org.w3c.dom.Element;
+
+/**
+ * Implementation of the JGroups <code>ChannelFactory</code> that supports a 
+ * number of JBoss AS-specific behaviors:
+ * <p>
+ * <ul>
+ * <li>Passing a config event to newly created channels containing 
+ * "additional_data" that will be associated with the JGroups 
+ * <code>IpAddress</code> for the peer. Used to provide logical addresses
+ * to cluster peers that remain consistent across channel and server restarts.</li>
+ * <li>Never returns instances of {@link org.jgroups.mux.MuxChannel} from
+ * the <code>createMultiplexerChannel</code> methods.  Instead always returns
+ * a channel with a shared transport protocol.</li>
+ * <li>Configures the channel's thread pools and thread factories to ensure
+ * that application thread context classloaders don't leak to the channel
+ * threads.</li>
+ * <li>Exposes a ProfileService ManagementView interface.</li>
+ * </ul>
+ * </p>
+ * 
+ * @author <a href="mailto://brian.stansberry@jboss.com">Brian Stansberry</a>
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * 
+ * @version $Revision$
+ */
+public class JChannelFactory
+      implements org.jgroups.ChannelFactory, JChannelFactoryMBean, MBeanRegistration
+{
+   private static final Logger log = Logger.getLogger(JChannelFactory.class);
+   
+   /** 
+    * Prefix prepended to the protocol stack name to create a synthetic
+    * transport protocol <code>singleton_name</code> value for channels
+    * that don't configure a <code>singleton_name</code>. 
+    */
+   public static final String UNSHARED_TRANSPORT_NAME_BASE = "unnamed_";
+   
+   /** Default value for property {@link #getDomain() domain}. */
+   public static final String DEFAULT_JMX_DOMAIN = "jgroups";
+
+   private static final String[] states = {
+      "Stopped", "Stopping", "Starting", "Started", "Failed",
+      "Destroyed", "Created", "Unregistered", "Registered"
+   };
+
+   /** The Service.stop has completed */
+   private static final int STOPPED  = 0;
+   /** The Service.stop has been invoked */
+   private static final int STOPPING = 1;
+   /** The Service.start has been invoked */
+   private static final int STARTING = 2;
+   /** The Service.start has completed */
+   private static final int STARTED  = 3;
+   /** There has been an error during some operation */
+   private static final int FAILED  = 4;
+   /** The Service.destroy has completed */
+   private static final int DESTROYED = 5;
+   /** The Service.create has completed */
+   private static final int CREATED = 6;
+   /** The MBean has been created but has not completed MBeanRegistration.postRegister */
+   private static final int UNREGISTERED = 7;
+   /** The MBean has been created and has completed MBeanRegistration.postRegister */
+   private static final int REGISTERED = 8;
+
+   private static final String VALUE_BIND_ADDRESS_ANY = "0.0.0.0";
+   
+   private InetAddress nodeAddress;
+   private String nodeName;
+   private int namingServicePort = -1;
+   private int state = UNREGISTERED;
+   private boolean assignLogicalAddresses = true;
+   private boolean manageNewThreadClassLoader = true;
+   private boolean manageReleasedThreadClassLoader = false;
+   private boolean addMissingSingletonName = true;
+   private final ContextClassLoaderSwitcher classLoaderSwitcher;
+   private final Map<Channel, ChannelInfo> registeredChannels = 
+      new ConcurrentHashMap<Channel, ChannelInfo>(16, 0.75f, 2);
+   
+   private ChannelCloseListener closeListener = new ChannelCloseListener();
+
+   /**
+    * Map<String,ProtocolStackConfigurator>. Hashmap which maps stack names to JGroups
+    * configurations. Keys are stack names, values are plain JGroups stack
+    * configs. This is (re-)populated whenever a setMultiplexerConfig() method
+    * is called
+    */
+   private final Map<String,ProtocolStackConfigInfo> stacks = 
+      new ConcurrentHashMap<String, ProtocolStackConfigInfo>(16, 0.75f, 2);
+
+   /** 
+    * Placeholder for stacks injected via {@link #setProtocolStackConfigurations(Map)}
+    * until createService is called.
+    */
+   private Map<String,ProtocolStackConfigInfo> injectedStacks;
+   
+   /**
+    * The MBeanServer to expose JMX management data with (no management data
+    * will be available if null)
+    */
+   private MBeanServer server = null;
+
+   /** To expose the channels and protocols */
+   private String domain = DEFAULT_JMX_DOMAIN;
+   private boolean domainSet = false;
+
+   /** Whether or not to expose channels via JMX */
+   private boolean expose_channels=true;
+
+   /** Whether to expose the factory only, or all protocols as well */
+   private boolean expose_protocols=true;
+
+   /**
+    * Creates a new JChannelFactory.
+    */
+   @SuppressWarnings("unchecked")
+   public JChannelFactory()
+   {
+      this.classLoaderSwitcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+   }   
+
+   /**
+    * Always throws <code>ChannelException</code>; this method is not supported.
+    */   
+   public Channel createChannel() throws ChannelException
+   {
+      throw new ChannelException("No-arg createChannel() is not supported");
+   }
+
+   /**
+    * Creates a channel by passing <code>properties</code> to the 
+    * <code>org.jgroups.JChannel</code> constructor.
+    * 
+    * @param properties protocol stack configuration object; can be <code>null</code>
+    *                   in which case a default stack will be used
+    * 
+    * @return the channel
+    */
+   public Channel createChannel(Object properties) throws ChannelException
+   {
+      checkStarted();
+
+      if (properties == null)
+         properties = JChannel.DEFAULT_PROTOCOL_STACK;
+
+      ProtocolStackConfigurator config = null;
+
+      try
+      {
+         @SuppressWarnings("deprecation")
+         ProtocolStackConfigurator c = ConfiguratorFactory.getStackConfigurator(properties);
+         config = c;
+      }
+      catch (Exception x)
+      {
+         throw new ChannelException("unable to load protocol stack", x);
+      }
+
+      JChannel channel = initializeChannel(config, null, false);
+
+      try
+      {
+         registerChannel(channel, null, null, ProtocolStackUtil.getProtocolData(config));
+      }
+      catch (ChannelException ce)
+      {
+         throw ce;
+      }
+      catch (Exception e)
+      {
+         throw new ChannelException("unable to register channel", e);
+      }
+
+      return channel;
+   }
+
+   /**
+    * Create a {@link Channel} using the specified stack. Channel will use a 
+    * shared transport.
+    * 
+    * @param stack_name 
+    *            The name of the stack to be used. All stacks are defined in
+    *            the configuration with which the factory is configured (see
+    *            {@link #setMultiplexerConfig(Object)} for example. If
+    *            clients attempt to create a Channel for an undefined stack 
+    *            name an exception will be thrown.
+    * 
+    * @return an implementation of Channel configured with a shared transport.
+    * 
+    * @throws IllegalArgumentException if <code>stack_name</code> is 
+    * <code>null</code> or {@link #getConfig(String)} returns <code>null</code>
+    * when <code>stack_name</code> is used.
+    * 
+    * @throws Exception
+    */
+   public Channel createChannel(String stack_name) throws Exception
+   {
+      return createChannelFromRegisteredStack(stack_name, null, false);
+   }
+   
+   /**
+    * Creates and returns a shared transport Channel configured with the specified 
+    * {@link #getConfig(String) protocol stack configuration}.
+    * <p>
+    * <emphasis>NOTE:</emphasis> The implementation of this method is somewhat
+    * different from what is described in 
+    * {@link org.jgroups.ChannelFactory#createMultiplexerChannel(String, String)}.
+    * The returned channel will not be an instance of 
+    * <code>org.jgroups.mux.MuxChannel</code>; rather a channel that uses a
+    * shared transport will be returned.  This will be the case whether or
+    * not the protocol stack specified by <code>stack_name</code> includes
+    * a <code>singleton_name</code> attribute in its 
+    * {@link org.jgroups.protocols.TP transport protocol} configuration. If no 
+    * <code>singleton_name</code> attribute is present, this factory will create
+    * a synthetic one by prepending "unnamed_" to the provided
+    * <code>id</code> param and will use that for the returned channel's 
+    * transport protocol. (Note this will not effect the protocol stack
+    * configuration named by <code>stack_name</code>; i.e. another request
+    * that passes the same <code>stack_name</code> will not inherit the
+    * synthetic singleton name.) 
+    * 
+    * @param stack_name
+    *            The name of the stack to be used. All stacks are defined in
+    *            the configuration with which the factory is configured (see
+    *            {@link #setMultiplexerConfig(Object)} for example. If
+    *            clients attempt to create a Channel for an undefined stack 
+    *            name an Exception will be thrown.
+    * @param id  Only used if {@link #isExposeChannels()} returns <code>true</code>,
+    *            in which case, if not <code>null</code>, is used as part of
+    *            the <code>ObjectName</code> for the JMX mbeans that represent
+    *            the channel and its protocols. Can be <code>null</code>.
+    *            
+    * @return an implementation of Channel configured with a shared transport.
+    *         
+    * @throws IllegalStateException if the specified protocol stack does not
+    *                               declare a <code>singleton_name</code> and
+    *                               {@link #getAddMissingSingletonName()} returns
+    *                               <code>false</code>.
+    * @throws ChannelException
+    */
+   public Channel createMultiplexerChannel(String stack_name, String id) throws Exception
+   {
+      return createChannelFromRegisteredStack(stack_name, id, true);
+   }  
+   
+   /**
+    * Creates and returns a shared transport Channel configured with the specified 
+    * {@link #getConfig(String) protocol stack configuration}.
+    * 
+    * See {@link #createMultiplexerChannel(String, String)}; the additional
+    * attributes specified in this overloaded version of that method are ignored.
+    *
+    * @param register_for_state_transfer ignored in JBoss AS. Treated as <code>false</code>.
+    * 
+    * @param substate_id ignored in JBoss AS
+    *            
+    * @return An implementation of Channel configured with a shared transport.
+    *         
+    *         
+    * @throws IllegalStateException if the specified protocol stack does not
+    *                               declare a <code>singleton_name</code> and
+    *                               {@link #getAddMissingSingletonName()} returns
+    *                               <code>false</code>.
+    * @throws ChannelException
+    */
+   public Channel createMultiplexerChannel(String stack_name, String id, boolean register_for_state_transfer, String substate_id) throws Exception
+   {
+      return createMultiplexerChannel(stack_name, id);
+   }
+   
+   /**
+    * {@link #parse(Element) Parses <code>properties</code>} and then adds
+    * the resulting protocol stack configurations to the set available for use.
+    * Same as 
+    * {@link #setMultiplexerConfig(Element, boolean) <code>setMultiplexerConfig(properties, true</code>}.
+    * 
+    * @param properties document root node for XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    */
+   public void setMultiplexerConfig(Element properties) throws Exception
+   {
+      setMultiplexerConfig(properties, true);
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use. Same as 
+    * {@link #setMultiplexerConfig(File, boolean) <code>setMultiplexerConfig(properties, true</code>}.
+    * 
+    * @param properties file which must contain XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    */
+   public void setMultiplexerConfig(File properties) throws Exception
+   {
+      setMultiplexerConfig(properties, true);      
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use. Same as 
+    * {@link #setMultiplexerConfig(Object, boolean) <code>setMultiplexerConfig(properties, true</code>}.
+    * 
+    * @param properties object that can be {@link ConfiguratorFactory#getConfigStream(Object) converted into a stream}
+    *                   which must contain XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    */
+   public void setMultiplexerConfig(Object properties) throws Exception
+   {
+      setMultiplexerConfig(properties, true);
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use. Same as 
+    * {@link #setMultiplexerConfig(String, boolean) <code>setMultiplexerConfig(properties, true</code>}.
+    * 
+    * @param properties string that can be {@link ConfiguratorFactory#getConfigStream(String) converted into a stream}
+    *                   which must contain XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    */
+   public void setMultiplexerConfig(String properties) throws Exception
+   {
+      setMultiplexerConfig(properties, true);
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use. Same as 
+    * {@link #setMultiplexerConfig(URL, boolean) <code>setMultiplexerConfig(properties, true</code>}.
+    * 
+    * @param properties URL which must contain XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    */
+   public void setMultiplexerConfig(URL properties) throws Exception
+   {
+      setMultiplexerConfig(properties, true);
+   }
+
+   // -------------------------------------------------------------  Properties
+
+   /**
+    * Gets the MBeanServer to use to register mbeans for channels and protocols
+    * we create.
+    * 
+    * @return the MBeanServer, or <code>null</code> if one isn't registered
+    */
+   public MBeanServer getServer() 
+   {
+       return server;
+   }
+
+   /**
+    * Sets the MBeanServer to use to register mbeans for channels and protocols
+    * we create.
+    * 
+    * @param server the MBeanServer. May be <code>null</code>
+    */
+   public void setServer(MBeanServer server) 
+   {
+       this.server=server;
+   }
+
+   /**
+    * Gets the domain portion of the JMX ObjectName to use when registering channels and protocols
+    * 
+    * @return the domain. Will not return <code>null</code> after {@link #create()}
+    *         has been invoked.
+    */
+   public String getDomain() 
+   {
+       return domain == null ? "jgroups" : domain;
+   }
+   
+   public void setDomain(String domain)
+   {
+      this.domain = domain;
+      this.domainSet = true;
+   }
+
+   public boolean isExposeChannels() 
+   {
+       return expose_channels;
+   }
+
+   public void setExposeChannels(boolean expose_channels) 
+   {
+       this.expose_channels=expose_channels;
+   }
+
+   public boolean isExposeProtocols() 
+   {
+       return expose_protocols;
+   }
+
+   public void setExposeProtocols(boolean expose_protocols) 
+   {      
+       this.expose_protocols=expose_protocols;
+       if (expose_protocols)
+          this.expose_channels=true;
+   }
+
+   /**
+    * Get any logical name assigned to this server; if not null this value
+    * will be the value of the 
+    * {@link #setAssignLogicalAddresses(boolean) logical address} assigned
+    * to the channels this factory creates.
+    * 
+    * @return the logical name for this server, or <code>null</code>.
+    */
+   public String getNodeName()
+   {
+      return nodeName;
+   }
+
+   /**
+    * Sets the logical name assigned to this server; if not null this value
+    * will be the value of the 
+    * {@link #setAssignLogicalAddresses(boolean) logical address} assigned
+    * to the channels this factory creates.
+    * 
+    * @param nodeName the logical name for this server, or <code>null</code>.
+    */
+   public void setNodeName(String nodeName)
+   {
+      this.nodeName = nodeName;
+   }
+   
+   /**
+    * Gets the address to which this server is bound; typically the value
+    * passed to <code>-b</code> when JBoss is started. Used in combination 
+    * with {@link #getNamingServicePort() the naming service port} to create
+    * a logical name for this server if no {@link #SetNodeName(String) node name}
+    * is specified.
+    * 
+    * @return the address to which this server is bound, or <code>null</code>
+    *         if not set
+    */
+   public InetAddress getNodeAddress()
+   {
+      return nodeAddress;
+   }
+   
+   /**
+    * Sets the address to which this server is bound; typically the value
+    * passed to <code>-b</code> when JBoss is started. Used in combination 
+    * with {@link #getNamingServicePort() the naming service port} to create
+    * a logical name for this server if no {@link #SetNodeName(String) node name}
+    * is specified.
+    * 
+    * @param nodeAddress the address to which this server is bound, 
+    *                    or <code>null</code>
+    */
+   public void setNodeAddress(InetAddress nodeAddress)
+   {
+      this.nodeAddress = nodeAddress;
+   }
+
+   /**
+    * Gets the port on which this server's naming service is listening. Used in 
+    * combination with {@link #getNodeAddress() the server bind address} to create
+    * a logical name for this server if no {@link #SetNodeName(String) node name}
+    * is specified.
+    * 
+    * @return the port on which JNDI is listening, or <code>-1</code> if not set.
+    */
+   public int getNamingServicePort()
+   {
+      return namingServicePort;
+   }
+
+   /**
+    * Sets the port on which this server's naming service is listening. Used in 
+    * combination with {@link #getNodeAddress() the server bind address} to create
+    * a logical name for this server if no {@link #SetNodeName(String) node name}
+    * is specified.
+    * 
+    * @param jndiPort the port on which JNDI is listening.
+    */
+   public void setNamingServicePort(int jndiPort)
+   {
+      this.namingServicePort = jndiPort;
+   }
+   
+   /**
+    * Gets whether this factory should create a "logical address" (or use
+    * one set via {@link #setNodeName(String)} and assign it to
+    * any newly created <code>Channel</code> as JGroups "additional_data".
+    * 
+    * @see #setAssignLogicalAddresses(boolean)
+    */
+   public boolean getAssignLogicalAddresses()
+   {
+      return assignLogicalAddresses;
+   }
+
+   /**
+    * Sets whether this factory should create a "logical address" (or use
+    * one set via {@link #setNodeName(String)} and assign it to
+    * any newly created <code>Channel</code> as JGroups "additional_data".
+    * <p>
+    * Any such logical address will be used by <code>HAPartition</code>
+    * to assign a name to the <code>ClusterNode</code> object representing 
+    * this node. If a logical address is not set, the <code>ClusterNode</code> 
+    * will use the address and port JGroups is using to receive messages to
+    * create its name.
+    * </p>
+    * <p>
+    * Default is <code>true</code>.
+    * </p>
+    */
+   public void setAssignLogicalAddresses(boolean logicalAddresses)
+   {
+      this.assignLogicalAddresses = logicalAddresses;
+   }
+
+   /**
+    * Gets whether this factory should update the standard JGroups
+    * thread factories to ensure application classloaders do not leak to 
+    * newly created channel threads.
+    * 
+    * @return <code>true</code> if the factories should be updated.
+    *         Default is <code>true</code>.
+    */
+   public boolean getManageNewThreadClassLoader()
+   {
+      return manageNewThreadClassLoader;
+   }
+
+   /**
+    * Sets whether this factory should update the standard JGroups
+    * thread factories to ensure application classloaders do not leak to 
+    * newly created channel threads. This should only be set to <code>false</code>
+    * if a JGroups release is used that itself prevents such classloader leaks.
+    * 
+    * @param manage <code>true</code> if the factories should be updated.
+    */
+   public void setManageNewThreadClassLoader(boolean manage)
+   {
+      this.manageNewThreadClassLoader = manage;
+   }
+
+   /**
+    * Gets whether this factory should update the standard JGroups
+    * thread pools to ensure application classloaders have not leaked to 
+    * threads returned to the pool.
+    * 
+    * @return <code>true</code> if the pools should be updated.
+    *         Default is <code>false</code>.
+    */
+   public boolean getManageReleasedThreadClassLoader()
+   {
+      return manageReleasedThreadClassLoader;
+   }
+
+   /**
+    * Sets whether this factory should update the standard JGroups
+    * thread pools to ensure application classloaders have not leaked to 
+    * threads returned to the pool.
+    * <p>
+    * There is a small performance cost to enabling this, and applications
+    * can prevent any need to enable it by properly restoring the thread
+    * context classloader if they change it.  Therefore, by default this
+    * is set to <code>false</code>.
+    * </p>
+    * 
+    * @param manage <code>true</code> if the factories should be updated.
+    */
+   public void setManageReleasedThreadClassLoader(boolean manage)
+   {
+      this.manageReleasedThreadClassLoader = manage;
+   }
+
+   /**
+    * Gets whether {@link #createMultiplexerChannel(String, String)} should 
+    * create a synthetic singleton name attribute for a channel's transport
+    * protocol if one isn't configured.  If this is <code>false</code> and
+    * no <code>singleton_name</code> is configured, 
+    * {@link #createMultiplexerChannel(String, String)} will throw an
+    * <code>IllegalStateException</code>. 
+    * 
+    * @return <code>true</code> if synthetic singleton names should be created.
+    *         Default is <code>true</code>.
+    */
+   public boolean getAddMissingSingletonName()
+   {
+      return addMissingSingletonName;
+   }
+
+   /**
+    * Sets whether {@link #createMultiplexerChannel(String, String)} should 
+    * create a synthetic singleton name attribute for a channel's transport
+    * protocol if one isn't configured.
+    * 
+    * @param addMissingSingletonName <code>true</code> if synthetic singleton 
+    *                                names should be created.
+    */
+   public void setAddMissingSingletonName(boolean addMissingSingletonName)
+   {
+      this.addMissingSingletonName = addMissingSingletonName;
+   }
+   
+   // -------------------------------------------------------------  Public
+
+   /**
+    * {@link #parse(Element) Parses <code>properties</code>} and then adds
+    * the resulting protocol stack configurations to the set available for use.
+    * 
+    * @param properties document root node for XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    * @param replace <code>true</code> if a configuration with the same
+    *                stack name as an already registered configuration should
+    *                replace that configuration; <code>false</code> if it
+    *                should be discarded.
+    */
+   public void setMultiplexerConfig(Element properties, boolean replace) throws Exception
+   {
+      Map<String, ProtocolStackConfigInfo> map = ProtocolStackUtil.parse(properties);
+      
+      for (Map.Entry<String, ProtocolStackConfigInfo> entry : map.entrySet())
+      {
+         addConfig(entry.getKey(), entry.getValue(), replace);
+      }
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use.
+    * 
+    * @param properties file which must contain XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    * @param replace <code>true</code> if a configuration with the same
+    *                stack name as an already registered configuration should
+    *                replace that configuration; <code>false</code> if it
+    *                should be discarded.
+    */
+   public void setMultiplexerConfig(File properties, boolean replace) throws Exception
+   {
+      InputStream input=ConfiguratorFactory.getConfigStream(properties);      
+      addConfigs(input, properties.toString(), replace);
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use. 
+    * 
+    * @param properties object that can be {@link ConfiguratorFactory#getConfigStream(Object) converted into a stream}
+    *                   which must contain XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    * @param replace <code>true</code> if a configuration with the same
+    *                stack name as an already registered configuration should
+    *                replace that configuration; <code>false</code> if it
+    *                should be discarded.
+    */
+   public void setMultiplexerConfig(Object properties, boolean replace) throws Exception
+   {
+      InputStream input=ConfiguratorFactory.getConfigStream(properties);
+      addConfigs(input, properties.toString(), replace);   
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use.
+    * 
+    * @param properties string that can be {@link ConfiguratorFactory#getConfigStream(String) converted into a stream}
+    *                   which must contain XML content in the JGroups 
+    *                   <code>stacks.xml</code> format
+    * @param replace <code>true</code> if a configuration with the same
+    *                stack name as an already registered configuration should
+    *                replace that configuration; <code>false</code> if it
+    *                should be discarded.
+    */
+   public void setMultiplexerConfig(String properties, boolean replace) throws Exception
+   {
+      InputStream input=ConfiguratorFactory.getConfigStream(properties);      
+      addConfigs(input, properties, replace);
+   }
+
+   /**
+    * {@link #parse(InputStream) Parses} an input stream created from 
+    * <code>properties</code> and then adds the resulting protocol stack 
+    * configurations to the set available for use.
+    * 
+    * @param properties URL which must contain XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    * @param replace <code>true</code> if a configuration with the same
+    *                stack name as an already registered configuration should
+    *                replace that configuration; <code>false</code> if it
+    *                should be discarded.
+    */
+   public void setMultiplexerConfig(URL url, boolean replace) throws Exception
+   {
+      InputStream input=ConfiguratorFactory.getConfigStream(url);      
+      addConfigs(input, url.toString(), replace);
+   }
+   
+
+   // --------------------------------------------------------  Management View
+   
+   /**
+    * Gets information on channels created by this factory that are currently
+    * open.
+    */
+   public Set<ChannelInfo> getOpenChannels()
+   {
+      return new HashSet<ChannelInfo>(registeredChannels.values());
+   }
+   
+   public Map<String, ProtocolStackConfigInfo> getProtocolStackConfigurations()
+   {
+      return Collections.unmodifiableMap(stacks);
+   }
+   
+   public void setProtocolStackConfigurations(Map<String, ProtocolStackConfigInfo> configs)
+   {
+      this.injectedStacks = configs;      
+      
+      if (state == STARTED)
+      {
+         // We're already running so this must be a ManagedComponent update
+         // so apply immediately
+         processInjectedStacks();
+      }
+   }
+
+   // ---------------------------------------------------  JChannelFactoryMBean
+
+   /**
+    * {@inheritDoc}
+    */
+   public void clearConfigurations()
+   {
+      this.stacks.clear();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String dumpChannels()
+   {
+      return "";
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String dumpConfiguration()
+   {
+      return stacks.keySet().toString();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String getConfig(String stack_name) throws Exception
+   {
+      ProtocolStackConfigInfo cfg = stacks.get(stack_name);
+      if (cfg == null)
+         throw new Exception("stack \"" + stack_name + "\" not found in " + stacks.keySet());
+      return cfg.getConfigurator().getProtocolStackString();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public String getMultiplexerConfig()
+   {
+      StringBuilder sb = new StringBuilder();
+      for (Map.Entry<String, ProtocolStackConfigInfo> entry : stacks.entrySet())
+      {
+         sb.append(entry.getKey()).append(": ").append(entry.getValue().getConfigurator().getProtocolStackString()).append("\n");
+      }
+      return sb.toString();
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public boolean removeConfig(String stack_name)
+   {
+      return stack_name != null && this.stacks.remove(stack_name) != null;
+   }
+   
+   // -------------------------------------------------------------  Lifecycle
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * This method largely directly concerns itself with the {@link #getStateString() state}
+    * field, delegating the real work to {@link #createService()}.
+    * </p>
+    */
+   public void create() throws Exception
+   {
+
+      if (state == CREATED || state == STARTING || state == STARTED
+         || state == STOPPING || state == STOPPED)
+      {
+         log.debug("Ignoring create call; current state is " + getStateString());
+         return;
+      }
+      
+      log.debug("Creating JChannelFactory");
+      
+      try
+      {
+         createService();
+         state = CREATED;
+      }
+      catch (Exception e)
+      {
+         log.debug("Initialization failed JChannelFactory", e);
+         throw e;
+      }
+      
+      log.debug("Created JChannelFactory");
+   }
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * This method largely directly concerns itself with the {@link #getStateString() state}
+    * field, delegating the real work to {@link #startService()}.
+    * </p>
+    */
+   public void start() throws Exception
+   {
+      if (state == STARTING || state == STARTED || state == STOPPING)
+      {
+         log.debug("Ignoring start call; current state is " + getStateString());
+         return;
+      }
+      
+      if (state != CREATED && state != STOPPED && state != FAILED)
+      {
+         log.debug("Start requested before create, calling create now");         
+         create();
+      }
+      
+      state = STARTING;
+      log.debug("Starting JChannelFactory");
+
+      try
+      {
+         startService();
+      }
+      catch (Exception e)
+      {
+         state = FAILED;
+         log.debug("Starting failed JChannelFactory", e);
+         throw e;
+      }
+
+      state = STARTED;
+      log.debug("Started JChannelFactory");
+      
+   }
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * This method largely directly concerns itself with the {@link #getStateString() state}
+    * field, delegating the real work to {@link #stopService()}.
+    * </p>
+    */
+   public void stop()
+   {
+      if (state != STARTED)
+      {
+         log.debug("Ignoring stop call; current state is " + getStateString());
+         return;
+      }
+      
+      state = STOPPING;
+      log.debug("Stopping JChannelFactory");
+
+      try
+      {
+         stopService();
+      }
+      catch (Throwable e)
+      {
+         state = FAILED;
+         log.warn("Stopping failed JChannelFactory", e);
+         return;
+      }
+      
+      state = STOPPED;
+      log.debug("Stopped JChannelFactory");
+   }
+
+   /**
+    * {@inheritDoc}
+    * <p>
+    * This method largely directly concerns itself with the {@link #getStateString() state}
+    * field, delegating the real work to {@link #destroyService()}.
+    * </p>
+    */
+   public void destroy()
+   {
+      if (state == DESTROYED)
+      {
+         log.debug("Ignoring destroy call; current state is " + getStateString());
+         return;
+      }
+      
+      if (state == STARTED)
+      {
+         log.debug("Destroy requested before stop, calling stop now");
+         stop();
+      }
+      
+      log.debug("Destroying JChannelFactory");
+      
+      try
+      {         
+         destroyService();
+      }
+      catch (Throwable t)
+      {
+         log.warn("Destroying failed JChannelFactory", t);
+      }
+      state = DESTROYED;
+      log.debug("Destroyed JChannelFactory");
+   }
+
+   // ------------------------------------------------------- MBeanRegistration
+
+   public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception
+   {
+      setServer(server);
+      if (!this.domainSet || this.domain == null)
+      {
+         setDomain(name.getDomain());
+      }
+      return name;
+   }
+
+   public void postRegister(Boolean registrationDone)
+   {
+      if (registrationDone != null && registrationDone.booleanValue()
+            && state == UNREGISTERED)
+      {
+         state = REGISTERED;
+      }
+   }
+
+   public void preDeregister() throws Exception
+   { 
+   }
+
+   public void postDeregister()
+   { 
+      setServer(null);
+      if (state == DESTROYED)
+         state = UNREGISTERED;
+   }
+
+   // --------------------------------------------------------------- Protected
+   
+   /**
+    * Gets the classloader that channel threads should be set to if
+    * {@link #getManageNewThreadClassloader()} or {@link #getManageReleasedThreadClassLoader()}
+    * are <code>true</code>.
+    * <p>
+    * This implementation returns this class' classloader.
+    * 
+    * @return the classloader.
+    */
+   protected ClassLoader getDefaultChannelThreadContextClassLoader()
+   {
+      return getClass().getClassLoader();
+   }
+
+   protected void createService() throws Exception
+   {
+      if(expose_channels) 
+      {
+         if(server == null)
+         {
+            throw new Exception("No MBeanServer found; JChannelFactory needs to " +
+            		"be run with an MBeanServer present, or with ExposeChannels " +
+            		"set to false");
+         }
+         
+         if(domain == null)
+         {
+            domain= DEFAULT_JMX_DOMAIN;
+         }
+      }
+   }
+
+   /**
+    * The actual startup work.
+    * 
+    * @throws Exception
+    */
+   protected void startService() throws Exception
+   {
+      // If the ProfileService injected stacks, process them now
+      processInjectedStacks();
+   }
+
+   /**
+    * The actual service stop work. This base implementation does nothing.
+    * 
+    * @throws Exception
+    */
+   protected void stopService() throws Exception
+   {
+      // no-op
+   }
+
+   /**
+    * The actual service destruction work.
+    *
+    */
+   protected void destroyService()
+   {  
+      for (Channel ch : registeredChannels.keySet())
+      {
+         unregisterChannel(ch);
+      }
+   }
+
+   // ----------------------------------------------------------------- Private
+
+
+   private void checkStarted()
+   {
+      if (state != STARTED)
+         throw new IllegalStateException("Cannot use factory; state is " + getStateString());
+   }
+   
+   private void addConfigs(InputStream input, String source, boolean replace) throws Exception
+   {
+      if(input == null)
+      {
+         throw new FileNotFoundException(source);
+      }
+      
+      Map<String, ProtocolStackConfigInfo> map = null;
+      try 
+      {
+          map = ProtocolStackUtil.parse(input);
+      }
+      catch(Exception ex) 
+      {
+          throw new Exception("failed parsing " + source, ex);
+      }
+      finally 
+      {
+          Util.close(input);
+      }
+      
+      for (Map.Entry<String, ProtocolStackConfigInfo> entry : map.entrySet())
+      {
+         addConfig(entry.getKey(), entry.getValue(), replace);
+      }
+   }
+
+   private boolean addConfig(String st_name, ProtocolStackConfigInfo val, boolean replace)
+   {
+      boolean added = replace;
+      if (replace)
+      {
+         stacks.put(st_name, val);
+         if (log.isTraceEnabled())
+            log.trace("added config '" + st_name + "'");
+      }
+      else
+      {
+         if (!stacks.containsKey(st_name))
+         {
+            stacks.put(st_name, val);
+            if (log.isTraceEnabled())
+               log.trace("added config '" + st_name + "'");
+            added = true;
+         }
+         else
+         {
+            if (log.isTraceEnabled())
+               log.trace("didn't add config '" + st_name + " because one of the same name already existed");
+         }
+      }
+      return added;      
+   }
+   
+   private synchronized void processInjectedStacks()
+   {
+      if (injectedStacks != null)
+      {
+         clearConfigurations();
+         stacks.putAll(injectedStacks);
+         injectedStacks = null;
+      }      
+   }
+
+   /**
+    * Creates a channel from one of the known stack configurations.
+    * 
+    * @param stack_name the name of the stack config
+    * @param id optional id for the channel
+    * @param forceSingletonStack <code>true</code> if a singleton_name must be
+    *             either configured, or addMissingSingletonName must be true
+    *             
+    * @return the channel
+    * 
+    * @throws IllegalArgumentException if stack_name is unknown
+    * @throws IllegalStateException if forceSingletonStack is <code>true</code>
+    *                               but a singleton_name couldn't be configured
+    *           
+    * @throws Exception
+    */
+   private Channel createChannelFromRegisteredStack(String stack_name, String id, boolean forceSingletonStack) throws Exception
+   {
+      checkStarted();
+      
+      ProtocolStackConfigInfo config = stacks.get(stack_name);
+      
+      if (config == null)
+         throw new IllegalArgumentException("Unknown stack_name " + stack_name);
+      
+      JChannel channel = initializeChannel(config.getConfigurator(), stack_name, forceSingletonStack);
+      
+      registerChannel(channel, id, stack_name, ProtocolStackUtil.getProtocolData(config.getConfigurator()));      
+      
+      return channel;
+   }
+
+   /**
+    * Construct a JChannel from the given config and then do post-construction
+    * processing like fixing up thread managment or setting a unique id.
+    * 
+    * @param config the config
+    * 
+    * @return the channel
+    * 
+    * @throws ChannelException
+    */
+   private JChannel initializeChannel(ProtocolStackConfigurator config, String stack_name,
+         boolean forceSingletonStack) throws ChannelException
+   {  
+      Map<String, String> tpProps = getTransportProperties(config);
+   
+      if (!tpProps.containsKey(Global.SINGLETON_NAME))
+      {
+         if (addMissingSingletonName && stack_name != null)
+         {
+            String singletonName = UNSHARED_TRANSPORT_NAME_BASE + stack_name;
+            
+            log.warn("Config for " + stack_name + " does not include " +
+                      "singleton_name; adding a name of " + singletonName +
+                      ". You should configure a singleton_name for this stack.");
+            
+            config = addSingletonName(config, singletonName);
+            log.debug("Stack config after adding singleton_name is " + config.getProtocolStackString());
+            tpProps = getTransportProperties(config);                       
+         }
+         else if (forceSingletonStack)
+         {
+            throw new IllegalStateException("Config for " + stack_name + " does not include " +
+                      "singleton_name and MuxChannels are not supported.");
+         }
+      }
+      JChannel channel = new JChannel(config);
+      
+      if (manageNewThreadClassLoader || manageReleasedThreadClassLoader)
+      {
+         fixChannelThreadManagement(channel);
+      }
+      
+      if (assignLogicalAddresses)
+      {
+         setChannelUniqueId(channel);
+      }
+      
+      return channel;
+   }
+   
+   /**
+    * Gets the current runtime lifecycle state (e.g. CREATED, STARTED).
+    */
+   private String getStateString()
+   {
+      return states[state];
+   }
+   
+   private void setChannelUniqueId(Channel channel)
+   {
+      String logical_address_name = channel.getName();
+      if (logical_address_name == null)
+      {
+         // We push the independent name in the protocol stack before connecting to the cluster
+         if (this.nodeName == null || "".equals(this.nodeName)) {
+            this.nodeName = generateUniqueNodeName();
+         }
+         
+         log.debug("Assigning unique node id " + nodeName + " to the channel");
+         
+         channel.setName(this.nodeName);        
+      }    
+   }
+
+   private synchronized String generateUniqueNodeName ()
+   {
+      // we first try to find a simple meaningful name:
+      // 1st) "local-IP:JNDI_PORT" if JNDI is running on this machine
+      // 2nd) "local-IP:JMV_GUID" otherwise
+      // 3rd) return a fully GUID-based representation
+      //
+
+      // Before anything we determine the local host IP (and NOT name as this could be
+      // resolved differently by other nodes...)
+
+      // But use the specified node address for multi-homing
+
+      String hostIP = null;
+      InetAddress address = fixRemoteAddress(nodeAddress);
+      if (address == null)
+      {
+         log.debug ("unable to create a GUID for this cluster, check network configuration is correctly setup (getLocalHost has returned an exception)");
+         log.debug ("using a full GUID strategy");
+         return new VMID().toString();
+      }
+      else
+      {
+         hostIP = getFastHostName(address);
+      }
+
+      // 1st: is JNDI up and running?
+      //
+      if (namingServicePort > 0)
+      {
+         // we can proceed with the JNDI trick!
+         return hostIP + ":" + namingServicePort;
+      }
+      else
+      {
+         log.warn("JNDI has been found but the service wasn't started. Most likely, " +
+         		    "HAPartition bean is missing dependency on JBoss Naming. " +
+         		    "Instead using host based UID strategy for defining a node " +
+         		    "GUID for the cluster.");
+      }
+
+      // 2nd: host-GUID strategy
+      //
+      String uid = new UID().toString();
+      return hostIP + ":" + uid;
+   }
+   
+   /**
+    * Tries to determine the hostname of the given InetAddress without 
+    * triggering a reverse DNS lookup.  Tries to parse a symbolic hostname 
+    * from {@link InetAddress.toString()}, which is documented to return a 
+    * String of the form "symbolicname/ipaddress" with 'symbolicname' blank 
+    * if not stored in the object.
+    * <p/>
+    * If the symbolic name cannot be determined from InetAddress.toString(),
+    * the value of {@link InetAddress.getHostAddress()} is returned.
+    */
+   private static String getFastHostName(InetAddress address)
+   {
+      String result = null;
+      
+      String hostAddress = address.getHostAddress();
+      
+      String inetAddr = address.toString();
+      int idx = inetAddr.lastIndexOf('/');
+      int idx1 = inetAddr.indexOf(hostAddress);
+      if (idx1 == idx + 1)
+      {
+         if (idx == 0)
+            result = hostAddress;
+         else
+            result = inetAddr.substring(0, idx);
+      }
+      else
+      {
+         // Doesn't follow the toString() contract!
+         result = hostAddress;
+      }
+      return result;
+   }
+   
+   private static InetAddress fixRemoteAddress(InetAddress address)
+   {
+      try
+      {
+         if (address == null
+               || InetAddress.getByName(VALUE_BIND_ADDRESS_ANY).equals(address))
+            return InetAddress.getLocalHost();
+      }
+      catch (UnknownHostException ignored)
+      {
+      }
+      return address;
+   }
+   
+   private Map<String, String> getTransportProperties(ProtocolStackConfigurator config)
+   {
+      Map<String, String> tpProps = null;
+      ProtocolData[] protocols= ProtocolStackUtil.getProtocolData(config);
+      ProtocolData transport=protocols[0];
+      @SuppressWarnings("unchecked")
+      Map<String,ProtocolParameter> tmp=transport.getParameters();
+      tpProps = new HashMap<String,String>();
+      for(Map.Entry<String,ProtocolParameter> entry: tmp.entrySet())
+      {
+          tpProps.put(entry.getKey(), entry.getValue().getValue());
+      }
+      
+      return tpProps;
+   }
+  
+   private ProtocolStackConfigurator addSingletonName(ProtocolStackConfigurator orig, String singletonName)
+      throws ChannelException
+   {
+      ProtocolStackConfigurator result = null;
+      try
+      {
+         ProtocolData[] protocols=orig.getProtocolStack();
+         ProtocolData transport=protocols[0];
+         ProtocolParameter singletonParam = new ProtocolParameter(Global.SINGLETON_NAME, singletonName);
+         transport.override(new ProtocolParameter[]{ singletonParam});
+         result = orig;
+      }
+      catch (UnsupportedOperationException uoe)
+      {
+         // JGroups version hasn't implemented ProtocolStackConfigurator.getProtocolStack()
+         // So we do things manually via string manipulation         
+         String config = orig.getProtocolStackString();
+         int idx = config.indexOf('(') + 1;
+         StringBuilder builder = new StringBuilder(config.substring(0, idx));
+         builder.append(Global.SINGLETON_NAME);
+         builder.append('=');
+         builder.append(singletonName);
+         builder.append(';');
+         builder.append(config.substring(idx));
+         
+         result = ConfiguratorFactory.getStackConfigurator(builder.toString());
+      }
+      
+      return result;
+   }
+   
+   private void fixChannelThreadManagement(Channel channel) throws ChannelException
+   {
+      if (!(channel instanceof JChannel))
+      {
+         log.debug("Cannot fix thread pools for unknown Channel type " + channel.getClass().getName());
+         return;
+      }
+      
+      JChannel jchannel = (JChannel) channel;
+      
+      ProtocolStack stack = jchannel.getProtocolStack();
+      List<Protocol> protocols = stack.getProtocols();
+      TP tp = null;
+      for (int i = protocols.size() - 1; i >= 0; i--)
+      {
+         if (protocols.get(i) instanceof TP)
+         {
+            tp = (TP) protocols.get(i);
+            break;
+         }
+      }
+      
+      ClassLoader defaultTCCL = getDefaultChannelThreadContextClassLoader();
+      ThreadDecoratorImpl threadDecorator = new ThreadDecoratorImpl(defaultTCCL);
+      if (manageNewThreadClassLoader)
+      {
+         fixProtocolThreadFactories(tp, threadDecorator);
+      }
+      
+      if (manageReleasedThreadClassLoader)
+      {
+         fixTransportThreadPools(tp, threadDecorator);
+      }
+   }
+
+   private void fixProtocolThreadFactories(TP tp, ThreadDecoratorImpl threadDecorator)
+   {
+      ThreadFactory stackFactory = tp.getThreadFactory();
+      if (stackFactory == null)
+      {
+         stackFactory = new DefaultThreadFactory(Util.getGlobalThreadGroup(), "", false);
+         tp.setThreadFactory(stackFactory);
+      }
+      fixThreadManager(stackFactory, threadDecorator, "TP.getThreadFactory()");
+      
+      log.debug("Fixed thread factory for " + tp);
+      
+      ThreadFactory timerFactory = tp.getTimerThreadFactory();
+      if (timerFactory == null)
+      {
+         timerFactory = new LazyThreadFactory(Util.getGlobalThreadGroup(), "Timer", true, true);
+         tp.setTimerThreadFactory(timerFactory);            
+      }
+      fixThreadManager(timerFactory, threadDecorator, "TP.getTimerThreadFactory()");
+      
+      log.debug("Fixed timer thread factory for " + tp);
+      
+      ThreadGroup pool_thread_group = null;
+      if (tp.isDefaulThreadPoolEnabled())
+      {
+         ThreadFactory defaultPoolFactory = tp.getDefaultThreadPoolThreadFactory();
+         if (defaultPoolFactory == null)
+         {
+            pool_thread_group=new ThreadGroup(Util.getGlobalThreadGroup(), "Thread Pools");
+            defaultPoolFactory = new DefaultThreadFactory(pool_thread_group, "Incoming", false, true);
+            tp.setThreadFactory(defaultPoolFactory);
+         }
+         fixThreadManager(defaultPoolFactory, threadDecorator, "TP.getDefaultThreadPoolThreadFactory()");
+         
+         log.debug("Fixed default pool thread factory for " + tp);
+      }
+      
+      if (tp.isOOBThreadPoolEnabled())
+      {
+         ThreadFactory oobPoolFactory = tp.getOOBThreadPoolThreadFactory();
+         if (oobPoolFactory == null)
+         {
+            if (pool_thread_group == null)
+               pool_thread_group=new ThreadGroup(Util.getGlobalThreadGroup(), "Thread Pools");
+            oobPoolFactory = new DefaultThreadFactory(pool_thread_group, "OOB", false, true);
+            tp.setThreadFactory(oobPoolFactory);
+         }
+         fixThreadManager(oobPoolFactory, threadDecorator, "TP.getOOBThreadPoolThreadFactory()");
+         
+         log.debug("Fixed oob pool thread factory for " + tp);
+      }
+      
+      Map<ThreadFactory, Protocol> factories= new HashMap<ThreadFactory, Protocol>();
+      Protocol tmp=tp.getUpProtocol();
+      while(tmp != null) {
+        ThreadFactory f=tmp.getThreadFactory();
+         if(f != null && !factories.containsKey(f))
+         {
+            factories.put(f, tmp);
+         }
+         tmp=tmp.getUpProtocol();
+      }
+      
+      for (Map.Entry<ThreadFactory, Protocol> entry : factories.entrySet())
+      {
+         fixThreadManager(entry.getKey(), threadDecorator, entry.getValue().getClass().getSimpleName() + ".getThreadFactory()");
+      }
+      
+      log.debug("Fixed Protocol thread factories");
+   }
+
+   private void fixTransportThreadPools(TP tp, ThreadDecoratorImpl threadDecorator)
+   {
+      Executor threadPool = tp.getDefaultThreadPool();
+      if (tp.isDefaulThreadPoolEnabled())
+      {
+         fixThreadManager(threadPool, threadDecorator, "TP.getDefaultThreadPool()");
+         
+         log.debug("Fixed default thread pool for " + tp);
+      }
+      
+      threadPool = tp.getOOBThreadPool();
+      if (tp.isOOBThreadPoolEnabled())
+      {
+         fixThreadManager(threadPool, threadDecorator, "TP.getOOBThreadPool()"); 
+         
+         log.debug("Fixed OOB thread pool for " + tp);
+      }
+   }
+   
+   private void fixThreadManager(Object manager, ThreadDecoratorImpl decorator, String managerSource)
+   {
+      if (manager instanceof ThreadManager)
+      {
+         ThreadManager threadManager = (ThreadManager) manager;
+         
+         ThreadDecorator existing = threadManager.getThreadDecorator();
+         if (existing instanceof ThreadDecoratorImpl)
+         {
+            // already been handled
+            return;
+         }
+         else if (existing != null)
+         {
+            // someone else has added one; integrate with it
+            decorator.setParent(existing);
+         }
+         threadManager.setThreadDecorator(decorator);
+      }
+      else
+      {
+         log.warn(managerSource + " is not a ThreadManager");
+      }
+   }
+   
+   /** 
+    * Sets the context class loader on <code>thread</code> to the classloader
+    * in effect when this factory was instantiated.
+    * 
+    * @param thread the thread to set
+    */
+   private void setDefaultThreadContextClassLoader(Thread thread, ClassLoader classLoader)
+   {
+      classLoaderSwitcher.setContextClassLoader(thread, classLoader);
+   }
+   
+   private void registerChannel(JChannel ch, String channelId, String stackName, ProtocolData[] config) throws Exception
+   {
+      // Register for channel closed notification so we can unregister
+      ch.addChannelListener(closeListener);
+      
+      ObjectName chName = null;
+      List<ObjectName> protNames = null;
+      List<ObjectName> allNames = registerInJmx(ch, channelId);
+      if (allNames != null && allNames.size() > 0)
+      {
+         chName = allNames.get(0);
+         if (allNames.size() > 1)
+         {
+            protNames = allNames.subList(1, allNames.size());
+         }
+      }
+      
+      ChannelInfo info = new ChannelInfo(channelId, stackName, ch, config, chName, protNames);
+      registeredChannels.put(ch, info);
+   }
+   
+   private List<ObjectName> registerInJmx(JChannel ch, String channelId) throws Exception 
+   {
+      List<ObjectName> allNames = null;
+      
+      if(isExposeChannels() && getServer() != null && channelId != null && channelId.length() > 0)
+      {
+         allNames = new ArrayList<ObjectName>();
+         ObjectName channelName = new ObjectName(getDomain() + ":type=channel,cluster=" + channelId);
+         JmxConfigurator conf = new JmxConfigurator();
+         getServer().registerMBean(conf.asDynamicMBean(ch), channelName);
+         allNames.add(channelName);
+         if (isExposeProtocols())
+         {
+            String baseName = getDomain() + ":type=protocol,cluster=" + channelId;
+            ProtocolStack stack=ch.getProtocolStack();
+            List<Protocol> protocols=stack.getProtocols();
+            
+            for(Protocol prot : protocols)
+            {                                
+                ObjectName prot_name=new ObjectName(baseName + ",protocol=" + prot.getName());
+                server.registerMBean(conf.asDynamicMBean(prot), prot_name);
+                allNames.add(prot_name);
+            }
+         }
+      }
+      
+      return allNames;
+   }
+   
+   private void unregisterChannel(Channel ch)
+   {
+      ChannelInfo info = registeredChannels.remove(ch);
+      if (info == null)
+      {
+         log.warn("Unknown channel " + ch.getClusterName());
+      }
+      else
+      {
+         unregisterFromJmx(info);
+      }
+      
+      ch.removeChannelListener(closeListener);
+   }
+   
+   private void unregisterFromJmx(ChannelInfo info) 
+   {
+      ObjectName oname = info.getChannelObjectName();
+      MBeanServer mbs = getServer();
+      if(info != null && mbs != null)
+      {
+         try
+         {
+            mbs.unregisterMBean(oname);
+         }
+         catch(Exception e)
+         {
+            log.error("failed unregistering " + oname, e);
+         }
+         
+         List<ObjectName> onames = info.getProtocolObjectNames();
+         if (onames != null)
+         {
+            for (ObjectName protName : onames)
+            {
+               try
+               {
+                  mbs.unregisterMBean(protName);
+               }
+               catch(Exception e)
+               {
+                  log.error("failed unregistering " + protName, e);
+               }
+               
+            }
+         }
+      }
+   }
+   
+   private class ThreadDecoratorImpl implements ThreadDecorator
+   {
+      private final ClassLoader classloader;
+      private ThreadDecorator parent;
+      
+      private ThreadDecoratorImpl(ClassLoader classloader)
+      {
+         this.classloader = classloader;
+      }
+
+      public void threadCreated(Thread thread)
+      {
+         if (parent != null)
+            parent.threadCreated(thread);
+         setDefaultThreadContextClassLoader(thread, classloader);
+      }
+
+      public void threadReleased(Thread thread)
+      {
+         if (parent != null)
+            parent.threadCreated(thread);
+         setDefaultThreadContextClassLoader(thread, classloader);
+      }
+
+      public ThreadDecorator getParent()
+      {
+         return parent;
+      }
+
+      public void setParent(ThreadDecorator parent)
+      {
+         this.parent = parent;
+      }
+      
+   }
+
+   private class ChannelCloseListener extends ChannelListenerAdapter
+   {
+      public void channelClosed(Channel channel) 
+      {
+         unregisterChannel(channel);
+      }            
+   }
+   
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactoryMBean.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/JChannelFactoryMBean.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactoryMBean.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/JChannelFactoryMBean.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,266 @@
+/*
+ * 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.ha.core.channelfactory;
+
+import javax.management.ObjectName;
+
+import org.jgroups.Channel;
+import org.jgroups.ChannelException;
+
+/**
+ * StandardMBean interface for {@link JChannelFactory}.
+ * <p>
+ * The plain-old-java-interface for the channel factory is 
+ * <code>org.jgroups.ChannelFactory</code>; users are encouraged to dependency
+ * inject a <code>org.jgroups.ChannelFactory</code>.
+ * 
+ * 
+ * @author Bela Ban
+ * @author Brian Stansberry
+ * 
+ * @version $Revision$
+ */
+public interface JChannelFactoryMBean 
+{   
+   /**
+    * Returns all configurations as a string
+    */
+   String getMultiplexerConfig();
+   
+   /**
+    * Instructs JGroups to load a set of XML protocol stack configurations.
+    * Same as {@link #setMultiplexerConfig(String, boolean) setMultiplexerConfig(properties, true)}. 
+    * 
+    * @param properties a string representing a system resource containing a
+    *                   JGroups XML configuration, a string representing a URL
+    *                   pointing to a JGroups XML configuration, or a string
+    *                   representing a file name that contains a JGroups XML
+    *                   configuration.
+    *                   
+    * @throws Exception
+    */
+   void setMultiplexerConfig(String properties) throws Exception;
+   
+   /**
+    * Instructs JGroups to load a set of XML protocol stack configurations.
+    * 
+    * @param properties a string representing a system resource containing a
+    *                   JGroups XML configuration, a string representing a URL
+    *                   pointing to a JGroups XML configuration, or a string
+    *                   representing a file name that contains a JGroups XML
+    *                   configuration.
+    * @param replace    <code>true</code> if any protocol stack configuration
+    *                   in <code>properties</code> that has the same name
+    *                   as an existing configuration should replace that
+    *                   existing configuration; <code>false</code> if the
+    *                   existing configuration should take precedence.
+    * 
+    * @throws Exception
+    */
+   void setMultiplexerConfig(String properties, boolean replace) throws Exception;
+
+   /**
+    * Gets the domain portion of any {@link ObjectName} the factory should
+    * use when registering channels or protocols in JMX.
+    * 
+    * @return the domain portion of the object name, or <code>null</code>.
+    */
+   String getDomain();
+   
+   /**
+    * Sets the domain portion of any {@link ObjectName} the factory should
+    * use when registering channels or protocols in JMX.
+    * 
+    * @param name the domain portion of the object name. Must conform to
+    *             the rules for elements in an ObjectName.
+    */
+   void setDomain(String name);
+
+   /**
+    * Gets whether this factory should register channels it creates in JMX.
+    * 
+    * @return <code>true</code> if channels should be registered, 
+    *         <code>false</code> if not
+    */
+   boolean isExposeChannels();
+  
+   /**
+    * Sets whether this factory should register channels it creates in JMX.
+    * 
+    * @param flag <code>true</code> if channels should be registered, 
+    *             <code>false</code> if not
+    */
+   void setExposeChannels(boolean flag);
+
+   /**
+    * Gets whether this factory should register protocols it creates in JMX.
+    * 
+    * @return <code>true</code> if protocols should be registered, 
+    *         <code>false</code> if not
+    */
+   boolean isExposeProtocols();
+   
+   /**
+    * Sets whether this factory should register protocols it creates in JMX.
+    * 
+    * @param flag <code>true</code> if protocols should be registered, 
+    *             <code>false</code> if not
+    */
+   void setExposeProtocols(boolean f);
+
+   /**
+    * Returns the stack configuration as a string (valid to be fed into new JChannel(String)). Throws an exception
+    * if the stack_name is not found. One of the setMultiplexerConfig() methods had to be called beforehand.
+    * 
+    * @return The protocol stack config as a plain string
+    */
+   String getConfig(String stack_name) throws Exception;
+   
+   /**
+    * Removes the given stack from the configuration.
+    * 
+    * @param stack_name the name of the stack
+    * @return <code>true</code> if the stack was removed; <code>false</code> if
+    *         it wasn't registered
+    */
+   boolean removeConfig(String stack_name);
+   
+   /** Removes all protocol stack configurations */
+   void clearConfigurations();
+
+   /**
+    * Create a {@link Channel} using the specified stack. Channel will use a 
+    * shared transport.
+    * 
+    * @param stack_name 
+    *            The name of the stack to be used. All stacks are defined in
+    *            the configuration with which the factory is configured (see
+    *            {@link #setMultiplexerConfig(Object)} for example. If
+    *            clients attempt to create a Channel for an undefined stack 
+    *            name an exception will be thrown.
+    * 
+    * @return an implementation of Channel configured with a shared transport.
+    * 
+    * @throws IllegalArgumentException if <code>stack_name</code> is 
+    * <code>null</code> or {@link #getConfig(String)} returns <code>null</code>
+    * when <code>stack_name</code> is used.
+    * 
+    * @throws Exception
+    */
+   Channel createChannel(String stack_name) throws Exception;
+    
+   /**
+    * Creates and returns a shared transport Channel configured with the specified 
+    * {@link #getConfig(String) protocol stack configuration}.
+    * <p>
+    * <emphasis>NOTE:</emphasis> The implementation of this method is somewhat
+    * different from what is described in 
+    * {@link org.jgroups.ChannelFactory#createMultiplexerChannel(String, String)}.
+    * The returned channel will not be an instance of 
+    * <code>org.jgroups.mux.MuxChannel</code>; rather a channel that uses a
+    * shared transport will be returned.  This will be the case whether or
+    * not the protocol stack specified by <code>stack_name</code> includes
+    * a <code>singleton_name</code> attribute in its 
+    * {@link org.jgroups.protocols.TP transport protocol} configuration. If no 
+    * <code>singleton_name</code> attribute is present, this factory will create
+    * a synthetic one by prepending "unnamed_" to the provided
+    * <code>id</code> param and will use that for the returned channel's 
+    * transport protocol. (Note this will not effect the protocol stack
+    * configuration named by <code>stack_name</code>; i.e. another request
+    * that passes the same <code>stack_name</code> will not inherit the
+    * synthetic singleton name.) 
+    * 
+    * @param stack_name
+    *            The name of the stack to be used. All stacks are defined in
+    *            the configuration with which the factory is configured (see
+    *            {@link #setMultiplexerConfig(Object)} for example. If
+    *            clients attempt to create a Channel for an undefined stack 
+    *            name an Exception will be thrown.
+    * @param id  Only used if the transport protocol configuration for the
+    *            specified stack does not include the <code>singleton_name</code>
+    *            attribute; then it is used to create a synthetic singleton-name
+    *            for the channel's protocol stack.
+    *            
+    * @return An implementation of Channel configured with a shared transport.
+    *         
+    * @throws ChannelException
+    */
+   Channel createMultiplexerChannel(String stack_name, String id) throws Exception;
+   
+   
+   /**
+    * Creates and returns a shared transport Channel configured with the specified 
+    * {@link #getConfig(String) protocol stack configuration}.
+    * 
+    * See {@link #createMultiplexerChannel(String, String)}; the additional
+    * attributes specified in this overloaded version of that method are ignored.
+    *
+    * @param register_for_state_transfer ignored in JBoss AS. Treated as <code>false</code>.
+    * 
+    * @param substate_id ignored in JBoss AS
+    *            
+    * @return An implementation of Channel configured with a shared transport.
+    *         
+    * @throws ChannelException
+    */
+   Channel createMultiplexerChannel(String stack_name, String id, boolean register_for_state_transfer, String substate_id) throws Exception;
+   
+   /**
+    * Execute the create phase of the 4 step lifecycle.
+    * 
+    * @throws Exception
+    */
+   void create() throws Exception;
+   
+   /**
+    * Execute the start phase of the 4 step lifecycle.
+    * 
+    * @throws Exception
+    */
+   void start() throws Exception;
+   
+   /**
+    * Execute the stop phase of the 4 step lifecycle.
+    * 
+    * @throws Exception
+    */
+   void stop();
+   
+   /**
+    * Execute the destroy phase of the 4 step lifecycle.
+    * 
+    * @throws Exception
+    */
+   void destroy();
+    
+   /**
+    * Returns the names of the currently registered protocol stack configurations.
+    */
+   String dumpConfiguration();
+   
+   /**
+    * Dumps the names of any currently running multiplexer channels along with
+    * the id's of any services that are using them.  Information about
+    * currently running non-multiplexer channels are not returned.
+    */
+   String dumpChannels();
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackConfigInfo.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/ProtocolStackConfigInfo.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackConfigInfo.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackConfigInfo.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,75 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, 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.ha.core.channelfactory;
+
+import org.jgroups.conf.ProtocolData;
+import org.jgroups.conf.ProtocolStackConfigurator;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public class ProtocolStackConfigInfo
+{
+   private final String name;
+   private final String description;
+   private final ProtocolStackConfigurator configurator;
+   
+   public ProtocolStackConfigInfo(String name, String description, ProtocolStackConfigurator configurator)
+   {
+      if (name == null)
+      {
+         throw new IllegalArgumentException("null name");
+      }
+      if (configurator == null)
+      {
+         throw new IllegalArgumentException("null configurator");
+      }
+      this.name = name;
+      this.description = description;
+      this.configurator = configurator;
+   }
+
+   public String getName()
+   {
+      return name;
+   }
+
+   public String getDescription()
+   {
+      return description;
+   }
+
+   ProtocolStackConfigurator getConfigurator()
+   {
+      return configurator;
+   }
+
+   public ProtocolData[] getConfiguration()
+   {
+      return ProtocolStackUtil.getProtocolData(configurator);
+   }
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackUtil.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/ProtocolStackUtil.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackUtil.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/channelfactory/ProtocolStackUtil.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,217 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009, 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.ha.core.channelfactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.jboss.logging.Logger;
+import org.jgroups.conf.ConfiguratorFactory;
+import org.jgroups.conf.ProtocolData;
+import org.jgroups.conf.ProtocolParameter;
+import org.jgroups.conf.ProtocolStackConfigurator;
+import org.jgroups.conf.XmlConfigurator;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Utilities related to JGroups protocol stack manipulation.
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision: $
+ */
+public final class ProtocolStackUtil
+{  
+   private static final Logger log = Logger.getLogger(ProtocolStackUtil.class);
+   
+   private static final String PROTOCOL_STACKS="protocol_stacks";
+   private static final String STACK="stack";
+   private static final String NAME="name";
+   private static final String DESCR="description";
+   private static final String CONFIG="config";
+
+   /**
+    * Parses the contents of <code>input</code> into a map of the
+    * protocol stack configurations contained in the XML.
+    * 
+    * @param input stream which must contain XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    *              
+    * @return a map of the protocol stack configurations contained in the XML
+    * 
+    * @throws IllegalArgumentException if <code>input</code> is <code>null</code>
+    * @throws Exception
+    */
+   public static Map<String, ProtocolStackConfigInfo> parse(InputStream input) throws Exception 
+   {
+      if (input == null)
+      {
+         throw new IllegalArgumentException("null input");
+      }
+      
+      DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
+      factory.setValidating(false); //for now
+      DocumentBuilder builder=factory.newDocumentBuilder();
+      Document document=builder.parse(input);
+
+      // The root element of the document should be the "config" element,
+      // but the parser(Element) method checks this so a check is not
+      // needed here.
+      Element configElement = document.getDocumentElement();
+      return parse(configElement);
+   }
+   
+   /**
+    * Parses the contents of <code>root</code> into a map of the
+    * protocol stack configurations contained in the XML.
+    * 
+    * @param root document root node for XML content in the JGroups 
+    *              <code>stacks.xml</code> format
+    *              
+    * @return a map of the protocol stack configurations contained in the XML
+    * 
+    * @throws IllegalArgumentException if <code>input</code> is <code>null</code>
+    * @throws Exception
+    */
+   public static Map<String, ProtocolStackConfigInfo> parse(Element root) throws Exception 
+   {
+      if (root == null)
+      {
+         throw new IllegalArgumentException("null root");
+      }
+      
+      String root_name = root.getNodeName();
+      if (!PROTOCOL_STACKS.equals(root_name.trim().toLowerCase()))
+      {
+         throw new IOException("Invalid XML configuration: configuration does not start with a '" + 
+                        PROTOCOL_STACKS + "' element");
+      }
+
+      Map<String, ProtocolStackConfigInfo> result = new HashMap<String, ProtocolStackConfigInfo>();
+
+      NodeList tmp_stacks = root.getChildNodes();
+      for (int i = 0; i < tmp_stacks.getLength(); i++)
+      {
+         Node node = tmp_stacks.item(i);
+         if (node.getNodeType() != Node.ELEMENT_NODE)
+            continue;
+
+         Element stack = (Element) node;
+         String tmp = stack.getNodeName();
+         if (!STACK.equals(tmp.trim().toLowerCase()))
+         {
+            throw new IOException("Invalid configuration: didn't find a \"" + STACK + "\" element under \""
+                  + PROTOCOL_STACKS + "\"");
+         }
+
+         NamedNodeMap attrs = stack.getAttributes();
+         Node name = attrs.getNamedItem(NAME);
+         String st_name = name.getNodeValue();
+         Node descr=attrs.getNamedItem(DESCR);
+         String stack_descr=descr.getNodeValue();
+         if (log.isTraceEnabled())
+         {
+            log.trace("Parsing \"" + st_name + "\" (" + stack_descr + ")");
+         }
+         NodeList configs = stack.getChildNodes();
+         for (int j = 0; j < configs.getLength(); j++)
+         {
+            Node tmp_config = configs.item(j);
+            if (tmp_config.getNodeType() != Node.ELEMENT_NODE)
+               continue;
+            Element cfg = (Element) tmp_config;
+            tmp = cfg.getNodeName();
+            if (!CONFIG.equals(tmp))
+            {
+               throw new IOException("Invalid configuration: didn't find a \"" + 
+                     CONFIG + "\" element under \"" + STACK + "\"");
+            }
+
+            XmlConfigurator conf = XmlConfigurator.getInstance(cfg);
+            // fixes http://jira.jboss.com/jira/browse/JGRP-290
+            ConfiguratorFactory.substituteVariables(conf); // replace vars with system props
+
+            result.put(st_name, new ProtocolStackConfigInfo(st_name, stack_descr, conf));
+         }
+      }
+
+      return result;
+   }
+   
+   public static ProtocolData[] getProtocolData(ProtocolStackConfigurator config)
+   {
+      ProtocolData[] result = null;
+      try
+      {
+         result = config.getProtocolStack();
+      }
+      catch (UnsupportedOperationException e)
+      {
+         String s = config.getProtocolStackString();
+         String[] prots = s.split(":");
+         result = new ProtocolData[prots.length];
+         for (int i = 0; i < prots.length; i++)
+         {
+            ProtocolParameter[] params = null;
+            int paren = prots[i].indexOf('(');
+            String name = paren > - 1 ? prots[i].substring(0, paren) : prots[1];
+            if (paren > -1 && paren < prots[1].length() - 2)
+            {
+               String unsplit = prots[i].substring(paren + 1, prots[i].length() -1);
+               String[] split = unsplit.split(";");
+                params = new ProtocolParameter[split.length];
+               for (int j = 0; j < split.length; j++)
+               {
+                  String[] keyVal = split[j].split("=");
+                  params[j] = new ProtocolParameter(keyVal[0], keyVal[1]);
+               }
+            }
+            else
+            {
+               params = new ProtocolParameter[0];
+            }
+            
+            result[i] = new ProtocolData(name, null, name, params);
+         }
+      }
+      
+      return result == null ? new ProtocolData[0] : result;
+   }   
+   
+   /**
+    * Prevent instantiation.
+    */
+   private ProtocolStackUtil()
+   {
+   }
+
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AddressPort.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/AddressPort.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AddressPort.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AddressPort.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,67 @@
+/*
+ * 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.ha.core.framework.server;
+
+import java.io.Serializable;
+import java.net.InetAddress;
+
+public class AddressPort implements Serializable
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -1878072081329209058L;
+
+   private InetAddress addr;
+
+   private Integer port;
+
+   public AddressPort(InetAddress addr, Integer port)
+   {
+      this.addr = addr;
+      this.port = port;
+   }
+
+   public Integer getPort()
+   {
+      return this.port;
+   }
+
+   public InetAddress getInetAddress()
+   {
+      return this.addr;
+   }
+
+   public String getHostAddress()
+   {
+      return this.addr.getHostAddress();
+   }
+
+   public String getHostName()
+   {
+      return this.addr.getHostName();
+   }
+
+   @Override
+   public String toString()
+   {
+      return "{host(" + this.addr + "), port(" + this.port + ")}";
+   }
+}
\ No newline at end of file

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AsynchEventHandler.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/AsynchEventHandler.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AsynchEventHandler.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/AsynchEventHandler.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,174 @@
+/*
+ * 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.ha.core.framework.server;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Utility class that accepts objects into a queue and maintains a separate
+ * thread that reads them off the queue and passes them to a registered
+ * "processor".
+ * 
+ * @todo find a better home for this than the cluster module
+ * 
+ * @author <a href="mailto://brian.stansberry@jboss.com">Brian Stansberry</a>
+ * @version $Revision$
+ */
+class AsynchEventHandler implements Runnable
+{
+   /**
+    * Interface implemented by classes able to process the objects
+    * placed into an AsynchEventHandler's queue.
+    */
+   public static interface AsynchEventProcessor
+   {
+      public void processEvent(Object event);
+   }
+   
+   private String name;
+   /** The LinkedQueue of events to pass to our processor */
+   private BlockingQueue<Object> events = new LinkedBlockingQueue<Object>();
+   /** Whether we're blocking on the queue */
+   private boolean blocking;
+   private AsynchEventProcessor processor;
+   private boolean stopped = true;
+   private Thread  handlerThread;
+   private Logger log;
+   
+   /**
+    * Create a new AsynchEventHandler.
+    * 
+    * @param processor  object to which objects placed in the queue should
+    *                   be handed when dequeued
+    * @param name       name for this instance.  Appended to the processor's
+    *                   class name to create a log category, and used
+    *                   to name to handler thread
+    */
+   public AsynchEventHandler(AsynchEventProcessor processor, String name)
+   {
+      super();
+      this.processor = processor;
+      if (name == null)
+         name = "AsynchEventHandler";
+      this.name = name;
+      this.log = Logger.getLogger(processor.getClass().getName() + "." + name);
+   }
+   
+   /**
+    * Place the given object in the queue.
+    * 
+    * @param event  the object to asynchronously pass to the 
+    *               AsynchEventHandler.
+    *               
+    * @throws InterruptedException  if the thread is interrupted while blocking
+    *                               on the queue.
+    */
+   public void queueEvent(Object event) throws InterruptedException
+   {
+      if (event != null)
+         events.add(event);
+   }
+   
+   public void run()
+   {
+      log.debug("Begin " + name + " Thread");
+      stopped = false;
+      boolean intr = false;
+      try
+      {
+         while( !stopped )
+         {
+            try
+            {
+               blocking = true;
+               Object event = events.take();
+               blocking = false;
+
+               if (!stopped)
+               {
+                  processor.processEvent(event);
+               }
+            }
+            catch(InterruptedException e)
+            {
+               intr = true;
+               blocking = false;
+               if (stopped)
+               {
+                  log.debug(name + " Thread interrupted");
+                  break;
+               }
+               log.error(name + " Thread interrupted", e);
+            }
+            catch (Throwable t)
+            {
+               log.error("Caught Throwable handling asynch events", t);
+            }
+         }
+         log.debug("End " + name + " Thread");
+      }
+      finally
+      {
+         if (intr) Thread.currentThread().interrupt();
+      }
+   }
+   
+   /**
+    * Starts the handler thread.
+    */
+   public void start()
+   {
+      handlerThread = new Thread(this, name + " Thread");
+      handlerThread.start();
+   }
+   
+   /**
+    * Stops the handler thread.
+    */
+   public void stop()
+   {
+      stopped = true;
+      if (blocking)
+         handlerThread.interrupt(); // it's just waiting on the LinkedQueue
+      
+      if (handlerThread.isAlive()) {
+         // Give it up to 100ms to finish whatever it's doing
+         try
+         {
+            handlerThread.join(100);
+         }
+         catch (Exception ignored) {}
+      }
+      
+      if (handlerThread.isAlive())
+         handlerThread.interrupt(); // kill it
+   }
+   
+   public boolean isStopped()
+   {
+      return stopped;
+   }
+
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeFactory.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/ClusterNodeFactory.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeFactory.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeFactory.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, 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.ha.core.framework.server;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jgroups.Address;
+
+/**
+ * Factory for ClusterNode(s)
+ * 
+ * 
+ * @see ClusterNodeImpl
+ * 
+ * @author Vladimir Blagojevic
+ */
+public interface ClusterNodeFactory
+{
+
+   public ClusterNode getClusterNode(Address address);
+
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeImpl.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/ClusterNodeImpl.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/ClusterNodeImpl.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,146 @@
+/*
+ * 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.ha.core.framework.server;
+
+import java.net.InetAddress;
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jgroups.Address;
+
+/**
+ * Replacement for a JG IpAddress that doesn't base its representation
+ * on the JG address but on the computed node name added to the IPAddress instead.
+ * This is to avoid any problem in the cluster as some nodes may interpret a node name
+ * differently (IP resolution, name case, FQDN or host name, etc.)
+ *
+ * @see org.jboss.ha.framework.server.ClusterPartitionMBean
+ *
+ * @author  <a href="mailto:sacha.labourey at jboss.org">Sacha Labourey</a>.
+ * @author Brian Stansberry
+ * @author Vladimir Blagojevic
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @version $Revision$
+ */
+
+public class ClusterNodeImpl
+   implements ClusterNode
+{
+   // Constants -----------------------------------------------------
+   
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -1831036833785680731L;
+   
+   // Attributes ----------------------------------------------------
+  
+   private final String id;
+   private final Address jgAddress;
+   private final AddressPort address;
+
+   // Static --------------------------------------------------------
+   
+   // Constructors --------------------------------------------------
+       
+   ClusterNodeImpl(String id, Address jgAddress, AddressPort addressPort)
+   {
+      if (id == null)
+      {
+         throw new IllegalArgumentException("Null id");
+      }
+      if (addressPort == null)
+      {
+         throw new IllegalArgumentException("Null addressPort");
+      }
+      this.id = id;
+      this.address = addressPort;
+      
+      this.jgAddress = jgAddress;
+      
+   }
+  
+   // Public --------------------------------------------------------
+
+   public String getName()
+   {
+      return this.id;
+   }
+   
+   public InetAddress getIpAddress()
+   {
+      return this.address.getInetAddress();
+   }
+   
+   public int getPort()
+   {
+      return this.address.getPort();      
+   }
+
+   // Package protected ----------------------------------------------
+
+   Address getOriginalJGAddress()
+   {
+      return this.jgAddress;
+   }
+   
+   // Comparable implementation ----------------------------------------------
+
+   public int compareTo(ClusterNode o)
+   {
+      if (o == null)
+         throw new ClassCastException("Comparison to null value");
+      
+      if (!(o instanceof ClusterNodeImpl))
+         throw new ClassCastException("Comparison between different classes");
+
+      return this.id.compareTo(o.getName());
+   }
+   
+   // java.lang.Object overrides ---------------------------------------------------
+
+   public boolean equals(Object obj)
+   {
+      if (this == obj) return true;
+      
+      if (!(obj instanceof ClusterNodeImpl)) return false;
+      
+      ClusterNodeImpl other = (ClusterNodeImpl) obj;
+      return this.id.equals(other.id);
+   }
+
+   public int hashCode()
+   {
+      return id.hashCode();
+   }
+
+   public String toString()
+   {
+      return this.getName();
+   }
+
+   // Package protected ---------------------------------------------
+   
+   // Protected -----------------------------------------------------
+
+
+   // Private -------------------------------------------------------
+   
+   // Inner classes -------------------------------------------------
+   
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/CoreGroupCommunicationService.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/CoreGroupCommunicationService.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/CoreGroupCommunicationService.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,2274 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009 Red Hat, Inc. 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.ha.core.framework.server;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.GroupCommunicationService;
+import org.jboss.ha.framework.interfaces.GroupMembershipListener;
+import org.jboss.ha.framework.interfaces.GroupMembershipNotifier;
+import org.jboss.ha.framework.interfaces.GroupRpcDispatcher;
+import org.jboss.ha.framework.interfaces.GroupStateTransferService;
+import org.jboss.ha.framework.interfaces.ResponseFilter;
+import org.jboss.ha.framework.interfaces.StateTransferProvider;
+import org.jboss.logging.Logger;
+import org.jboss.util.loading.ContextClassLoaderSwitcher;
+import org.jboss.util.loading.ContextClassLoaderSwitcher.SwitchContext;
+import org.jboss.util.stream.MarshalledValueInputStream;
+import org.jboss.util.stream.MarshalledValueOutputStream;
+import org.jgroups.Address;
+import org.jgroups.Channel;
+import org.jgroups.Event;
+import org.jgroups.ExtendedMembershipListener;
+import org.jgroups.ExtendedMessageListener;
+import org.jgroups.MembershipListener;
+import org.jgroups.MergeView;
+import org.jgroups.Message;
+import org.jgroups.MessageListener;
+import org.jgroups.UpHandler;
+import org.jgroups.Version;
+import org.jgroups.View;
+import org.jgroups.blocks.GroupRequest;
+import org.jgroups.blocks.MethodCall;
+import org.jgroups.blocks.RequestOptions;
+import org.jgroups.blocks.RpcDispatcher;
+import org.jgroups.blocks.mux.MuxRpcDispatcher;
+import org.jgroups.blocks.mux.MuxUpHandler;
+import org.jgroups.blocks.mux.Muxer;
+import org.jgroups.stack.IpAddress;
+import org.jgroups.util.Rsp;
+import org.jgroups.util.RspList;
+
+
+/**
+ * Implementation of the {@link GroupCommunicationService} interface and its
+ * direct subinterfaces based on a
+ * <a href="http://www.jgroups.com/">JGroups</a> <code>MuxRpcDispatcher</code>
+ * and a <code>JChannel</code>.
+ *
+ * TODO: look into decomposing this class: the RPC stuff, the membership stuff
+ * and the state transfer stuff could be handled by separate components with
+ * this class used to integrate the pieces and expose a common API. The separate 
+ * components could then be separately testable.
+ * 
+ * @author <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @author <a href="mailto:bill at burkecentral.com">Bill Burke</a>.
+ * @author Scott.Stark at jboss.org
+ * @author Brian Stansberry
+ * @author Vladimir Blagojevic
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * 
+ * @version $Revision: 104456 $
+ */
+public class CoreGroupCommunicationService
+   implements GroupRpcDispatcher, GroupMembershipNotifier, GroupStateTransferService
+{
+   private static final byte NULL_VALUE   = 0;
+   private static final byte SERIALIZABLE_VALUE = 1;
+   // TODO add Streamable support
+   // private static final byte STREAMABLE_VALUE = 2;
+
+   // Constants -----------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   private   String cacheConfigName;
+   @SuppressWarnings("deprecation")
+   private   org.jgroups.ChannelFactory channelFactory;
+   private   String stackName;
+   private   String groupName;
+
+   private boolean channelSelfConnected;
+   
+   /** The JGroups channel */
+   private Channel channel;
+   /** the local JG IP Address */
+   private Address localJGAddress = null;
+   /** me as a ClusterNode */
+   private ClusterNode me = null;
+   /** The current view of the group */
+   private volatile GroupView groupView = new GroupView();
+   
+   private long method_call_timeout=60000;
+   private Short scopeId;
+   private RpcDispatcher dispatcher = null;
+   private final Map<String, Object> rpcHandlers = new ConcurrentHashMap<String, Object>();
+   private final Map<String, WeakReference<ClassLoader>> clmap = new ConcurrentHashMap<String, WeakReference<ClassLoader>>();
+
+   /** Do we send any membership change notifications synchronously? */
+   private boolean allowSyncListeners = false;
+   /** The asynchronously invoked GroupMembershipListeners */
+   private final ArrayList<GroupMembershipListener> asyncMembershipListeners = new ArrayList<GroupMembershipListener>();
+   /** The HAMembershipListener and HAMembershipExtendedListeners */
+   private final ArrayList<GroupMembershipListener> syncMembershipListeners = new ArrayList<GroupMembershipListener>();
+   /** The handler used to send membership change notifications asynchronously */
+   private AsynchEventHandler asynchHandler;
+   
+   private long state_transfer_timeout=60000;
+   private String stateIdPrefix;
+   private final Map<String, StateTransferProvider> stateProviders = new HashMap<String, StateTransferProvider>();   
+   private final Map<String, StateTransferTask> stateTransferTasks = new Hashtable<String, StateTransferTask>();
+   
+   @SuppressWarnings("unchecked")
+   private final ContextClassLoaderSwitcher classLoaderSwitcher = (ContextClassLoaderSwitcher) AccessController.doPrivileged(ContextClassLoaderSwitcher.INSTANTIATOR);
+ 
+   /** The cluster instance log category */
+   protected Logger log = Logger.getLogger(getClass().getName());;
+   private Logger clusterLifeCycleLog = Logger.getLogger(getClass().getName() + ".lifecycle");   
+   private final Vector<String> history = new Vector<String>();
+   private int maxHistoryLength = 100;
+   
+   /** Thread pool used to run state transfer requests */
+   private Executor threadPool;
+   
+   private final ThreadGate flushBlockGate = new ThreadGate();
+   
+   private final ClusterNodeFactory nodeFactory = new ClusterNodeFactoryImpl();
+   
+   private final Object channelLock = new Object();
+   
+   // Static --------------------------------------------------------
+
+   // Constructors --------------------------------------------------
+
+   // GroupCommunicationService implementation ----------------------
+   
+   public boolean isConsistentWith(GroupCommunicationService other)
+   {
+      return this == other;
+   }
+   
+   public String getNodeName()
+   {
+      return this.me == null ? null : this.me.getName();
+   }
+
+   public String getGroupName()
+   {
+      return this.groupName;
+   }
+   
+   public Vector<String> getCurrentView()
+   {
+      GroupView curView = this.groupView;
+      Vector<String> result = new Vector<String>(curView.allMembers.size());
+      for (ClusterNode member: curView.allMembers)
+      {
+         result.add(member.getName());
+      }
+      return result;
+   }
+   
+   public long getCurrentViewId()
+   {
+      return this.groupView.viewId;
+   }
+
+   public ClusterNode[] getClusterNodes ()
+   {
+      GroupView curView = this.groupView;
+      synchronized (curView.allMembers)
+      {
+         return curView.allMembers.toArray(new ClusterNode[curView.allMembers.size()]);
+      }
+   }
+
+   public ClusterNode getClusterNode ()
+   {
+      return this.me;
+   }
+
+   public boolean isCurrentNodeCoordinator ()
+   {
+      GroupView curView = this.groupView;
+      if(curView.allMembers.size() == 0 || this.me == null)
+      {
+         return false;
+      }
+      return curView.allMembers.elementAt (0).equals (this.me);
+   }
+
+   // ***************************
+   // ***************************
+   // RPC multicast communication
+   // ***************************
+   // ***************************
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void registerRPCHandler(String objName, Object subscriber)
+   {
+      this.rpcHandlers.put(objName, subscriber);
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void registerRPCHandler(String objName, Object subscriber, ClassLoader classloader)
+   {
+      this.registerRPCHandler(objName, subscriber);
+      this.clmap.put(objName, new WeakReference<ClassLoader>(classloader));
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void unregisterRPCHandler(String objName, Object subscriber)
+   {
+      this.rpcHandlers.remove(objName);
+      this.clmap.remove(objName);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public ArrayList<?> callMethodOnCluster(String serviceName, String methodName,
+      Object[] args, Class<?>[] types, boolean excludeSelf) throws InterruptedException
+   {
+      return this.callMethodOnCluster(serviceName, methodName, args, types, Object.class, excludeSelf, null, this.getMethodCallTimeout(), false);
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public ArrayList<?> callMethodOnCluster(String serviceName, String methodName, 
+      Object[] args, Class<?>[] types, boolean excludeSelf, ResponseFilter filter) throws InterruptedException
+   {
+      return this.callMethodOnCluster(serviceName, methodName, args, types, Object.class, excludeSelf, filter, this.getMethodCallTimeout(), false);
+   }   
+
+   /**
+    * {@inheritDoc}
+    */
+   public <T> ArrayList<T> callMethodOnCluster(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         Class<T> returnType, boolean excludeSelf, ResponseFilter filter, long methodTimeout, boolean unordered)
+         throws InterruptedException
+   {
+      MethodCall m = new MethodCall(serviceName + "." + methodName, args, types);
+      RspFilterAdapter rspFilter = filter == null ? null : new RspFilterAdapter(filter, this.nodeFactory);
+      RequestOptions ro = new RequestOptions( GroupRequest.GET_ALL, methodTimeout, false, rspFilter);
+      if (excludeSelf)
+      {
+         ro.setExclusionList(this.localJGAddress);
+      }
+      
+      if(this.channel.flushSupported())
+      {
+         this.flushBlockGate.await(this.getMethodCallTimeout());
+      }
+      
+      boolean trace = this.log.isTraceEnabled();
+      if(trace)
+      {
+         this.log.trace("calling synchronous method on cluster, serviceName="+serviceName
+            +", methodName="+methodName+", members="+this.groupView+", excludeSelf="+excludeSelf);
+      }
+      RspList rsp = this.dispatcher.callRemoteMethods(null, m, ro);
+      return this.processResponseList(rsp, returnType, trace);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Object callMethodOnCoordinatorNode(String objName, String methodName,
+          Object[] args, Class<?>[] types,boolean excludeSelf) throws Exception
+   {
+      return this.callMethodOnCoordinatorNode(objName,methodName,args,types,Object.class,excludeSelf, this.getMethodCallTimeout(),false);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public <T> T callMethodOnCoordinatorNode(String objName, String methodName,
+          Object[] args, Class<?>[] types, Class<T> returnType, boolean excludeSelf, long methodTimeout, boolean unordered) throws Exception
+   {
+      boolean trace = this.log.isTraceEnabled();
+
+      MethodCall m = new MethodCall(objName + "." + methodName, args, types);
+      
+      if( trace )
+      {
+         this.log.trace("callMethodOnCoordinatorNode(false), objName="+objName
+            +", methodName="+methodName);
+      }
+      
+      if (returnType == null)
+      {
+         // Use void.class as return type; a call to void.class.cast(object)
+         // below will throw CCE for anything other than null response
+         @SuppressWarnings("unchecked")
+         Class<T> unchecked = (Class<T>) void.class;
+         returnType = unchecked;
+      }
+
+      // the first cluster view member is the coordinator
+      // If we are the coordinator, only call ourself if 'excludeSelf' is false
+      if (this.isCurrentNodeCoordinator () && excludeSelf)
+      {
+         return null;
+      } 
+      
+      Address coord = this.groupView.coordinator;
+      RequestOptions opt = new RequestOptions( GroupRequest.GET_ALL, methodTimeout);
+      if (unordered)
+      {
+         opt.setFlags(Message.OOB);
+      }
+      try
+      {
+         return returnType.cast(this.dispatcher.callRemoteMethod(coord, m, opt));
+      }
+      catch (Exception e)
+      {
+         throw e;
+      }
+      catch (Error e)
+      {
+         throw e;
+      }
+      catch (Throwable e)
+      {
+         throw new RuntimeException("Caught raw Throwable on remote invocation", e);
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Object callMethodOnNode(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         ClusterNode targetNode) throws Exception
+   {
+      return this.callMethodOnNode(serviceName, methodName, args, types, Object.class, this.getMethodCallTimeout(), targetNode, false);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public Object callMethodOnNode(String serviceName, String methodName,
+           Object[] args, Class<?>[] types, long methodTimeout, ClusterNode targetNode) throws Exception
+   {
+      return this.callMethodOnNode(serviceName, methodName, args, types, Object.class, methodTimeout, targetNode, false);
+   }   
+
+   /**
+    * {@inheritDoc}
+    */
+   public <T> T callMethodOnNode(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         Class<T> returnType, long methodTimeout, ClusterNode targetNode, boolean unordered)
+         throws Exception
+   {
+      if (returnType == null)
+      {
+         // Use void.class as return type; a call to void.class.cast(object)
+         // below will throw CCE for anything other than null response
+         @SuppressWarnings("unchecked")
+         Class<T> unchecked = (Class<T>) void.class;
+         returnType = unchecked;
+      }
+      
+      if (!(targetNode instanceof ClusterNodeImpl))
+      {
+         throw new IllegalArgumentException("targetNode " + targetNode + " is not an instance of " +
+                                          ClusterNodeImpl.class + " -- only targetNodes provided by this HAPartition should be used");
+      }
+      boolean trace = this.log.isTraceEnabled();
+
+      MethodCall m = new MethodCall(serviceName + "." + methodName, args, types);
+
+      if (trace)
+      {
+         this.log.trace("callMethodOnNode( objName=" + serviceName + ", methodName=" + methodName);
+      }
+      Object rsp = null;
+      RequestOptions opt = new RequestOptions(GroupRequest.GET_FIRST, methodTimeout);
+      if (unordered)
+      {
+         opt.setFlags(Message.OOB);
+      }
+      try
+      {
+         rsp = this.dispatcher.callRemoteMethod(((ClusterNodeImpl) targetNode).getOriginalJGAddress(), m, opt);
+      }
+      catch (Exception e)
+      {
+         throw e;
+      }
+      catch (Error e)
+      {
+         throw e;
+      }
+      catch (Throwable e)
+      {
+         throw new RuntimeException("Caught raw Throwable on remote invocation", e);
+      }
+      
+      if (rsp instanceof NoHandlerForRPC)
+      {
+         this.log.trace("Ignoring NoHandlerForRPC");
+         rsp = null;
+      }
+
+      return returnType.cast(rsp);
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void callAsyncMethodOnNode(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         ClusterNode targetNode) throws Exception
+   {
+      this.callAsyncMethodOnNode(serviceName, methodName, args, types, targetNode, false);
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void callAsyncMethodOnNode(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         ClusterNode targetNode, boolean unordered) throws Exception
+   {
+
+      if (!(targetNode instanceof ClusterNodeImpl))
+      {
+         throw new IllegalArgumentException("targetNode " + targetNode + " is not an instance of " +
+                                         ClusterNodeImpl.class + " -- only targetNodes provided by this HAPartition should be used");
+      }
+      boolean trace = this.log.isTraceEnabled();
+
+      MethodCall m = new MethodCall(serviceName + "." + methodName, args, types);
+
+      if (trace)
+      {
+         this.log.trace("callAsyncMethodOnNode( objName=" + serviceName + ", methodName=" + methodName);
+      }
+      RequestOptions opt = new RequestOptions(GroupRequest.GET_NONE, this.getMethodCallTimeout());
+      if (unordered)
+      {
+         opt.setFlags(Message.OOB);
+      }
+      try
+      {
+         this.dispatcher.callRemoteMethod(((ClusterNodeImpl) targetNode).getOriginalJGAddress(), m, opt);
+      }
+      catch (Exception e)
+      {
+         throw e;
+      }
+      catch (Error e)
+      {
+         throw e;
+      }
+      catch (Throwable e)
+      {
+         throw new RuntimeException("Caught raw Throwable on remote invocation", e);
+      }
+   }
+
+   /**
+    * {@inheritDoc}
+    */
+   public void callAsynchMethodOnCluster(String serviceName, String methodName,
+      Object[] args, Class<?>[] types, boolean excludeSelf) throws InterruptedException
+   {
+      this.callAsynchMethodOnCluster(serviceName, methodName, args, types, excludeSelf, false);
+   }
+   
+   /**
+    * {@inheritDoc}
+    */
+   public void callAsynchMethodOnCluster(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         boolean excludeSelf, boolean unordered) throws InterruptedException
+   {
+      MethodCall m = new MethodCall(serviceName + "." + methodName, args, types);
+      RequestOptions ro = new RequestOptions( GroupRequest.GET_NONE, this.getMethodCallTimeout());
+      if (excludeSelf)
+      {         
+         ro.setExclusionList(this.localJGAddress);
+      }
+      
+      if(this.channel.flushSupported())
+      {
+         this.flushBlockGate.await(this.getMethodCallTimeout());
+      }
+      if(this.log.isTraceEnabled())
+      {
+         this.log.trace("calling asynch method on cluster, serviceName="+serviceName
+            +", methodName="+methodName+", members="+this.groupView+", excludeSelf="+excludeSelf);
+      }
+      this.dispatcher.callRemoteMethods(null, m, ro);
+
+   }
+
+   public void callAsyncMethodOnCoordinatorNode(String serviceName, String methodName, Object[] args, Class<?>[] types,
+         boolean excludeSelf) throws Exception
+   {
+      this.callAsyncMethodOnCoordinatorNode(serviceName, methodName, args, types, excludeSelf, false);
+   }
+
+   public void callAsyncMethodOnCoordinatorNode(String serviceName, String methodName, Object[] args,
+         Class<?>[] types, boolean excludeSelf, boolean unordered) throws Exception
+   {
+
+      boolean trace = this.log.isTraceEnabled();
+
+      MethodCall m = new MethodCall(serviceName + "." + methodName, args, types);
+      
+      if( trace )
+      {
+         this.log.trace("callMethodOnCoordinatorNode(false), objName=" + serviceName
+            +", methodName="+methodName);
+      }
+
+      // the first cluster view member is the coordinator
+      // If we are the coordinator, only call ourself if 'excludeSelf' is false
+      if (this.isCurrentNodeCoordinator () && excludeSelf)
+      {
+         return;
+      } 
+      
+      Address coord = this.groupView.coordinator;
+      RequestOptions opt = new RequestOptions( GroupRequest.GET_ALL, this.getMethodCallTimeout());
+      if (unordered)
+      {
+         opt.setFlags(Message.OOB);
+      }
+      try
+      {
+         this.dispatcher.callRemoteMethod(coord, m, opt);
+      }
+      catch (Exception e)
+      {
+         throw e;
+      }
+      catch (Error e)
+      {
+         throw e;
+      }
+      catch (Throwable e)
+      {
+         throw new RuntimeException("Caught raw Throwable on remote invocation", e);
+      }
+   }
+   
+   // *************************
+   // *************************
+   // Group Membership listeners
+   // *************************
+   // *************************
+   
+   public boolean getAllowSynchronousMembershipNotifications()
+   {
+      return this.allowSyncListeners;
+   }
+
+   /**
+    * Sets whether this partition will synchronously notify any 
+    * HAPartition.HAMembershipListener of membership changes using the  
+    * calling thread from the underlying group communications layer
+    * (e.g. JGroups).
+    * 
+    * @param allowSync  <code>true</code> if registered listeners that don't 
+    *         implement <code>AsynchHAMembershipExtendedListener</code> or
+    *         <code>AsynchHAMembershipListener</code> should be notified
+    *         synchronously of membership changes; <code>false</code> if
+    *         those listeners can be notified asynchronously.  Default
+    *         is <code>false</code>.
+    */
+   public void setAllowSynchronousMembershipNotifications(boolean allowSync)
+   {
+      this.allowSyncListeners = allowSync;
+   }
+  
+   public void registerGroupMembershipListener(GroupMembershipListener listener)
+   {
+      registerGroupMembershipListener(listener, false);
+   }
+
+   public void unregisterGroupMembershipListener(GroupMembershipListener listener)
+   {
+      unregisterGroupMembershipListener(listener, false);
+   } 
+   
+   // *************************
+   // *************************
+   // State transfer management
+   // *************************
+   // *************************
+
+   public long getStateTransferTimeout() {
+      return this.state_transfer_timeout;
+   }
+
+   public void setStateTransferTimeout(long timeout)
+   {
+      this.state_transfer_timeout = timeout;
+   }
+   
+   public Future<Serializable> getServiceState(String serviceName, ClassLoader classloader)
+   {
+      RunnableFuture<Serializable> future = null;
+      StateTransferTask task = stateTransferTasks.get(serviceName);
+      if (task == null)
+      {
+         task = new StateTransferTask(serviceName, classloader);
+         stateTransferTasks.put(serviceName, task);
+         future = new FutureTask<Serializable>(task);
+         Executor e = getThreadPool();
+         if (e == null)
+         {
+            e = Executors.newSingleThreadExecutor();
+         }
+         e.execute(future);
+      }
+      else
+      {
+         // Unlikely scenario
+         future = new FutureTask<Serializable>(task);
+      }
+      return future;
+   }
+
+   public Future<Serializable> getServiceState(String serviceName)
+   {
+      return getServiceState(serviceName, null);
+   }
+
+   public void registerStateTransferProvider(String serviceName, StateTransferProvider provider)
+   {
+      this.stateProviders.put(serviceName, provider);
+   }
+
+   public void unregisterStateTransferProvider(String serviceName)
+   {
+      this.stateProviders.remove(serviceName);
+   }
+   
+   // Public ------------------------------------------------------------------  
+   
+   public String showHistory()
+   {
+      StringBuffer buff = new StringBuffer();
+      Vector<String> data = new Vector<String>(this.history);
+      for (java.util.Iterator<String> row = data.iterator(); row.hasNext();)
+      {
+         String info = row.next();
+         buff.append(info).append("\n");
+      }
+      return buff.toString();
+   }
+
+   public String showHistoryAsXML()
+   {
+      StringBuffer buff = new StringBuffer();
+      buff.append("<events>\n");
+      Vector<String> data = new Vector<String>(this.history);
+      for (java.util.Iterator<String> row = data.iterator(); row.hasNext();)
+      {
+         buff.append("   <event>\n      ");
+         String info = row.next();
+         buff.append(info);
+         buff.append("\n   </event>\n");
+      }
+      buff.append("</events>\n");
+      return buff.toString();
+   }
+   
+   public Short getScopeId()
+   {
+      return scopeId;
+   }
+   
+   public void setScopeId(short scopeId)
+   {
+      this.scopeId = Short.valueOf(scopeId);
+   }
+   
+   public int getMaxHistoryLength()
+   {
+      return maxHistoryLength;
+   }
+
+   public void setMaxHistoryLength(int maxHistoryLength)
+   {
+      this.maxHistoryLength = maxHistoryLength;
+   }
+
+   public Executor getThreadPool()
+   {
+      return this.threadPool;
+   }
+
+   public void setThreadPool(Executor threadPool)
+   {
+      this.threadPool = threadPool;
+   }
+
+   public String getJGroupsVersion()
+   {
+      return Version.description + "( " + Version.cvs + ")";
+   }
+
+   @SuppressWarnings("deprecation")
+   public org.jgroups.ChannelFactory getChannelFactory()
+   {
+      return this.channelFactory;
+   }
+   
+   @SuppressWarnings("deprecation")
+   public void setChannelFactory(org.jgroups.ChannelFactory factory)
+   {
+      this.channelFactory = factory;
+   }
+
+   public String getCacheConfigName()
+   {
+      return this.cacheConfigName;
+   }
+   
+   public String getChannelStackName()
+   {
+      return this.stackName;
+   }
+   
+   public void setChannelStackName(String stackName)
+   {
+      this.stackName = stackName;
+   }
+
+   public long getMethodCallTimeout() {
+      return this.method_call_timeout;
+   }
+
+   public void setMethodCallTimeout(long timeout)
+   {
+      this.method_call_timeout = timeout;
+   }
+   
+   public void setGroupName(String groupName)
+   {
+      this.groupName = groupName;
+   }
+   
+   public void setChannel(Channel channel)
+   {
+      this.channel = channel;
+   }
+   
+   // Lifecycle ----------------------------------------------------------------
+   
+ 
+   public void create() throws Exception
+   {      
+      createService();
+      
+      this.log.debug("created");
+   }
+   
+   public void start() throws Exception
+   {
+      try
+      {
+         startService();
+      }
+      catch (Throwable t)
+      {
+         this.log.debug("Caught exception after channel connected; closing channel -- " + t.getLocalizedMessage());
+         if (this.channel != null)
+         {
+            this.channel.close();
+            this.channel = null;
+         }
+         if (t instanceof Exception) throw (Exception) t;
+         else if (t instanceof Error) throw  (Error) t;
+         else throw new RuntimeException(t);
+      }
+   }
+
+   public void stop()
+   {
+      try
+      {
+         this.log.info("Stopping partition " + this.getGroupName());
+         stopService();
+         this.log.info("Partition " + this.getGroupName() + " stopped.");
+      }
+      catch (InterruptedException e)
+      {
+         Thread.currentThread().interrupt();
+         log.warn("Error in stop ", e);
+      }
+      catch (Exception e)
+      {
+         log.warn("Error in stop ", e);
+      }
+      
+   }
+   
+   public void destroy()
+   {
+      destroyService();
+   }
+
+   // Protected --------------------------------------------------------------
+   
+   protected void createService() throws Exception
+   {
+      this.setupLoggers(this.getGroupName());
+      
+      // Create the asynchronous handler for view changes
+      this.asynchHandler = new AsynchEventHandler(new ViewChangeEventProcessor(), "AsynchViewChangeHandler");
+   }
+   
+   protected void startService() throws Exception
+   {
+      if (this.scopeId == null)
+      {
+         throw new IllegalStateException("Must set scopeId before calling start()");         
+      }
+      
+      this.stateIdPrefix = getClass().getName() + "." + this.scopeId + ".";
+      
+      
+      if (this.channel == null || !this.channel.isOpen())
+      {
+         this.log.debug("Creating Channel for partition " + this.getGroupName() +
+               " using stack " + this.getChannelStackName());
+   
+         this.channel = this.createChannel();               
+      }
+      
+      this.log.info("Initializing partition " + this.getGroupName());
+      
+      this.dispatcher = new RpcHandler(this.scopeId.shortValue(), this.channel, null, null, new Object(), false);
+      
+      this.dispatcher.setRequestMarshaller(new RequestMarshallerImpl());
+      this.dispatcher.setResponseMarshaller(new ResponseMarshallerImpl());
+      
+      // Subscribe to events generated by the channel
+      this.dispatcher.setMembershipListener(new MembershipListenerImpl());
+      if (this.stateIdPrefix != null)
+      {
+         this.dispatcher.setMessageListener(new MessageListenerAdapter());
+      }
+      
+      if (!this.channel.isConnected())
+      {
+         this.channelSelfConnected = true;
+         this.channel.connect(this.getGroupName());
+      }
+      
+      this.log.debug("Get current members");
+      this.waitForView();
+      
+      // get current JG group properties
+      this.localJGAddress = this.channel.getAddress();
+      this.me = this.nodeFactory.getClusterNode(localJGAddress);         
+
+      this.verifyNodeIsUnique();
+      
+      // Start the asynch listener handler thread
+      this.asynchHandler.start();
+   }
+   
+   protected void stopService() throws Exception
+   {
+      try
+      {
+         this.asynchHandler.stop();
+      }
+      catch( Exception e)
+      {
+         this.log.warn("Failed to stop asynchHandler", e);
+      }
+      
+//    NR 200505 : [JBCLUSTER-38] replace channel.close() by a disconnect and
+//    add the destroyPartition() step
+      try
+      {
+         if (this.channelSelfConnected && this.channel != null && this.channel.isConnected())
+         {
+            this.channelSelfConnected = false;
+            this.channel.disconnect();
+         }
+      }
+      catch (Exception e)
+      {
+         this.log.error("channel disconnection failed", e);
+      }
+   }
+
+   protected void destroyService()
+   {
+      try
+      {
+         if (this.channel != null && this.channel.isOpen())
+         {
+            this.channel.close();
+         }
+      }
+      catch (Exception e)
+      {
+         this.log.error("Closing channel failed", e);
+      }
+   }
+
+   @SuppressWarnings("deprecation")
+   protected Channel createChannel()
+   {
+      org.jgroups.ChannelFactory factory = this.getChannelFactory();
+      if (factory == null)
+      {
+         throw new IllegalStateException("HAPartitionConfig has no JChannelFactory");
+      }
+      String stack = this.getChannelStackName();
+      if (stack == null)
+      {
+         throw new IllegalStateException("HAPartitionConfig has no multiplexer stack");
+      }
+      try
+      {
+         return factory.createMultiplexerChannel(stack, this.getGroupName());
+      }
+      catch (RuntimeException e)
+      {
+         throw e;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException("Failure creating multiplexed Channel", e);
+      }
+   }
+   
+   protected Channel getChannel()
+   {
+      return this.channel;
+   }
+   
+   protected void registerGroupMembershipListener(GroupMembershipListener listener, boolean sync)
+   {
+      if (sync && this.allowSyncListeners)
+      {
+         synchronized(this.syncMembershipListeners) 
+         {
+            this.syncMembershipListeners.add(listener);
+         }          
+      }
+      else
+      {
+         synchronized(this.asyncMembershipListeners) 
+         {
+            this.asyncMembershipListeners.add(listener);
+         }  
+      }
+   }
+   
+   protected void unregisterGroupMembershipListener(GroupMembershipListener listener, boolean sync)
+   {
+      if (sync && this.allowSyncListeners)
+      {
+         synchronized(this.syncMembershipListeners) 
+         {
+            this.syncMembershipListeners.remove(listener);
+         }          
+      }
+      else
+      {
+         synchronized(this.asyncMembershipListeners) 
+         {
+            this.asyncMembershipListeners.remove(listener);
+         }  
+      }    
+   }
+   
+   protected void logHistory (String message)
+   {
+      if (this.maxHistoryLength  > 0)
+      {
+         try
+         {
+            
+            this.history.add(new SimpleDateFormat().format (new Date()) + " : " + message);
+            if (this.history.size() > this.maxHistoryLength)
+            {
+               this.history.remove(0);
+            }
+         }
+         catch (Exception ignored){}
+      }
+   }
+   
+   
+   // Private -------------------------------------------------------
+   
+   /**
+    * Creates an object from a byte buffer
+    */
+   private Object objectFromByteBufferInternal (byte[] buffer) throws Exception
+   {
+      if(buffer == null)
+      {
+         return null;
+      }
+
+      ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
+      MarshalledValueInputStream mvis = new MarshalledValueInputStream(bais);
+      return mvis.readObject();
+   }
+   
+   /**
+    * Serializes an object into a byte buffer.
+    * The object has to implement interface Serializable or Externalizable
+    */
+   private byte[] objectToByteBufferInternal (Object obj) throws Exception
+   {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      MarshalledValueOutputStream mvos = new MarshalledValueOutputStream(baos);
+      mvos.writeObject(obj);
+      mvos.flush();
+      return baos.toByteArray();
+   }
+   
+   /**
+    * Creates a response object from a byte buffer - optimized for response marshalling
+    */
+   private Object objectFromByteBufferResponseInternal (byte[] buffer) throws Exception
+   {
+      if(buffer == null)
+      {
+         return null;
+      }
+
+      if (buffer[0] == NULL_VALUE)
+      {
+         return null;
+      }
+
+      ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
+      // read past the null/serializable byte
+      bais.read();
+      MarshalledValueInputStream mvis = new MarshalledValueInputStream(bais);
+      return mvis.readObject();
+   }
+   
+   /**
+    * Serializes a response object into a byte buffer, optimized for response marshalling.
+    * The object has to implement interface Serializable or Externalizable
+    */
+   private byte[] objectToByteBufferResponseInternal (Object obj) throws Exception
+   {
+      if (obj == null)
+      {
+         return new byte[]{NULL_VALUE};
+      }
+
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      // write a marker to stream to distinguish from null value stream
+      baos.write(SERIALIZABLE_VALUE);
+      MarshalledValueOutputStream mvos = new MarshalledValueOutputStream(baos);
+      mvos.writeObject(obj);
+      mvos.flush();
+      return baos.toByteArray();
+   }
+   
+   private void notifyChannelLock()
+   {
+      synchronized (this.channelLock)
+      {
+         this.channelLock.notifyAll();
+      }
+   }
+
+   private <T> ArrayList<T> processResponseList(RspList rspList, Class<T> returnType, boolean trace)
+   {
+      if (returnType == null)
+      {
+         // Use void.class as return type; a call to void.class.cast(object)
+         // below will throw CCE for anything other than null response
+         @SuppressWarnings("unchecked")
+         Class<T> unchecked = (Class<T>) void.class;
+         returnType = unchecked;
+      }
+      
+      ArrayList<T> rtn = new ArrayList<T>();
+      if (rspList != null)
+      {
+         for (Rsp<?> response : rspList.values())
+         {
+            // Only include received responses
+            if(response.wasReceived())
+            {
+               Object item = response.getValue();
+               if (item instanceof NoHandlerForRPC)
+               {
+                  continue;
+               }
+               else
+               {
+                  rtn.add(returnType.cast(item));
+               }
+            }
+            else if( trace )
+            {
+               this.log.trace("Ignoring non-received response: "+response);
+            }
+         }
+         
+      }
+      return rtn;
+   }
+   
+   private GroupView processViewChange(View newView) throws Exception
+   {
+      GroupView oldMembers = this.groupView;
+      GroupView newGroupView = new GroupView(newView, oldMembers, this.nodeFactory);
+
+      this.logHistory ("New view: " + newGroupView.allMembers + " with viewId: " + newGroupView.viewId +
+            " (old view: " + newGroupView.allMembers + " )");
+      
+      this.groupView = newGroupView;
+      
+      if (oldMembers.viewId == -1)
+      {
+         // Initial viewAccepted
+         this.log.debug("ViewAccepted: initial members set for partition " + this.getGroupName() + ": " +
+               newGroupView.viewId + " (" + this.groupView + ")");
+         
+         this.log.info("Number of cluster members: " + newGroupView.allMembers.size());
+         for(ClusterNode node : newGroupView.allMembers)
+         {
+            this.log.debug(node);
+         }
+         
+         // Wake up the deployer thread blocking in waitForView
+         this.notifyChannelLock();
+      }
+      else
+      {
+         int difference = newGroupView.allMembers.size() - oldMembers.allMembers.size();
+         
+         boolean merge = newView instanceof MergeView;
+         if (this.isCurrentNodeCoordinator ())
+         {
+            this.clusterLifeCycleLog.info ("New cluster view for partition " + this.getGroupName() + " (id: " +
+                  newGroupView.viewId + ", delta: " + difference + ", merge: " + merge + ") : " + newGroupView.allMembers);
+         }
+         else
+         {
+            this.log.info("New cluster view for partition " + this.getGroupName() + ": " +
+                  newGroupView.viewId + " (" + this.groupView + " delta: " + difference + ", merge: " + merge + ")");
+         }
+         
+         this.log.debug("dead members: " + newGroupView.deadMembers);
+         this.log.debug("membership changed from " + oldMembers.allMembers.size() + " to " + newGroupView.allMembers.size());
+         // Put the view change to the asynch queue
+         this.asynchHandler.queueEvent(newGroupView);
+
+
+         // Broadcast the new view to the synchronous view change listeners
+         if (this.allowSyncListeners)
+         {
+            this.notifyListeners(this.syncMembershipListeners, newGroupView.viewId, newGroupView.allMembers,
+                  newGroupView.deadMembers, newGroupView.newMembers, newGroupView.originatingGroups);
+         }
+      }
+
+
+      return newGroupView;
+   }
+
+   private void waitForView() throws Exception
+   {
+      boolean intr = false;
+      try
+      {
+         synchronized (this.channelLock)
+         {
+            if (this.getCurrentViewId() == -1)
+            {
+               try
+               {
+                  this.channelLock.wait(this.getMethodCallTimeout());
+               }
+               catch (InterruptedException iex)
+               {
+                  intr = true;
+               }
+
+               if (this.groupView == null)
+               {
+                  throw new IllegalStateException("No view received from Channel");
+               }
+            }
+         }
+      }
+      finally
+      {
+         if (intr) Thread.currentThread().interrupt();
+      }
+   }
+   
+   private void setupLoggers(String partitionName)
+   {
+      if (partitionName == null)
+      {
+         this.log = Logger.getLogger(getClass().getName());
+         this.clusterLifeCycleLog = Logger.getLogger(getClass().getName() + ".lifecycle");
+      }
+      else
+      {
+         this.log = Logger.getLogger(getClass().getName() + "." + partitionName);
+         this.clusterLifeCycleLog = Logger.getLogger(getClass().getName() + ".lifecycle." + partitionName);
+      }
+   }
+
+   private void verifyNodeIsUnique () throws IllegalStateException
+   {
+      ClusterNodeImpl matched = null;
+      for (ClusterNode member : this.getClusterNodes())
+      {
+         if (member.equals(this.me))
+         {
+            if (matched == null)
+            {
+               // We of course are in the view, so we expect one match
+               // Just track that we've had one
+               matched = (ClusterNodeImpl) member;
+            }
+            else
+            {
+               // Two nodes in view match us; try to figure out which one isn't us
+               ClusterNodeImpl other = matched;
+               if (other.getOriginalJGAddress().equals(((ClusterNodeImpl)this.me).getOriginalJGAddress()))
+               {
+                  other = (ClusterNodeImpl) member;
+               }
+               throw new IllegalStateException("Found member " + other +
+                     " in current view that duplicates us (" + this.me + "). This" +
+                     " node cannot join partition until duplicate member has" +
+                     " been removed");
+            }
+         }
+      }
+   }
+
+   private static Vector<ClusterNode> translateAddresses(Vector<Address> addresses, ClusterNodeFactory factory)
+   {
+      if (addresses == null)
+      {
+         return null;
+      }
+
+      Vector<ClusterNode> result = new Vector<ClusterNode>(addresses.size());
+      for (Address address: addresses)
+      {
+         result.add(factory.getClusterNode(address));
+      }
+
+      return result;
+   }
+
+   /**
+    * Helper method that returns a vector of dead members from two input vectors: new and old vectors of two views.
+    * Dead members are old - new members.
+    * @param oldMembers Vector of old members
+    * @param newMembers Vector of new members
+    * @return Vector of members that have died between the two views, can be empty.
+    */
+   private static Vector<ClusterNode> getDeadMembers(Vector<ClusterNode> oldMembers, Vector<ClusterNode> newMembers)
+   {
+      if(oldMembers == null)
+      {
+         oldMembers=new Vector<ClusterNode>();
+      }
+      if(newMembers == null)
+      {
+         newMembers=new Vector<ClusterNode>();
+      }
+      Vector<ClusterNode> dead= cloneMembers(oldMembers);
+      dead.removeAll(newMembers);
+      return dead;
+   }
+   
+   /**
+    * Helper method that returns a vector of new members from two input vectors: new and old vectors of two views.
+    * @param oldMembers Vector of old members
+    * @param allMembers Vector of new members
+    * @return Vector of members that have joined the partition between the two views
+    */
+   private static Vector<ClusterNode> getNewMembers(Vector<ClusterNode> oldMembers, Vector<ClusterNode> allMembers)
+   {
+      if(oldMembers == null)
+      {
+         oldMembers=new Vector<ClusterNode>();
+      }
+      if(allMembers == null)
+      {
+         allMembers=new Vector<ClusterNode>();
+      }
+      Vector<ClusterNode> newMembers= cloneMembers(allMembers);
+      newMembers.removeAll(oldMembers);
+      return newMembers;
+   }
+
+   private void notifyListeners(ArrayList<GroupMembershipListener> theListeners, long viewID,
+      Vector<ClusterNode> allMembers, Vector<ClusterNode> deadMembers, Vector<ClusterNode> newMembers,
+      Vector<List<ClusterNode>> originatingGroups)
+   {
+      this.log.debug("Begin notifyListeners, viewID: "+viewID);
+      List<GroupMembershipListener> toNotify = null;
+      synchronized(theListeners)
+      {
+         // JBAS-3619 -- don't hold synch lock while notifying
+         toNotify = cloneListeners(theListeners);
+      }
+      
+      for (GroupMembershipListener aListener : toNotify)
+      {
+         try
+         {
+            if(originatingGroups != null)
+            {
+               aListener.membershipChangedDuringMerge (deadMembers, newMembers,
+                  allMembers, originatingGroups);
+            }
+            else
+            {
+               aListener.membershipChanged(deadMembers, newMembers, allMembers);
+            }
+         }
+         catch (Throwable e)
+         {
+            // a problem in a listener should not prevent other members to receive the new view
+            this.log.warn("Membership listener callback failure: "+aListener, e);
+         }
+      }
+      
+      this.log.debug("End notifyListeners, viewID: "+viewID);
+   }
+   
+   @SuppressWarnings("unchecked")
+   private static Vector<Address> cloneMembers(View view)
+   {
+      return (Vector<Address>) view.getMembers().clone();
+   }
+   
+   @SuppressWarnings("unchecked")
+   private static Vector<ClusterNode> cloneMembers(Vector<ClusterNode> toClone)
+   {
+      return (Vector<ClusterNode>) toClone.clone();
+   }
+   
+   @SuppressWarnings("unchecked")
+   private static List<GroupMembershipListener> cloneListeners(ArrayList<GroupMembershipListener> toClone)
+   {
+      return (List<GroupMembershipListener>) toClone.clone();
+   }
+   
+   // Inner classes -------------------------------------------------
+   
+   /**
+    * A simple data class containing the current view information as well as
+    * change information needed to notify the GroupMembershipListeners about
+    * the event that led to this view.
+    */
+   protected static class GroupView
+   {
+      protected final long viewId;
+      protected final Vector<ClusterNode> deadMembers;
+      protected final Vector<ClusterNode> newMembers;
+      protected final Vector<ClusterNode> allMembers;
+      protected final Vector<List<ClusterNode>> originatingGroups;
+      protected final Vector<Address> jgmembers;
+      protected final Address coordinator;
+      
+      private GroupView()
+      {
+         this.viewId = -1;
+         this.deadMembers = new Vector<ClusterNode>();
+         this.newMembers = this.allMembers = new Vector<ClusterNode>();
+         this.jgmembers = new Vector<Address>();
+         this.coordinator = null;
+         this.originatingGroups = null;
+      }
+      
+      private GroupView(View newView, GroupView previousView, ClusterNodeFactory factory)
+      {
+         this.viewId = newView.getVid().getId();
+         this.jgmembers = cloneMembers(newView);
+         this.coordinator = this.jgmembers.size() == 0 ? null : this.jgmembers.elementAt(0);
+         this.allMembers = translateAddresses(newView.getMembers(), factory);
+         this.deadMembers = getDeadMembers(previousView.allMembers, allMembers);
+         this.newMembers = getNewMembers(previousView.allMembers, allMembers);
+         if(newView instanceof MergeView)
+         {
+            MergeView mergeView = (MergeView) newView;
+            Vector<View> subgroups = mergeView.getSubgroups();
+            this.originatingGroups = new Vector<List<ClusterNode>>(subgroups.size());
+            for (View view : subgroups)
+            {
+               this.originatingGroups.add(translateAddresses(view.getMembers(), factory));
+            }
+         }
+         else
+         {
+            this.originatingGroups = null;
+         }
+      }
+   }
+   
+   /**
+    * Marshalls request payloads for transmission across the cluster.
+    */
+   private class RequestMarshallerImpl implements org.jgroups.blocks.RpcDispatcher.Marshaller
+   {
+
+      public Object objectFromByteBuffer(byte[] buf) throws Exception
+      {
+         return CoreGroupCommunicationService.this.objectFromByteBufferInternal(buf);
+      }
+
+      public byte[] objectToByteBuffer(Object obj) throws Exception
+      {
+         // wrap MethodCall in Object[service_name, byte[]] so that service name is available during demarshalling
+         if (obj instanceof MethodCall)
+         {
+            String name = ((MethodCall)obj).getName();
+            int idx = name.lastIndexOf('.');
+            String serviceName = name.substring(0, idx);
+            return CoreGroupCommunicationService.this.objectToByteBufferInternal(new Object[]{serviceName, CoreGroupCommunicationService.this.objectToByteBufferInternal(obj)});
+         }
+
+         return CoreGroupCommunicationService.this.objectToByteBufferInternal(obj);
+      }
+   }
+   
+   /**
+    * Marshalls responses for transmission across the cluster.
+    */
+   private class ResponseMarshallerImpl implements org.jgroups.blocks.RpcDispatcher.Marshaller
+   {
+      
+      public Object objectFromByteBuffer(byte[] buf) throws Exception
+      {
+         boolean trace = CoreGroupCommunicationService.this.log.isTraceEnabled();
+         Object retval = CoreGroupCommunicationService.this.objectFromByteBufferResponseInternal(buf);
+         // HAServiceResponse is only received when a scoped classloader is required for unmarshalling
+         if (!(retval instanceof HAServiceResponse))
+         {
+            return retval;
+         }
+          
+         String serviceName = ((HAServiceResponse)retval).getServiceName();
+         byte[] payload = ((HAServiceResponse)retval).getPayload();
+
+         ClassLoader previousCL = null;
+         boolean overrideCL = false;
+         try
+         {
+            WeakReference<ClassLoader> weak = CoreGroupCommunicationService.this.clmap.get(serviceName);
+            if (weak != null) // this should always be true since we only use HAServiceResponse when classloader is specified
+            {
+               previousCL = Thread.currentThread().getContextClassLoader();
+               ClassLoader loader = weak.get();
+               if( trace )
+               {
+                  CoreGroupCommunicationService.this.log.trace("overriding response Thread ContextClassLoader for service " + serviceName);
+               }
+               overrideCL = true;
+               Thread.currentThread().setContextClassLoader(loader);
+            }
+            retval = CoreGroupCommunicationService.this.objectFromByteBufferResponseInternal(payload);
+   
+            return retval;
+         }
+         finally
+         {
+            if (overrideCL == true)
+            {
+               CoreGroupCommunicationService.this.log.trace("resetting response classloader");
+               Thread.currentThread().setContextClassLoader(previousCL);
+            }
+         }
+      }
+
+      public byte[] objectToByteBuffer(Object obj) throws Exception
+      {
+         return CoreGroupCommunicationService.this.objectToByteBufferResponseInternal(obj);
+      }
+   }
+   
+   /**
+    * Overrides RpcDispatcher.Handle so that we can dispatch to many
+    * different objects.
+    */
+   private class RpcHandler extends MuxRpcDispatcher implements StateTransferFilter
+   {
+      private final short scopeId;
+      private RpcHandler(short scopeId, Channel channel, MessageListener messageListener, MembershipListener membershipListener, Object serverObject,
+            boolean deadlock_detection)
+      {
+         super(scopeId);
+         this.scopeId = scopeId;
+         
+         setMessageListener(messageListener);
+         setMembershipListener(membershipListener);
+         setServerObject(serverObject);
+         setChannel(channel);
+         channel.addChannelListener(this);
+         start();
+      }
+      
+      /**
+       * Analyze the MethodCall contained in <code>req</code> to find the
+       * registered service object to invoke against, and then execute it
+       * against *that* object and return result.
+       *
+       * This overrides RpcDispatcher.Handle so that we can dispatch to many different objects.
+       * @param req The org.jgroups. representation of the method invocation
+       * @return The serializable return value from the invocation
+       */
+      public Object handle(Message req)
+      {
+         Object body = null;
+         Object retval = null;
+         Object handler = null;
+         boolean trace = this.log.isTraceEnabled();
+         boolean overrideCL = false;
+         ClassLoader previousCL = null;
+         String service = null;
+         byte[] request_bytes = null;
+         
+         if( trace )
+         {
+            this.log.trace("Partition " + CoreGroupCommunicationService.this.getGroupName() + " received msg");
+         }
+         if(req == null || req.getBuffer() == null)
+         {
+            this.log.warn("Partition " + CoreGroupCommunicationService.this.getGroupName() + " message or message buffer is null!");
+            return null;
+         }
+         
+         try
+         {
+            Object wrapper = CoreGroupCommunicationService.this.objectFromByteBufferInternal(req.getBuffer());
+            if(wrapper == null || !(wrapper instanceof Object[]))
+            {
+               this.log.warn("Partition " + CoreGroupCommunicationService.this.getGroupName() + " message wrapper does not contain Object[] object!");
+               return null;
+            }
+
+            // wrapper should be Object[]{service_name, byte[]}
+            Object[] temp = (Object[])wrapper;
+            service = (String)temp[0];
+            request_bytes = (byte[])temp[1];
+
+            // see if this node has registered to handle this service
+            handler = CoreGroupCommunicationService.this.rpcHandlers.get(service);
+            if (handler == null)
+            {
+               if( trace )
+               {
+                  this.log.trace("Partition " + CoreGroupCommunicationService.this.getGroupName() + " no rpc handler registered under service " + service);
+               }
+               return new NoHandlerForRPC();
+            }
+         }
+         catch(Exception e)
+         {
+            this.log.warn("Partition " + CoreGroupCommunicationService.this.getGroupName() + " failed unserializing message buffer (msg=" + req + ")", e);
+            return null;
+         }
+         
+         try
+         {
+            // If client registered the service with a classloader, override the thread classloader here
+            WeakReference<ClassLoader> weak = CoreGroupCommunicationService.this.clmap.get(service);
+            if (weak != null)
+            {
+               if( trace )
+               {
+                  this.log.trace("overriding Thread ContextClassLoader for RPC service " + service);
+               }
+               previousCL = Thread.currentThread().getContextClassLoader();
+               ClassLoader loader = weak.get();
+               overrideCL = true;
+               Thread.currentThread().setContextClassLoader(loader);
+            }
+            body = CoreGroupCommunicationService.this.objectFromByteBufferInternal(request_bytes);
+         }
+         catch (Exception e)
+         {
+            this.log.warn("Partition " + CoreGroupCommunicationService.this.getGroupName() + " failed extracting message body from request bytes", e);
+            return null;
+         }
+         finally
+         {
+            if (overrideCL)
+            {
+               this.log.trace("resetting Thread ContextClassLoader");
+               Thread.currentThread().setContextClassLoader(previousCL);
+            }
+         }
+         
+         if(body == null || !(body instanceof MethodCall))
+         {
+            this.log.warn("Partition " + CoreGroupCommunicationService.this.getGroupName() + " message does not contain a MethodCall object!");
+            return null;
+         }
+         
+         // get method call information
+         MethodCall method_call = (MethodCall)body;
+         String methodName = method_call.getName();
+         
+         if( trace )
+         {
+            this.log.trace("full methodName: " + methodName);
+         }
+         
+         int idx = methodName.lastIndexOf('.');
+         String handlerName = methodName.substring(0, idx);
+         String newMethodName = methodName.substring(idx + 1);
+         if( trace )
+         {
+            this.log.trace("handlerName: " + handlerName + " methodName: " + newMethodName);
+            this.log.trace("Handle: " + methodName);
+         }
+         
+         // prepare method call
+         method_call.setName(newMethodName);
+
+         /* Invoke it and just return any exception with trace level logging of
+         the exception. The exception semantics of a group rpc call are weak as
+         the return value may be a normal return value or the exception thrown.
+         */
+         try
+         {
+            retval = method_call.invoke(handler);
+            if (overrideCL)
+            {
+               // wrap the response so that the service name can be accessed during unmarshalling of the response
+               byte[] retbytes = CoreGroupCommunicationService.this.objectToByteBufferResponseInternal(retval);
+               retval = new HAServiceResponse(handlerName, retbytes);
+            }
+            if( trace )
+            {
+               this.log.trace("rpc call return value: " + retval);
+            }
+         }
+         catch (Throwable t)
+         {
+            if( trace )
+            {
+               this.log.trace("Partition " + CoreGroupCommunicationService.this.getGroupName() + " rpc call threw exception", t);
+            }
+            retval = t;
+         }
+
+         return retval;
+      }
+      
+
+
+      @Override
+      public void start() {
+          super.start();
+          // Replace the handler again! FIXME get this in superclass
+          Muxer<UpHandler> muxer = this.getMuxer();
+          if (muxer != null) {
+              muxer.add(scopeId, new DelegatingStateTransferUpHandler(this.getProtocolAdapter(), this));
+          }
+      }
+      
+      public boolean accepts(String stateId)
+      {
+         return stateId != null && stateId.startsWith(CoreGroupCommunicationService.this.stateIdPrefix );
+      }
+
+      private Muxer<UpHandler> getMuxer() {
+          UpHandler handler = channel.getUpHandler();
+          return ((handler != null) && (handler instanceof MuxUpHandler)) ? (MuxUpHandler) handler : null;
+      }
+      
+   }
+   
+   /**
+    * Handles callbacks from the thread that asynchronously deals with
+    * view change events.
+    */
+   private class ViewChangeEventProcessor implements AsynchEventHandler.AsynchEventProcessor
+   {
+      public void processEvent(Object event)
+      {
+         GroupView vce = (GroupView) event;
+         CoreGroupCommunicationService.this.notifyListeners(CoreGroupCommunicationService.this.asyncMembershipListeners, vce.viewId, vce.allMembers,
+               vce.deadMembers, vce.newMembers, vce.originatingGroups);
+         
+      }
+   }
+   
+   /**
+    * Copyright (c) 2005 Brian Goetz and Tim Peierls
+    * Released under the Creative Commons Attribution License
+    * (http://creativecommons.org/licenses/by/2.5)
+    * Official home: http://www.jcip.net
+    * 
+    * ThreadGate <p/> Recloseable gate using wait and notifyAll
+    * 
+    * @author Brian Goetz and Tim Peierls
+    */
+
+   private static class ThreadGate
+   {
+      private static final int OPEN = 1;
+      private static final int CLOSED = -1;
+      
+      private static class Sync extends AbstractQueuedSynchronizer
+      {
+         /** The serialVersionUID */
+         private static final long serialVersionUID = 1L;
+         
+         Sync(int state)
+         {
+            this.setState(state);
+         }
+         
+         @Override
+         protected int tryAcquireShared(int ingored)
+         {
+            return this.getState();
+         }
+
+         @Override
+         protected boolean tryReleaseShared(int state)
+         {
+            this.setState(state);
+            return true;
+         }
+      }
+
+      private final Sync sync = new Sync(CLOSED);
+      
+      public void open()
+      {
+         this.sync.releaseShared(OPEN);
+      }
+      
+      public void close()
+      {
+         this.sync.releaseShared(CLOSED);
+      }
+      
+      public boolean await(long timeout) throws InterruptedException
+      {
+         return this.sync.tryAcquireSharedNanos(0, TimeUnit.MILLISECONDS.toNanos(timeout));
+      }
+   }
+   
+   /**
+    * Converts JGroups address objects into ClusterNode
+    */
+   private class ClusterNodeFactoryImpl implements ClusterNodeFactory
+   {
+      private final ConcurrentMap<Address, IpAddress> addressMap = new ConcurrentHashMap<Address, IpAddress>();
+      
+      public ClusterNode getClusterNode(Address a)
+      {
+         IpAddress result = addressMap.get(a);
+         if (result == null)
+         {
+            result = (IpAddress) channel.downcall(new Event(Event.GET_PHYSICAL_ADDRESS, a));
+            if (result == null)
+            {
+               throw new IllegalStateException("Address " + a + "not registered in transport layer");
+            }
+            addressMap.put(a, result);
+         }
+         AddressPort addrPort = new AddressPort(result.getIpAddress(), result.getPort());
+         String id = channel.getName(a);
+         if (id == null)
+         {
+            id = addrPort.getHostAddress() + ":" + addrPort.getPort();
+         }
+         return new ClusterNodeImpl(id, a, addrPort);
+      }
+   }
+   
+   /**
+    * Returned when an RPC call arrives for a service that isn't registered.
+    */
+   public static class NoHandlerForRPC implements Serializable
+   {
+      static final long serialVersionUID = -1263095408483622838L;
+   }
+   
+   /**
+    * Used internally when an RPC call requires a custom classloader for unmarshalling
+    */
+   private static class HAServiceResponse implements Serializable
+   {
+      private static final long serialVersionUID = -6485594652749906437L;
+      private final String serviceName;
+      private final byte[] payload;
+           
+      public HAServiceResponse(String serviceName, byte[] payload)
+      {
+         this.serviceName = serviceName;
+         this.payload = payload;
+      }
+           
+      public String getServiceName()
+      {
+         return this.serviceName;
+      }
+           
+      public byte[] getPayload()
+      {
+         return this.payload;
+      }
+   } 
+   
+   /**
+    * Handles MembershipListener callbacks from JGroups Channel
+    */   
+   private class MembershipListenerImpl implements ExtendedMembershipListener
+   {
+      public void suspect(org.jgroups.Address suspected_mbr)
+      {
+         CoreGroupCommunicationService.this.logHistory ("Node suspected: " + (suspected_mbr==null?"null":suspected_mbr.toString()));
+         if (CoreGroupCommunicationService.this.isCurrentNodeCoordinator ())
+         {
+            CoreGroupCommunicationService.this.clusterLifeCycleLog.info ("Suspected member: " + suspected_mbr);
+         }
+         else
+         {
+            CoreGroupCommunicationService.this.log.info("Suspected member: " + suspected_mbr);
+         }
+      }
+   
+      public void block()
+      {
+         CoreGroupCommunicationService.this.flushBlockGate.close();
+         CoreGroupCommunicationService.this.log.debug("Block processed at " + CoreGroupCommunicationService.this.me);
+      }
+      
+      public void unblock()
+      {
+         CoreGroupCommunicationService.this.flushBlockGate.open();
+         CoreGroupCommunicationService.this.log.debug("Unblock processed at " + CoreGroupCommunicationService.this.me);
+      }
+      
+      /** 
+       * Notification of a cluster view change. This is done from the JG protocol
+       * handler thread and we must be careful to not unduly block this thread.
+       * Because of this there are two types of listeners, synchronous and
+       * asynchronous. The synchronous listeners are messaged with the view change
+       * event using the calling thread while the asynchronous listeners are
+       * messaged using a separate thread.
+       *
+       * @param newView
+       */
+      public void viewAccepted(View newView)
+      {
+         try
+         {
+            processViewChange(newView);
+         }
+         catch (InterruptedException ex)
+         {
+            Thread.currentThread().interrupt();
+            CoreGroupCommunicationService.this.log.error("ViewAccepted failed", ex);
+         }
+         catch (Exception ex)
+         {
+            CoreGroupCommunicationService.this.log.error("ViewAccepted failed", ex);
+         }
+      }
+   }
+
+   /**
+    * Handles MessageListener callbacks from the JGroups layer.
+    */
+   private class MessageListenerAdapter
+         implements ExtendedMessageListener
+   {
+
+      public void receive(org.jgroups.Message msg)
+      { 
+         // no-op
+      }
+      
+      public void getState(String state_id, OutputStream ostream)
+      {
+         // FIXME refactor to share logic
+         CoreGroupCommunicationService.this.log.debug("getState called for service " + state_id);
+         
+         StateTransferProvider provider = stateProviders.get(state_id);
+         if (provider != null)
+         {
+            MarshalledValueOutputStream mvos = null;
+            // FIXME add a streaming api to StateTransferProvider
+            Object state = provider.getCurrentState();
+            try
+            {
+               mvos = new MarshalledValueOutputStream(ostream);
+               mvos.writeObject(state);
+            }
+            catch (Exception ex)
+            {
+               CoreGroupCommunicationService.this.log.error("getState failed for service " + state_id, ex);
+            }
+            finally
+            {
+               if (mvos != null)
+               {
+                  try
+                  {
+                     mvos.flush();
+                     mvos.close();
+                  }
+                  catch (IOException ignored)
+                  {
+                     log.debug("Caught exception closing stream used for marshalling state", ignored);
+                  }
+               }
+            }
+         }
+      }
+
+      public byte[] getState(String state_id)
+      {
+         CoreGroupCommunicationService.this.log.debug("getState called for service " + state_id);
+         
+         StateTransferProvider provider = stateProviders.get(state_id);
+         if (provider != null)
+         {
+            MarshalledValueOutputStream mvos = null;
+            Object state = provider.getCurrentState();
+            try
+            {
+               ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+               mvos = new MarshalledValueOutputStream(baos);
+               mvos.writeObject(state);
+               mvos.flush();
+               mvos.close();
+               return baos.toByteArray();
+            }
+            catch (Exception ex)
+            {
+               CoreGroupCommunicationService.this.log.error("getState failed for service " + state_id, ex);
+            }
+            finally
+            {
+               if (mvos != null)
+               {
+                  try
+                  {
+                     mvos.close();
+                  }
+                  catch (IOException ignored)
+                  {
+                     log.debug("Caught exception closing stream used for marshalling state", ignored);
+                  }
+               }
+            }
+         }
+
+         return null; // This will cause the receiver to get a "false" on the channel.getState() call
+      }
+
+      public void setState(String state_id, byte[] state)
+      {
+         StateTransferTask task = CoreGroupCommunicationService.this.stateTransferTasks.get(state_id);
+         if (task == null)
+         {
+            CoreGroupCommunicationService.this.log.warn("No " + StateTransferTask.class.getSimpleName() + 
+                  " registered to receive state for service " + state_id);
+         }
+         else
+         {
+            task.setState(state);
+         }
+      }
+
+      public void setState(String state_id, InputStream istream)
+      {
+         StateTransferTask task = CoreGroupCommunicationService.this.stateTransferTasks.get(state_id);
+         if (task == null)
+         {
+            CoreGroupCommunicationService.this.log.warn("No " + StateTransferTask.class.getSimpleName() + 
+                  " registered to receive state for service " + state_id);
+         }
+         else
+         {
+            task.setState(istream);
+         }
+      }
+
+      public byte[] getState()
+      {
+         throw new UnsupportedOperationException("Only partial state transfer (with a state_id) is supported");
+      }
+      
+      public void getState(OutputStream stream)
+      {
+         throw new UnsupportedOperationException("Only partial state transfer (with a state_id) is supported");         
+      }
+      
+      public void setState(byte[] obj)
+      {
+         throw new UnsupportedOperationException("Only partial state transfer (with a state_id) is supported");    
+      }
+      
+      public void setState(InputStream stream)
+      {
+         throw new UnsupportedOperationException("Only partial state transfer (with a state_id) is supported");
+      }
+      
+   }
+   
+   /**
+    * Allows a state transfer request to be executed asynchronously.
+    */
+   private class StateTransferTask implements Callable<Serializable>
+   {
+      private final String serviceName;
+      private final WeakReference<ClassLoader> classloader;
+      private Serializable result;
+      private boolean isStateSet;
+      private Exception setStateException;
+      
+      StateTransferTask(String serviceName, ClassLoader cl)
+      {
+         this.serviceName = serviceName;
+         if (cl != null)
+         {
+            classloader = null;
+         }
+         else
+         {
+            classloader = new WeakReference<ClassLoader>(cl);
+         }
+      }
+
+      public Serializable call() throws Exception
+      {
+         boolean intr = false;
+         try
+         {
+            long start, stop;
+            this.isStateSet = false;
+            start = System.currentTimeMillis();
+            boolean rc = CoreGroupCommunicationService.this.getChannel().getState(null, serviceName, CoreGroupCommunicationService.this.getStateTransferTimeout());
+            if (rc)
+            {
+               synchronized (this)
+               {
+                  while (!this.isStateSet)
+                  {
+                     if (this.setStateException != null)
+                     {
+                        throw this.setStateException;
+                     }
+
+                     try
+                     {
+                        wait();
+                     }
+                     catch (InterruptedException iex)
+                     {
+                        intr = true;
+                     }
+                  }
+               }
+               stop = System.currentTimeMillis();
+               CoreGroupCommunicationService.this.log.debug("serviceState was retrieved successfully (in " + (stop - start) + " milliseconds)");
+            }
+            else
+            {
+               // No one provided us with serviceState.
+               // We need to find out if we are the coordinator, so we must
+               // block until viewAccepted() is called at least once
+               
+               synchronized (CoreGroupCommunicationService.this.channelLock)
+               {
+                  while (CoreGroupCommunicationService.this.getCurrentView().size() == 0)
+                  {
+                     CoreGroupCommunicationService.this.log.debug("waiting on viewAccepted()");
+                     try
+                     {
+                        CoreGroupCommunicationService.this.channelLock.wait();
+                     }
+                     catch (InterruptedException iex)
+                     {
+                        intr = true;
+                     }
+                  }
+               }
+
+               if (CoreGroupCommunicationService.this.isCurrentNodeCoordinator())
+               {
+                  CoreGroupCommunicationService.this.log.debug("State could not be retrieved for service " + serviceName + " (we are the first member in group)");
+               }
+               else
+               {
+                  throw new IllegalStateException("Initial serviceState transfer failed: " +
+                     "Channel.getState() returned false");
+               }
+            }
+         }
+         finally
+         {
+            if (intr) Thread.currentThread().interrupt();
+         }
+         
+         return result;
+      }     
+      
+      void setState(byte[] state)
+      {
+         try
+         {
+            if (state == null)
+            {
+               CoreGroupCommunicationService.this.log.debug("transferred state for service " + 
+                     serviceName + " is null (may be first member in cluster)");
+            }
+            else
+            {
+               ByteArrayInputStream bais = new ByteArrayInputStream(state);
+               setStateInternal(bais);
+               bais.close();
+            }
+            
+            this.isStateSet = true;
+         }
+         catch (Throwable t)
+         {
+            recordSetStateFailure(t);
+         }
+         finally
+         {
+            // Notify waiting thread that serviceState has been set.
+            synchronized(this)
+            {
+               notifyAll();
+            }
+         }
+      }     
+      
+      void setState(InputStream state)
+      {
+         try
+         {
+            if (state == null)
+            {
+               CoreGroupCommunicationService.this.log.debug("transferred state for service " + 
+                     serviceName + " is null (may be first member in cluster)");
+            }
+            else
+            {
+               setStateInternal(state);
+            }
+            
+            this.isStateSet = true;
+         }
+         catch (Throwable t)
+         {
+            recordSetStateFailure(t);
+         }
+         finally
+         {
+            // Notify waiting thread that serviceState has been set.
+            synchronized(this)
+            {
+               notifyAll();
+            }
+         }
+         
+      }
+      
+      private void setStateInternal(InputStream is) throws IOException, ClassNotFoundException
+      {
+         ClassLoader cl = getStateTransferClassLoader();
+         SwitchContext switchContext = CoreGroupCommunicationService.this.classLoaderSwitcher.getSwitchContext(cl);
+         try
+         {
+            MarshalledValueInputStream mvis = new MarshalledValueInputStream(is);
+            this.result = (Serializable) mvis.readObject();
+         }
+         finally
+         {
+            switchContext.reset();
+         }
+      }
+
+      private void recordSetStateFailure(Throwable t)
+      {
+         CoreGroupCommunicationService.this.log.error("failed setting serviceState for service " + serviceName, t);
+         if (t instanceof Exception)
+         {
+            this.setStateException = (Exception) t;
+         }
+         else
+         {
+            this.setStateException = new Exception(t);
+         }
+      }
+      
+      private ClassLoader getStateTransferClassLoader()
+      {
+         ClassLoader cl = classloader == null ? null : classloader.get();
+         if (cl == null)
+         {
+            cl = this.getClass().getClassLoader();
+         }
+         return cl;
+      }  
+   }
+   
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DelegatingStateTransferUpHandler.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DelegatingStateTransferUpHandler.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DelegatingStateTransferUpHandler.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, Red Hat, Inc. 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.ha.core.framework.server;
+
+import org.jgroups.Event;
+import org.jgroups.UpHandler;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision$
+ */
+public class DelegatingStateTransferUpHandler implements StateTransferFilter, UpHandler
+{
+   private final UpHandler delegate;
+   private final StateTransferFilter filter;
+   
+   public DelegatingStateTransferUpHandler(UpHandler delegate, StateTransferFilter filter)
+   {
+      this.delegate = delegate;
+      this.filter = filter;
+   }
+
+   public Object up(Event evt)
+   {
+      return delegate.up(evt);
+   }
+
+   public boolean accepts(String stateId)
+   {
+      return filter.accepts(stateId);
+   }
+
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImpl.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/DistributedReplicantManagerImpl.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImpl.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,1123 @@
+/*
+ * 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.ha.core.framework.server;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.logging.Logger;
+
+
+/**
+ * This class manages replicated objects.
+ * 
+ * @author  <a href="mailto:bill at burkecentral.com">Bill Burke</a>.
+ * @author  <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @author  Scott.stark at jboss.org
+ * @author  <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @author  <a href="mailto:pferraro at redhat.com">Paul Ferraro</a>
+ * @version $Revision$
+ */
+public class DistributedReplicantManagerImpl
+   implements DistributedReplicantManagerImplMBean,
+              HAPartition.HAMembershipExtendedListener,
+              HAPartition.HAPartitionStateTransfer,
+              AsynchEventHandler.AsynchEventProcessor
+{
+   // Constants -----------------------------------------------------
+   
+   static final String OBJECT_NAME_BASE = "jboss:service=DistributedReplicantManager";
+   
+   static final String SERVICE_NAME = "DistributedReplicantManager";
+   
+   private static final Class<?>[] add_types = new Class<?>[] { String.class, String.class, Serializable.class };
+   private static final Class<?>[] remove_types = new Class<?>[] { String.class, String.class };
+
+   // Attributes ----------------------------------------------------
+   private static final AtomicInteger threadID = new AtomicInteger();
+   
+   private final ConcurrentMap<String, Serializable> localReplicants = new ConcurrentHashMap<String, Serializable>();
+   private final ConcurrentMap<String, ConcurrentMap<String, Serializable>> replicants = new ConcurrentHashMap<String, ConcurrentMap<String, Serializable>>();
+   private final ConcurrentMap<String, List<ReplicantListener>> keyListeners = new ConcurrentHashMap<String, List<ReplicantListener>>();
+   private Map<String, Integer> intraviewIdCache = new ConcurrentHashMap<String, Integer>();
+   
+   private final HAPartition partition;
+   /** The handler used to send replicant change notifications asynchronously */
+   private final AsynchEventHandler asynchHandler;
+   
+   private final Logger log;
+   
+   private String nodeName = null;
+   
+   // Works like a simple latch
+   private volatile CountDownLatch partitionNameKnown = new CountDownLatch(1);
+
+   // Static --------------------------------------------------------
+   
+   // Constructors --------------------------------------------------
+   
+   public DistributedReplicantManagerImpl(HAPartition partition)
+   {
+      super();
+      
+      if (partition == null)
+      {
+         throw new NullPointerException("partition is null");
+      }
+      
+      this.partition = partition;
+      this.log = Logger.getLogger(this.getClass().getName() + "." + partition.getPartitionName());
+      
+      // JBAS-5068 Create the handler early so we don't risk NPEs
+      this.asynchHandler = new AsynchEventHandler(this, "AsynchKeyChangeHandler");
+   }
+
+   // Public --------------------------------------------------------
+   
+   public void createService() throws Exception
+   {
+      if (this.partition == null)
+      {
+         throw new IllegalStateException("HAPartition property must be set before creating DistributedReplicantManager service");
+      }
+
+      this.log.debug("registerRPCHandler");
+      this.partition.registerRPCHandler(SERVICE_NAME, this);
+      this.log.debug("subscribeToStateTransferEvents");
+      this.partition.subscribeToStateTransferEvents(SERVICE_NAME, this);
+      this.log.debug("registerMembershipListener");
+      this.partition.registerMembershipListener(this);
+   }
+   
+   public void startService() throws Exception
+   {
+      this.nodeName = this.partition.getNodeName();
+      
+      this.asynchHandler.start();
+
+      this.partitionNameKnown.countDown(); // partition name is now known!
+      
+      //log.info("mergemembers");
+      //mergeMembers();
+   }
+   
+   public void stopService() throws Exception
+   {
+      // Stop the asynch handler thread
+      try
+      {
+         this.asynchHandler.stop();
+      }
+      catch( Exception e)
+      {
+         this.log.warn("Failed to stop asynchHandler", e);
+      }
+      
+      // Reset the latch
+      this.partitionNameKnown = new CountDownLatch(1);
+   }
+
+   // NR 200505 : [JBCLUSTER-38] unbind at destroy
+   public void destroyService() throws Exception
+   {
+      // we cleanly shutdown. This should be optimized.
+      for (String key: this.localReplicants.keySet())
+      {
+         this.removeLocal(key); // channel is disconnected, so don't try to notify cluster
+      }
+      
+      if (this.partition != null)
+      {
+         this.partition.unregisterRPCHandler(SERVICE_NAME, this);
+         this.partition.unsubscribeFromStateTransferEvents(SERVICE_NAME, this);
+         this.partition.unregisterMembershipListener(this);
+      }
+   }
+
+   public void registerWithJmx(MBeanServer server) throws Exception
+   {
+      server.registerMBean(this, this.getObjectName());
+   }
+   
+   public void unregisterWithJmx(MBeanServer server) throws Exception
+   {
+      server.unregisterMBean(this.getObjectName());
+   }
+   
+   private ObjectName getObjectName() throws Exception
+   {
+      return new ObjectName("jboss:service=" + SERVICE_NAME + ",partition=" + this.partition.getPartitionName());
+   }
+   
+//   @ManagementProperty(use={ViewUse.STATISTIC}, description="The partition's name")
+//   @ManagementObjectID(type="DistributedReplicantManager")
+   public String getPartitionName()
+   {
+      return this.partition.getPartitionName();
+   }
+
+//   public void setHAPartition(HAPartition clusterPartition)
+//   {
+//      this.partition = clusterPartition;
+//   }
+   
+//   @ManagementOperation(name="listDRMContent",
+//         description="List all known keys and the nodes that have registered bindings",
+//         impact=Impact.ReadOnly)
+   public String listContent() throws Exception
+   {
+      StringBuilder result = new StringBuilder();
+      
+      result.append("<pre>");
+
+      // we merge all replicants services: local only or not
+      //
+      for (String category: this.getAllServices())
+      {
+         result.append("-----------------------------------------------\n");
+         result.append("Service : ").append(category).append("\n\n");
+         
+         Serializable local = this.localReplicants.get(category);
+         
+         if (local == null)
+         {
+            result.append("\t- Service is *not* available locally\n");
+         }
+         else
+         {
+            result.append("\t- Service *is* also available locally\n");
+         }
+
+         Map<String, Serializable> content = this.replicants.get(category);
+         
+         if (content != null)
+         {
+            for (String location: content.keySet())
+            {
+               result.append("\t- ").append(location).append("\n");
+            }
+         }
+         
+         result.append ("\n");
+         
+      }
+      
+      result.append ("</pre>");
+      
+      return result.toString();
+   }
+   
+//   @ManagementOperation(name="listDRMContentAsXml",
+//         description="List in XML format all known services and the nodes that have registered bindings",
+//         impact=Impact.ReadOnly)
+   public String listXmlContent() throws Exception
+   {
+      StringBuilder result = new StringBuilder();
+      
+      result.append ("<ReplicantManager>\n");
+
+      // we merge all replicants services: local only or not
+      //
+      for (String category: this.getAllServices())
+      {
+         result.append("\t<Service>\n");
+         result.append("\t\t<ServiceName>").append(category).append("</ServiceName>\n");
+
+         Serializable local = this.localReplicants.get(category);
+         
+         if (local != null)
+         {
+            result.append("\t\t<Location>\n");
+            result.append("\t\t\t<Name local=\"True\">").append (this.nodeName).append ("</Name>\n");
+            result.append("\t\t</Location>\n");
+         }
+
+         Map<String, Serializable> content = this.replicants.get(category);
+         
+         if (content != null)
+         {
+            for (String location: content.keySet())
+            {
+               result.append("\t\t<Location>\n");
+               result.append("\t\t\t<Name local=\"False\">").append (location).append ("</Name>\n");
+               result.append("\t\t</Location>\n");
+            }
+         }
+         
+         result.append("\t</Service>\n");
+      }
+
+      result.append("</ReplicantManager>\n");
+      
+      return result.toString();
+   }
+
+   // HAPartition.HAPartitionStateTransfer implementation ----------------------------------------------
+   
+   public Serializable getCurrentState()
+   {
+      Map<String, ConcurrentMap<String, Serializable>> result = new HashMap<String, ConcurrentMap<String, Serializable>>();
+      
+      for (String category: this.getAllServices())
+      {
+         ConcurrentMap<String, Serializable> map = new ConcurrentHashMap<String, Serializable>();
+         
+         ConcurrentMap<String, Serializable> content = this.replicants.get(category);
+         
+         if (content != null)
+         {
+            map.putAll(content);
+         }
+         
+         Serializable local = this.localReplicants.get(category);
+         
+         if (local != null)
+         {
+            map.put(this.nodeName, local);
+         }
+         
+         result.put(category, map);
+      }
+      
+      // we add the intraviewid cache to the global result
+      //
+      return new Object[] { result, this.intraviewIdCache };
+   }
+
+   @SuppressWarnings("unchecked")
+   public void setCurrentState(Serializable newState)
+   {
+      Object[] globalState = (Object[]) newState;
+      Map<String, ConcurrentMap<String, Serializable>> map = (Map) globalState[0];
+      
+      this.replicants.putAll(map);
+      
+      this.intraviewIdCache = (Map) globalState[1];
+
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace(this.nodeName + ": received new state, will republish local replicants");
+      }
+      
+      new MembersPublisher().start();
+   }
+      
+//   @ManagementOperation(name="getAllDRMServices",
+//         description="Get a collection of the names of all keys for which we have bindings",
+//         impact=Impact.ReadOnly)
+   public Collection<String> getAllServices()
+   {
+      Set<String> services = new HashSet<String>();
+      services.addAll(this.localReplicants.keySet());
+      services.addAll(this.replicants.keySet());
+      return services;
+   }
+   
+   // HAPartition.HAMembershipListener implementation ----------------------------------------------
+
+   @SuppressWarnings("unchecked")
+   public void membershipChangedDuringMerge(Vector deadMembers, Vector newMembers, Vector allMembers, Vector originatingGroups)
+   {
+      // Here we only care about deadMembers.  Purge all replicant lists of deadMembers
+      // and then notify all listening nodes.
+      //
+      this.log.info("Merging partitions...");
+      this.log.info("Dead members: " + deadMembers.size());
+      this.log.info("Originating groups: " + originatingGroups);
+      this.purgeDeadMembers(deadMembers, true);
+      if (newMembers.size() > 0)
+      {
+         new MergeMembers().start();
+      }
+   }
+   
+   @SuppressWarnings("unchecked")
+   public void membershipChanged(Vector deadMembers, Vector newMembers, Vector allMembers)
+   {
+      // Here we only care about deadMembers.  Purge all replicant lists of deadMembers
+      // and then notify all listening nodes.
+      //
+      this.log.info("I am (" + this.nodeName + ") received membershipChanged event:");
+      this.log.info("Dead members: " + deadMembers.size() + " (" + deadMembers + ")");
+      this.log.info("New Members : " + newMembers.size()  + " (" + newMembers + ")");
+      this.log.info("All Members : " + allMembers.size()  + " (" + allMembers + ")");
+      this.purgeDeadMembers(deadMembers, false);
+      
+      // we don't need to merge members anymore
+   }
+   
+   // AsynchEventHandler.AsynchEventProcessor implementation -----------------
+   
+   public void processEvent(Object event)
+   {
+      KeyChangeEvent kce = (KeyChangeEvent) event;
+      this.notifyKeyListeners(kce.key, kce.replicants, kce.merge);
+   }
+   
+   static class KeyChangeEvent
+   {
+      String key;
+      List<Serializable> replicants;
+      boolean merge;
+   }
+   
+   // DistributedReplicantManager implementation ----------------------------------------------
+   
+   public void add(String key, Serializable replicant) throws Exception
+   {
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("add, key=" + key + ", value=" + replicant);
+      }
+      
+      this.partitionNameKnown.await(); // we don't propagate until our name is known
+      
+      Object[] args = { key, this.nodeName, replicant };
+      
+      this.partition.callMethodOnCluster(SERVICE_NAME, "_add", args, add_types, true);
+
+      List<Serializable> replicants = null;
+      
+      synchronized (this.localReplicants)
+      {
+         this.localReplicants.put(key, replicant);
+         
+         replicants = this.getReplicants(key);
+      }
+      
+      this.notifyKeyListeners(key, replicants, false);
+   }
+   
+   public void remove(String key) throws Exception
+   {
+      this.partitionNameKnown.await(); // we don't propagate until our name is known
+      
+      // optimisation: we don't make a costly network call
+      // if there is nothing to remove
+      if (this.localReplicants.containsKey(key))
+      {
+         Object[] args = { key, this.nodeName };
+         
+         this.partition.callAsynchMethodOnCluster(SERVICE_NAME, "_remove", args, remove_types, true);
+         
+         this.removeLocal(key);
+      }
+   }
+   
+   private void removeLocal(String key)
+   {
+      List<Serializable> replicants = null;
+      
+      synchronized (this.localReplicants)
+      {
+         if (this.localReplicants.remove(key) != null)
+         {
+            replicants = this.getReplicants(key);
+         }
+      }
+      
+      if (replicants != null)
+      {
+         this.notifyKeyListeners(key, replicants, false);
+      }
+   }
+   
+   public Serializable lookupLocalReplicant(String key)
+   {
+      return this.localReplicants.get(key);
+   }
+   
+   public List<Serializable> lookupReplicants(String key)
+   {
+      Serializable local = this.localReplicants.get(key);
+      
+      Map<String, Serializable> replicant = this.replicants.get(key);
+
+      if (replicant == null)
+      {
+         return (local != null) ? Collections.singletonList(local) : null;
+      }
+
+      // JBAS-2677. Put the replicants in view order.
+      ClusterNode[] nodes = this.partition.getClusterNodes();
+
+      List<Serializable> result = new ArrayList<Serializable>(nodes.length);
+      
+      for (ClusterNode node: nodes)
+      {
+         String name = node.getName();
+         
+         if (local != null && this.nodeName.equals(name))
+         {
+            result.add(local);
+         }
+         else
+         {
+            Serializable value = replicant.get(name);
+            
+            if (value != null)
+            {
+               result.add(value);
+            }
+         }
+      }
+      
+      return result;
+   }
+   
+   private List<Serializable> getReplicants(String key)
+   {
+      List<Serializable> result = this.lookupReplicants(key);
+      
+      if (result == null)
+      {
+         result = Collections.emptyList();
+      }
+      
+      return result;
+   }
+
+//   @ManagementOperation(name="lookupDRMNodeNames",
+//         description="Returns the names of the nodes that have registered objects under the given key",
+//                        impact=Impact.ReadOnly,
+//                        params={@ManagementParameter(name="key",
+//                                                     description="The name of the service")})
+   public List<String> lookupReplicantsNodeNames(String key)
+   {
+      List<ClusterNode> nodes = this.lookupReplicantsNodes(key);
+      
+      if (nodes == null) return null;
+      
+      List<String> nodeNames = new ArrayList<String>(nodes.size());
+      
+      for (ClusterNode node : nodes)
+      {
+         nodeNames.add(node.getName());
+      }
+      
+      return nodeNames;
+   }
+
+   public List<ClusterNode> lookupReplicantsNodes(String key)
+   {
+      boolean local = this.localReplicants.containsKey(key);
+      Map<String, Serializable> replicant = this.replicants.get(key);
+      
+      if (replicant == null)
+      {
+         return local ? Collections.singletonList(this.partition.getClusterNode()) : null;
+      }
+      
+      Set<String> keys = replicant.keySet();
+      ClusterNode[] nodes = this.partition.getClusterNodes();
+      List<ClusterNode> rtn = new ArrayList<ClusterNode>(nodes.length);
+
+      for (ClusterNode node : nodes)
+      {
+         String name = node.getName();
+         
+         if (local && this.nodeName.equals(name))
+         {
+            rtn.add(this.partition.getClusterNode());
+         }
+         else if (keys.contains(name))
+         {
+            rtn.add(node);
+         }
+      }
+      
+      return rtn;
+   }
+   
+   public void registerListener(String key, ReplicantListener subscriber)
+   {
+      List<ReplicantListener> list = new CopyOnWriteArrayList<ReplicantListener>();
+      
+      List<ReplicantListener> existing = this.keyListeners.putIfAbsent(key, list);
+      
+      ((existing != null) ? existing : list).add(subscriber);
+   }
+   
+   public void unregisterListener(String key, DistributedReplicantManager.ReplicantListener subscriber)
+   {
+      List<ReplicantListener> listeners = this.keyListeners.get(key);
+      
+      if (listeners != null)
+      {
+         listeners.remove(subscriber);
+         
+         this.keyListeners.remove(key, Collections.emptyList());
+      }
+   }
+   
+//   @ManagementOperation(name="getDRMServiceViewId",
+//         description="Returns a hash of the list of nodes that " +
+//   		                            "have registered an object for the given key",
+//   		                impact=Impact.ReadOnly,
+//                        params={@ManagementParameter(name="key",
+//                                                     description="The name of the service")})
+   public int getReplicantsViewId(String key)
+   {
+      Integer result = this.intraviewIdCache.get(key);
+      
+      return (result != null) ? result.intValue() : 0;
+   }
+   
+//   @ManagementOperation(name="isDRMMasterForService",
+//         description="Returns whether the DRM considers this node to be the master for the given service",
+//         impact=Impact.ReadOnly,
+//         params={@ManagementParameter(name="key", description="The name of the service")})
+   public boolean isMasterReplica(String key)
+   {
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("isMasterReplica, key=" + key);
+      }
+      // if I am not a replicant, I cannot be the master...
+      //
+      if (!this.localReplicants.containsKey(key))
+      {
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("no localReplicants, key=" + key + ", isMasterReplica=false");
+         }
+         return false;
+      }
+
+      Map<String, Serializable> repForKey = this.replicants.get(key);
+      if (repForKey == null)
+      {
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("no replicants, key=" + key + ", isMasterReplica=true");
+         }
+         return true;
+      }
+
+      List<String> allNodes = this.partition.getCurrentView();
+      for (String node: allNodes)
+      {
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("Testing member: " + node);
+         }
+         
+         if (repForKey.containsKey(node))
+         {
+            if (this.log.isTraceEnabled())
+            {
+               this.log.trace("Member found in replicaNodes, isMasterReplica=false");
+            }
+            return false;
+         }
+         else if (node.equals(this.nodeName))
+         {
+            if (this.log.isTraceEnabled())
+            {
+               this.log.trace("Member == nodeName, isMasterReplica=true");
+            }
+            return true;
+         }
+      }
+      return false;
+   }
+
+   // DistributedReplicantManager cluster callbacks ----------------------------------------------
+   
+   /**
+    * Cluster callback called when a new replicant is added on another node
+    * @param key Replicant key
+    * @param nodeName Node that add the current replicant
+    * @param replicant Serialized representation of the replicant
+    */
+   public void _add(String key, String nodeName, Serializable replicant)
+   {
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("_add(" + key + ", " + nodeName);
+      }
+      
+      KeyChangeEvent event = new KeyChangeEvent();
+      event.key = key;
+      
+      synchronized (this.replicants)
+      {
+         this.addReplicant(key, nodeName, replicant);
+         
+         event.replicants = this.getReplicants(key);
+      }
+      
+      try
+      {
+         this.asynchHandler.queueEvent(event);
+      }
+      catch (InterruptedException e)
+      {
+         Thread.currentThread().interrupt();
+         
+         this.log.error("_add failed", e);
+      }
+   }
+   
+   /**
+    * Cluster callback called when a replicant is removed by another node
+    * @param key Name of the replicant key
+    * @param nodeName Node that wants to remove its replicant for the give key
+    */
+   public void _remove(String key, String nodeName)
+   {
+      KeyChangeEvent event = new KeyChangeEvent();
+      event.key = key;
+      
+      synchronized (this.replicants)
+      {
+         if (this.removeReplicant(key, nodeName))
+         {
+            event.replicants = this.getReplicants(key);
+         }
+      }
+      
+      if (event.replicants != null)
+      {
+         try
+         {
+            this.asynchHandler.queueEvent(event);
+         }
+         catch (InterruptedException e)
+         {
+            Thread.currentThread().interrupt();
+            
+            this.log.error("_remove failed", e);
+         }
+      }
+   }
+   
+   protected boolean removeReplicant(String key, String nodeName)
+   {
+      Map<String, Serializable> replicant = this.replicants.get(key);
+      
+      if (replicant != null)
+      {
+         if (replicant.remove(nodeName) != null)
+         {
+            // If replicant map is empty, prune it
+            this.replicants.remove(key, Collections.emptyMap());
+            
+            return true;
+         }
+      }
+      
+      return false;
+   }
+   
+   /**
+    * Cluster callback called when a node wants to know our complete list of local replicants
+    * @throws Exception Thrown if a cluster communication exception occurs
+    * @return A java array of size 2 containing the name of our node in this cluster and the serialized representation of our state
+    */
+   public Object[] lookupLocalReplicants() throws Exception
+   {
+      this.partitionNameKnown.await(); // we don't answer until our name is known
+      
+      Object[] rtn = { this.nodeName, this.localReplicants };
+      
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("lookupLocalReplicants called ("+ rtn[0] + "). Return: " + this.localReplicants.size());
+      }
+      
+      return rtn;
+   }
+   
+   // Package protected ---------------------------------------------
+   
+   // Protected -----------------------------------------------------
+   
+   protected int calculateReplicantsHash(List<ClusterNode> members)
+   {
+      int result = 0;
+      
+      for (ClusterNode member: members)
+      {
+         if (member != null)
+         {
+            result += member.getName().hashCode(); // no explicit overflow with int addition
+         }
+      }
+      
+      return result;
+   }
+   
+   protected int updateReplicantsHashId(String key)
+   {
+      // we first get a list of all nodes names that replicate this key
+      //
+      List<ClusterNode> nodes = this.lookupReplicantsNodes(key);
+      int result = 0;
+      
+      if ((nodes == null) || nodes.isEmpty())
+      {
+         // no nore replicants for this key: we uncache our view id
+         //
+         this.intraviewIdCache.remove(key);
+      }
+      else
+      {
+         result = this.calculateReplicantsHash(nodes);
+         this.intraviewIdCache.put(key, new Integer(result));
+      }
+      
+      return result;
+      
+   }
+   
+   ///////////////
+   // DistributedReplicantManager API
+   ///////////////
+   
+   /**
+    * Add a replicant to the replicants map.
+    * @param key replicant key name
+    * @param nodeName name of the node that adds this replicant
+    * @param replicant Serialized representation of the replica
+    * @return true, if this replicant was newly added to the map, false otherwise
+    */
+   protected boolean addReplicant(String key, String nodeName, Serializable replicant)
+   {
+      ConcurrentMap<String, Serializable> map = new ConcurrentHashMap<String, Serializable>();
+      
+      ConcurrentMap<String, Serializable> existingMap = this.replicants.putIfAbsent(key, map);
+      
+      return (((existingMap != null) ? existingMap : map).put(nodeName, replicant) != null);
+   }
+   
+   /**
+    * Notifies, through a callback, the listeners for a given replicant that the set of replicants has changed
+    * @param key The replicant key name
+    * @param newReplicants The new list of replicants
+    * @param merge is the notification the result of a cluster merge?
+    * 
+    */
+   protected void notifyKeyListeners(String key, List<Serializable> newReplicants, boolean merge)
+   {
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("notifyKeyListeners");
+      }
+
+      // we first update the intra-view id for this particular key
+      //
+      int newId = this.updateReplicantsHashId(key);
+      
+      List<ReplicantListener> listeners = this.keyListeners.get(key);
+
+      if (listeners == null)
+      {
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("listeners is null");
+         }
+         return;
+      }
+      
+      if (this.log.isTraceEnabled())
+      {
+         this.log.trace("notifying " + listeners.size() + " listeners for key change: " + key);
+      }
+      
+      for (ReplicantListener listener: listeners)
+      {
+         if (listener != null)
+         {
+            listener.replicantsChanged(key, newReplicants, newId, merge);
+         }
+      }
+   }
+
+   protected void republishLocalReplicants()
+   {
+      try
+      {
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("Start Re-Publish local replicants in DRM");
+         }
+
+         for (Map.Entry<String, Serializable> entry: this.localReplicants.entrySet())
+         {
+            Serializable replicant = entry.getValue();
+            
+            if (replicant != null)
+            {
+               String key = entry.getKey();
+               
+               if (this.log.isTraceEnabled())
+               {
+                  this.log.trace("publishing, key=" + key + ", value=" + replicant);
+               }
+
+               Object[] args = { key, this.nodeName, replicant };
+
+               this.partition.callAsynchMethodOnCluster(SERVICE_NAME, "_add", args, add_types, true);
+               
+               this.notifyKeyListeners(key, this.getReplicants(key), false);
+            }
+         }
+         
+         if (this.log.isTraceEnabled())
+         {
+            this.log.trace("End Re-Publish local replicants");
+         }
+      }
+      catch (Exception e)
+      {
+         this.log.error("Re-Publish failed", e);
+      }
+   }
+
+   ////////////////////
+   // Group membership API
+   ////////////////////
+
+   protected void mergeMembers()
+   {
+      try
+      {
+         this.log.debug("Start merging members in DRM service...");
+         
+         List<?> rsp = this.partition.callMethodOnCluster(SERVICE_NAME,
+                                        "lookupLocalReplicants",
+                                        new Object[]{}, new Class[]{}, true);
+         if (rsp.isEmpty())
+         {
+            this.log.debug("No responses from other nodes during the DRM merge process.");
+         }
+         else
+         {
+            this.log.debug("The DRM merge process has received " + rsp.size() + " answers");
+         }
+         
+         // Record keys to be notified, and replicant list per key
+         Map<String, List<Serializable>> notifications = new HashMap<String, List<Serializable>>();
+         
+         // Perform add/remove and replicant lookup atomically
+         synchronized (this.replicants)
+         {
+            for (Object o: rsp)
+            {
+               if (o == null)
+               {
+                  this.log.warn("As part of the answers received during the DRM merge process, a NULL message was received!");
+                  continue;
+               }
+               else if (o instanceof Throwable)
+               {
+                  this.log.warn("As part of the answers received during the DRM merge process, a Throwable was received!", (Throwable) o);
+                  continue;
+               }
+               
+               Object[] objs = (Object[]) o;
+               String node = (String) objs[0];
+               @SuppressWarnings("unchecked")
+               Map<String, Serializable> replicants = (Map<String, Serializable>) objs[1];
+               
+               //FIXME: We don't remove keys in the merge process but only add new keys!
+               for (Map.Entry<String, Serializable> entry: replicants.entrySet())
+               {
+                  String key = entry.getKey();
+                  
+                  if (this.addReplicant(key, node, entry.getValue()))
+                  {
+                     notifications.put(key, null);
+                  }
+               }
+               
+               // The merge process needs to remove some (now) unexisting keys
+               for (Map.Entry<String, ConcurrentMap<String, Serializable>> entry: this.replicants.entrySet())
+               {
+                  String key = entry.getKey();
+                  
+                  if (entry.getValue().containsKey(node))
+                  {
+                     if (!replicants.containsKey(key))
+                     {
+                        if (this.removeReplicant(key, node))
+                        {
+                           notifications.put(key, null);
+                        }
+                     }
+                  }
+               }
+            }
+            
+            // Lookup replicants for each changed key
+            for (Map.Entry<String, List<Serializable>> entry: notifications.entrySet())
+            {
+               entry.setValue(this.getReplicants(entry.getKey()));
+            }
+         }
+         
+         // Notify recorded key changes
+         for (Map.Entry<String, List<Serializable>> entry: notifications.entrySet())
+         {
+            this.notifyKeyListeners(entry.getKey(), entry.getValue(), true);
+         }
+
+         this.log.debug("..Finished merging members in DRM service");
+
+      }
+      catch (Exception ex)
+      {
+         this.log.error("merge failed", ex);
+      }
+   }
+
+   /**
+    * Get rid of dead members from replicant list.
+    * 
+    * @param deadMembers the members that are no longer in the view
+    * @param merge       whether the membership change occurred during
+    *                    a cluster merge
+    */
+   protected void purgeDeadMembers(Vector<ClusterNode> deadMembers, boolean merge)
+   {
+      if (deadMembers.isEmpty()) return;
+
+      this.log.debug("purgeDeadMembers, " + deadMembers);
+
+      List<String> deadNodes = new ArrayList<String>(deadMembers.size());
+      
+      for (ClusterNode member: deadMembers)
+      {
+         deadNodes.add(member.getName());
+      }
+      
+      for (Map.Entry<String, ConcurrentMap<String, Serializable>> entry: this.replicants.entrySet())
+      {
+         String key = entry.getKey();
+         ConcurrentMap<String, Serializable> replicant = entry.getValue();
+         
+         List<Serializable> replicants = null;
+         
+         synchronized (this.replicants)
+         {
+            if (replicant.keySet().removeAll(deadNodes))
+            {
+               replicants = this.getReplicants(key);
+            }
+         }
+         
+         if (replicants != null)
+         {
+            this.notifyKeyListeners(key, replicants, merge);
+         }
+      }
+   }
+
+   /**
+    */
+   protected void cleanupKeyListeners()
+   {
+      // NOT IMPLEMENTED YET
+   }
+
+   // Private -------------------------------------------------------
+   
+   // Inner classes -------------------------------------------------
+
+   protected class MergeMembers extends Thread
+   {
+      public MergeMembers()
+      {
+         super("DRM Async Merger#" + threadID.getAndIncrement());
+      }
+
+      /**
+       * Called when the service needs to merge with another partition. This
+       * process is performed asynchronously
+       */
+      public void run()
+      {
+         DistributedReplicantManagerImpl.this.log.debug("Sleeping for 50ms before mergeMembers");
+         try
+         {
+            // if this thread invokes a cluster method call before
+            // membershipChanged event completes, it could timeout/hang
+            // we need to discuss this with Bela.
+            Thread.sleep(50);
+         }
+         catch (InterruptedException e)
+         {
+            Thread.currentThread().interrupt();
+         }
+         DistributedReplicantManagerImpl.this.mergeMembers();
+      }
+   }
+
+   protected class MembersPublisher extends Thread
+   {
+      public MembersPublisher()
+      {
+         super("DRM Async Publisher#" + threadID.getAndIncrement());
+      }
+
+      /**
+       * Called when service needs to re-publish its local replicants to other
+       * cluster members after this node has joined the cluster.
+       */
+      public void run()
+      {
+         DistributedReplicantManagerImpl.this.log.debug("DRM: Sleeping before re-publishing for 50ms just in case");
+         try
+         {
+            // if this thread invokes a cluster method call before
+            // membershipChanged event completes, it could timeout/hang
+            // we need to discuss this with Bela.
+            Thread.sleep(50);
+         }
+         catch (InterruptedException e)
+         {
+            Thread.currentThread().interrupt();
+         }
+         DistributedReplicantManagerImpl.this.republishLocalReplicants();
+      }
+   }
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImplMBean.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/DistributedReplicantManagerImplMBean.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImplMBean.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/DistributedReplicantManagerImplMBean.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -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.ha.core.framework.server;
+
+import org.jboss.ha.framework.interfaces.HAPartition;
+
+/**
+ * MBean interface for the Distributed Replicant Manager (DRM) service
+ *
+ * @author  <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @version $Revision$
+ *   
+ * <p><b>Revisions:</b>
+ *
+ * <p><b>12 janvier 2002 Sacha Labourey:</b>
+ * <ul>
+ * <li> First implementation </li> 
+ * </ul>
+ */
+
+public interface DistributedReplicantManagerImplMBean
+   extends org.jboss.ha.framework.interfaces.DistributedReplicantManager
+{   
+   /**
+    * Get the {@link HAPartition#getPartitionName() name of the underlying partition}
+    * used by this service.
+    * 
+    * @return the name of the partition
+    */
+   String getPartitionName();
+   
+   String listContent () throws Exception;
+   String listXmlContent () throws Exception;
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/HAPartitionImpl.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/HAPartitionImpl.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/HAPartitionImpl.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,356 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2009 Red Hat, Inc. 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.ha.core.framework.server;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+
+import org.jboss.ha.framework.interfaces.ClusterNode;
+import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
+import org.jboss.ha.framework.interfaces.GroupMembershipListener;
+import org.jboss.ha.framework.interfaces.HAPartition;
+import org.jboss.ha.framework.interfaces.StateTransferProvider;
+//import org.jboss.ha.framework.server.spi.ManagedDistributedState;
+
+/**
+ * Extends {@link CoreGroupCommunicationService} to add implemenation of the
+ * {@link HAPartition} methods that are not part of its superinterfaces.
+ *
+ * @author <a href="mailto:sacha.labourey at cogito-info.ch">Sacha Labourey</a>.
+ * @author <a href="mailto:bill at burkecentral.com">Bill Burke</a>.
+ * @author Scott.Stark at jboss.org
+ * @author Brian Stansberry
+ * @author Vladimir Blagojevic
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ * @version $Revision: 104456 $
+ */
+public class HAPartitionImpl
+   extends CoreGroupCommunicationService
+   implements HAPartition
+{
+   // Constants -----------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   @SuppressWarnings("deprecation")
+   private final Map<String, HAPartitionStateTransfer> initialStateRecipients = new ConcurrentHashMap<String, HAPartitionStateTransfer>();
+   /** The cluster replicant manager */
+   private DistributedReplicantManagerImpl replicantManager;
+   /** The DistributedState service we manage */
+   @SuppressWarnings("deprecation")
+   private org.jboss.ha.framework.interfaces.DistributedState distributedState;
+
+   // Static --------------------------------------------------------
+
+   // Constructors --------------------------------------------------
+   
+   public HAPartitionImpl()
+   {
+      this.logHistory("Partition object created");
+   }
+
+   // HAPartition implementation ----------------------------------------------
+   
+   public String getPartitionName()
+   {
+      return getGroupName();
+   }
+   
+   public DistributedReplicantManager getDistributedReplicantManager()
+   {
+      return this.replicantManager;
+   }
+   
+   @SuppressWarnings("deprecation")
+   public org.jboss.ha.framework.interfaces.DistributedState getDistributedStateService()
+   {
+      return this.distributedState;
+   }
+   
+   // *************************
+   // *************************
+   // Group Membership listeners
+   // *************************
+   // *************************
+   
+   @SuppressWarnings("deprecation")
+   public void registerMembershipListener(HAMembershipListener listener)
+   {
+      GroupMembershipListener adapter = new GroupMembershipListenerAdapter(listener);
+      
+      boolean isAsynch = (!getAllowSynchronousMembershipNotifications())
+            || (listener instanceof AsynchHAMembershipListener)
+            || (listener instanceof AsynchHAMembershipExtendedListener);
+      
+      registerGroupMembershipListener(adapter, !isAsynch);
+   }
+
+   @SuppressWarnings("deprecation")
+   public void unregisterMembershipListener(HAMembershipListener listener)
+   {
+      GroupMembershipListener adapter = new GroupMembershipListenerAdapter(listener);
+      
+      boolean isAsynch = (!getAllowSynchronousMembershipNotifications())
+            || (listener instanceof AsynchHAMembershipListener)
+            || (listener instanceof AsynchHAMembershipExtendedListener);
+      
+      unregisterGroupMembershipListener(adapter, !isAsynch);
+   } 
+   
+   // *************************
+   // *************************
+   // State transfer management
+   // *************************
+   // *************************
+   
+   @SuppressWarnings("deprecation")
+   public void subscribeToStateTransferEvents(String serviceName, HAPartitionStateTransfer subscriber)
+   {
+      StateTransferProvider provider = new StateTransferProviderAdapter(subscriber);
+      this.registerStateTransferProvider(serviceName, provider);
+      this.initialStateRecipients.put(serviceName, subscriber);
+   }
+
+   @SuppressWarnings("deprecation")
+   public void unsubscribeFromStateTransferEvents(String serviceName, HAPartitionStateTransfer subscriber)
+   {
+      this.initialStateRecipients.remove(serviceName);
+   }  
+   
+   
+   // Public ------------------------------------------------------------------
+   
+   @SuppressWarnings("deprecation")
+   public void setDistributedStateImpl(org.jboss.ha.framework.interfaces.DistributedState distributedState)
+   {
+      this.distributedState = distributedState;
+   }   
+
+   public void setPartitionName(String newName)
+   {
+      setGroupName(newName);
+   }
+   
+   public DistributedReplicantManagerImpl getDistributedReplicantManagerImpl()
+   {
+      return this.replicantManager;
+   }
+
+   // Protected -------------------------------------------------------------- 
+   
+   @Override
+   protected void createService() throws Exception
+   {
+      this.log.info("Initializing partition " + this.getPartitionName());
+      this.logHistory ("Initializing partition " + this.getPartitionName());
+      
+      super.createService();
+      if (this.replicantManager == null)
+      {
+         this.replicantManager = new DistributedReplicantManagerImpl(this);
+      }
+
+//      registerDRM();
+      
+      this.replicantManager.createService();
+      
+//      if (this.distributedState instanceof ManagedDistributedState)
+//      {
+//         ((ManagedDistributedState) this.distributedState).createService();
+//      }
+      
+      this.log.debug("done initializing partition "  + this.getPartitionName());
+   }
+   
+   @Override
+   protected  void startService() throws Exception
+   {
+      this.logHistory ("Starting partition "  + this.getPartitionName());
+      
+      super.startService();
+      
+      this.fetchInitialState();
+      
+      this.replicantManager.startService();
+      
+//      if (this.distributedState instanceof ManagedDistributedState)
+//      {
+//         ((ManagedDistributedState) this.distributedState).startService();
+//      }      
+   }
+
+   @Override
+   protected void stopService() throws Exception
+   {      
+      this.logHistory ("Stopping partition");
+      this.log.info("Stopping partition " + this.getPartitionName());
+      
+//      if (this.distributedState instanceof ManagedDistributedState)
+//      {
+//         ((ManagedDistributedState) this.distributedState).stopService();
+//      }
+
+      this.replicantManager.stopService();      
+
+      super.stopService();
+
+      this.log.info("Partition " + this.getPartitionName() + " stopped.");
+   }
+   
+   @Override
+   protected void destroyService() 
+   {
+      this.log.debug("Destroying HAPartition: " + this.getPartitionName());
+      
+      @SuppressWarnings("deprecation")
+      String svc = org.jboss.ha.framework.interfaces.DistributedState.class.getSimpleName();
+      try
+      {         
+//         if (this.distributedState instanceof ManagedDistributedState)
+//         {
+//            ((ManagedDistributedState) this.distributedState).destroyService();
+//         }
+         svc = DistributedReplicantManager.class.getSimpleName();
+         this.replicantManager.destroyService();
+//       unregisterDRM();
+      }
+      catch (InterruptedException e)
+      {
+         Thread.currentThread().interrupt();
+         this.log.error("Destroying " + svc + " failed", e);
+      }
+      catch (Exception e)
+      {
+         this.log.error("Destroying " + svc + " failed", e);
+      }
+      
+      super.destroyService();
+      
+      this.log.info("Partition " + this.getPartitionName() + " destroyed.");
+   }
+   
+   
+   // Private -------------------------------------------------------
+
+   @SuppressWarnings("deprecation")
+   private void fetchInitialState() throws Exception
+   {
+      this.log.info("Fetching serviceState (will wait for " + this.getStateTransferTimeout() +
+            " milliseconds for each service):");
+      
+      for (Map.Entry<String, HAPartitionStateTransfer> entry : this.initialStateRecipients.entrySet())
+      {
+         try
+         {
+            Future<Serializable> future = this.getServiceState(entry.getKey());
+            entry.getValue().setCurrentState(future.get());
+         }
+         catch (Exception e)
+         {
+            log.error("Could not acquire initial state for service " + entry.getKey(), e);
+         }
+      }
+   }
+
+   
+   // Inner classes -------------------------------------------------
+   
+   /**
+    * Adapts the legacy HAMembershipListener API to the new GroupMemberhshipListener API.
+    */
+   @SuppressWarnings("deprecation")
+   private static class GroupMembershipListenerAdapter implements GroupMembershipListener
+   {
+      private final HAMembershipListener target;
+      
+      GroupMembershipListenerAdapter(HAMembershipListener target)
+      {
+         this.target = target;
+      }
+
+      public void membershipChanged(List<ClusterNode> deadMembers, List<ClusterNode> newMembers,
+            List<ClusterNode> allMembers)
+      {
+         target.membershipChanged(castMembers(deadMembers), castMembers(newMembers), castMembers(allMembers));
+      }
+
+      public void membershipChangedDuringMerge(List<ClusterNode> deadMembers, List<ClusterNode> newMembers,
+            List<ClusterNode> allMembers, List<List<ClusterNode>> originatingGroups)
+      {
+         if (target instanceof HAMembershipExtendedListener)
+         {
+            ((HAMembershipExtendedListener) target).membershipChangedDuringMerge(castMembers(deadMembers), castMembers(newMembers), castMembers(allMembers), castOriginatingGroups(originatingGroups));
+         }
+         else
+         {
+            membershipChanged(deadMembers, newMembers, allMembers);
+         }
+         
+      }
+      
+      public boolean equals(Object obj)
+      {
+         return obj instanceof GroupMembershipListenerAdapter && ((GroupMembershipListenerAdapter) obj).target.equals(target);
+      }
+
+      public int hashCode()
+      {
+         return target.hashCode();
+      }
+
+      private static Vector<ClusterNode> castMembers(List<ClusterNode> members)
+      {
+         return members instanceof Vector<?> ? (Vector<ClusterNode>) members : new Vector<ClusterNode>(members);
+      }
+
+      private static Vector<List<ClusterNode>> castOriginatingGroups(List<List<ClusterNode>> groups)
+      {
+         return groups instanceof Vector<?> ? (Vector<List<ClusterNode>>) groups : new Vector<List<ClusterNode>>(groups);
+      }
+   }
+   
+   /**
+    * Allows a legacy HAPartitionStateTransfer to be used as a StateTransferProvider.
+    */
+   private static class StateTransferProviderAdapter implements StateTransferProvider
+   {
+      @SuppressWarnings("deprecation")
+      private final HAPartitionStateTransfer delegate;
+      
+      @SuppressWarnings("deprecation")
+      private StateTransferProviderAdapter(HAPartitionStateTransfer delegate)
+      {
+         this.delegate = delegate;
+      }
+      
+      public Serializable getCurrentState()
+      {
+         return delegate.getCurrentState();
+      }
+      
+   }
+   
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxHandlerChannel.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxHandlerChannel.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxHandlerChannel.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,418 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, Red Hat, Inc. 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.ha.core.framework.server;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.jgroups.Address;
+import org.jgroups.Channel;
+import org.jgroups.ChannelClosedException;
+import org.jgroups.ChannelException;
+import org.jgroups.ChannelListener;
+import org.jgroups.ChannelNotConnectedException;
+import org.jgroups.Event;
+import org.jgroups.Message;
+import org.jgroups.Receiver;
+import org.jgroups.TimeoutException;
+import org.jgroups.UpHandler;
+import org.jgroups.View;
+import org.jgroups.logging.Log;
+import org.jgroups.logging.LogFactory;
+import org.jgroups.stack.ProtocolStack;
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision$
+ */
+public class MuxHandlerChannel extends Channel
+{
+   private final Log log=LogFactory.getLog(MuxHandlerChannel.class);
+   private final Channel delegate;
+   private MuxUpHandler upHandler;
+   
+   public MuxHandlerChannel(Channel delegate)
+   {
+      this.delegate = delegate;
+      UpHandler uh = delegate.getUpHandler();
+      if (uh == null)
+      {
+         this.upHandler = new MuxUpHandler();
+         this.delegate.setUpHandler(upHandler);
+      }
+      else if (uh instanceof MuxUpHandler)
+      {
+         this.upHandler = (MuxUpHandler) uh;
+      }
+      else
+      {
+         throw new IllegalStateException("delegate Channel already has a non-Mux UpHandler");
+      }
+   }
+   
+   @Override
+   public void blockOk()
+   {
+      delegate.blockOk();
+   }
+
+   @Override
+   public void close()
+   {
+      delegate.close();
+   }
+
+   @Override
+   public void connect(String clusterName) throws ChannelException
+   {
+      delegate.connect(clusterName);
+   }
+
+   @Override
+   public void connect(String clusterName, Address target, String stateId, long timeout) throws ChannelException
+   {
+      delegate.connect(clusterName, target, stateId, timeout);
+   }
+
+   @Override
+   public void disconnect()
+   {
+      delegate.disconnect();
+   }
+
+   @Override
+   public Map<String, Object> dumpStats()
+   {
+      return delegate.dumpStats();
+   }
+
+   @Override
+   public boolean flushSupported()
+   {
+      return delegate.flushSupported();
+   }
+
+   @Override
+   public Address getAddress()
+   {
+      return delegate.getAddress();
+   }
+
+   @Override
+   public boolean getAllStates(Vector targets, long timeout) throws ChannelNotConnectedException,
+         ChannelClosedException
+   {
+      return delegate.getAllStates(targets, timeout);
+   }
+
+   @Override
+   public String getChannelName()
+   {
+      return delegate.getChannelName();
+   }
+
+   @Override
+   public String getClusterName()
+   {
+      return delegate.getClusterName();
+   }
+
+   @Override
+   public Map<String, Object> getInfo()
+   {
+      return delegate.getInfo();
+   }
+
+   @Override
+   public Address getLocalAddress()
+   {
+      return delegate.getLocalAddress();
+   }
+
+   @Override
+   protected Log getLog()
+   {
+      return log;
+   }
+
+   @Override
+   public String getName()
+   {
+      return delegate.getName();
+   }
+
+   @Override
+   public String getName(Address member)
+   {
+      return delegate.getName(member);
+   }
+
+   @Override
+   public Object getOpt(int option)
+   {
+      return delegate.getOpt(option);
+   }
+
+   @Override
+   public ProtocolStack getProtocolStack()
+   {
+      return delegate.getProtocolStack();
+   }
+
+   @Override
+   public boolean getState(Address target, long timeout) throws ChannelNotConnectedException, ChannelClosedException
+   {
+      return delegate.getState(target, timeout);
+   }
+
+   @Override
+   public boolean getState(Address target, String stateId, long timeout) throws ChannelNotConnectedException,
+         ChannelClosedException
+   {
+      return delegate.getState(target, stateId, timeout);
+   }
+
+   @Override
+   public View getView()
+   {
+      return delegate.getView();
+   }
+
+   @Override
+   public boolean isConnected()
+   {
+      return delegate.isConnected();
+   }
+
+   @Override
+   public boolean isOpen()
+   {
+      return delegate.isOpen();
+   }
+
+   @Override
+   public Object peek(long timeout) throws ChannelNotConnectedException, ChannelClosedException, TimeoutException
+   {
+      return delegate.peek(timeout);
+   }
+
+   @Override
+   public Object receive(long timeout) throws ChannelNotConnectedException, ChannelClosedException, TimeoutException
+   {
+      return delegate.receive(timeout);
+   }
+
+   @Override
+   public void returnState(byte[] state)
+   {
+      delegate.returnState(state);
+   }
+
+   @Override
+   public void returnState(byte[] state, String stateId)
+   {
+      delegate.returnState(state, stateId);
+   }
+
+   @Override
+   public void send(Message msg) throws ChannelNotConnectedException, ChannelClosedException
+   {
+      delegate.send(msg);
+   }
+
+   @Override
+   public void send(Address dst, Address src, Serializable obj) throws ChannelNotConnectedException,
+         ChannelClosedException
+   {
+      delegate.send(dst, src, obj);
+   }
+
+   @Override
+   public void send(Address dst, Address src, byte[] buf) throws ChannelNotConnectedException, ChannelClosedException
+   {
+      delegate.send(dst, src, buf);
+   }
+
+   @Override
+   public void send(Address dst, Address src, byte[] buf, int offset, int length) throws ChannelNotConnectedException,
+         ChannelClosedException
+   {
+      delegate.send(dst, src, buf, offset, length);
+   }
+
+   @Override
+   public void setInfo(String key, Object value)
+   {
+      delegate.setInfo(key, value);
+   }
+
+   @Override
+   public void setName(String name)
+   {
+      delegate.setName(name);
+   }
+
+   @Override
+   public void setOpt(int option, Object value)
+   {
+      delegate.setOpt(option, value);
+   }
+
+   @Override
+   public void shutdown()
+   {
+      delegate.shutdown();
+   }
+
+   @Override
+   public boolean startFlush(boolean automaticResume)
+   {
+      return delegate.startFlush(automaticResume);
+   }
+
+   @Override
+   public boolean startFlush(List<Address> flushParticipants, boolean automaticResume)
+   {
+      return delegate.startFlush(flushParticipants, automaticResume);
+   }
+
+   @Override
+   public boolean startFlush(long timeout, boolean automaticResume)
+   {
+      return delegate.startFlush(timeout, automaticResume);
+   }
+
+   @Override
+   public void stopFlush()
+   {
+      delegate.stopFlush();
+   }
+
+   @Override
+   public void stopFlush(List<Address> flushParticipants)
+   {
+      delegate.stopFlush(flushParticipants);
+   }
+
+   @Override
+   public synchronized void addChannelListener(ChannelListener listener)
+   {
+      delegate.addChannelListener(listener);
+   }
+
+   @Override
+   public synchronized void clearChannelListeners()
+   {
+      delegate.clearChannelListeners();
+   }
+
+   @Override
+   public void down(Event evt)
+   {
+      delegate.down(evt);
+   }
+
+   @Override
+   public Object downcall(Event evt)
+   {
+      return delegate.downcall(evt);
+   }
+
+   @Override
+   public String dumpQueue()
+   {
+      return delegate.dumpQueue();
+   }
+
+   @Override
+   public int getNumMessages()
+   {
+      return delegate.getNumMessages();
+   }
+
+   @Override
+   public String getProperties()
+   {
+      return delegate.getProperties();
+   }
+
+   @Override
+   public Receiver getReceiver()
+   {
+      return delegate.getReceiver();
+   }
+
+   @Override
+   public UpHandler getUpHandler()
+   {
+      // We hide the upHandler from callers so they will set it. Yuck!
+      return null;
+   }
+
+   @Override
+   public void open() throws ChannelException
+   {
+      delegate.open();
+   }
+
+   @Override
+   public synchronized void removeChannelListener(ChannelListener listener)
+   {
+      delegate.removeChannelListener(listener);
+   }
+
+   @Override
+   public void setChannelListener(ChannelListener channelListener)
+   {
+      delegate.setChannelListener(channelListener);
+   }
+
+   @Override
+   public void setReceiver(Receiver r)
+   {
+      delegate.setReceiver(r);
+   }
+
+   @Override
+   public void setUpHandler(UpHandler upHandler)
+   {
+      if (this.upHandler != null)
+      {
+         throw new IllegalStateException("UpHandler already set");
+      }
+      this.upHandler = new MuxUpHandler(upHandler);
+      delegate.setUpHandler(this.upHandler);
+   }
+   
+   public MuxUpHandler getMuxUpHandler()
+   {
+      return upHandler;
+   }
+   
+
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxUpHandler.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxUpHandler.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/MuxUpHandler.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,155 @@
+package org.jboss.ha.core.framework.server;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+import org.jgroups.Event;
+import org.jgroups.Message;
+import org.jgroups.UpHandler;
+import org.jgroups.blocks.mux.MuxHeader;
+import org.jgroups.blocks.mux.MuxRequestCorrelator;
+import org.jgroups.blocks.mux.Muxer;
+import org.jgroups.blocks.mux.NoMuxHandler;
+import org.jgroups.conf.ClassConfigurator;
+import org.jgroups.logging.Log;
+import org.jgroups.logging.LogFactory;
+import org.jgroups.stack.StateTransferInfo;
+
+/**
+ * Allows up handler multiplexing.
+ * 
+ * @author Bela Ban
+ * @author Paul Ferraro
+ * @version $Id: MuxUpHandler.java,v 1.2 2010/04/15 20:05:22 ferraro Exp $
+ */
+public class MuxUpHandler implements UpHandler, Muxer<UpHandler> {
+
+    protected final Log log=LogFactory.getLog(getClass());
+    protected final static short MUX_ID = ClassConfigurator.getProtocolId(MuxRequestCorrelator.class);
+    private final Map<Short, UpHandler> handlers = new ConcurrentHashMap<Short, UpHandler>();
+    private final Set<StateTransferFilter> stateTransferHandlers = new CopyOnWriteArraySet<StateTransferFilter>();
+    private final List<UpHandler> basicHandlers = new CopyOnWriteArrayList<UpHandler>();
+    private volatile UpHandler defaultHandler;
+    
+    /**
+     * Creates a multiplexing up handler, with no default handler.
+     */
+    public MuxUpHandler() {
+        this.defaultHandler = null;
+    }
+
+    /**
+     * Creates a multiplexing up handler using the specified default handler.
+     * @param defaultHandler a default up handler to handle messages with no {@link MuxHeader}
+     */
+    public MuxUpHandler(UpHandler defaultHandler) {
+        this.defaultHandler = defaultHandler;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see org.jgroups.blocks.mux.Muxer#add(short, java.lang.Object)
+     */
+    @Override
+    public void add(short id, UpHandler handler) {
+        handlers.put(id, handler);
+        if (handler instanceof StateTransferFilter)
+        {
+           stateTransferHandlers.add((StateTransferFilter) handler);
+        }
+        else
+        {
+           basicHandlers.add(handler);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see org.jgroups.blocks.mux.Muxer#remove(short)
+     */
+    @Override
+    public void remove(short id) {
+        UpHandler handler = handlers.remove(id);
+        if (handler instanceof StateTransferFilter)
+        {
+           stateTransferHandlers.remove(handler);
+        }
+        else
+        {
+           basicHandlers.remove(handler);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @see org.jgroups.UpHandler#up(org.jgroups.Event)
+     */
+    @Override
+    public Object up(Event evt) {
+        switch (evt.getType()) {
+            case Event.MSG: {
+                Message msg = (Message) evt.getArg();
+                MuxHeader hdr = (MuxHeader) msg.getHeader(MUX_ID);
+                if (hdr != null) {
+                    short id = hdr.getId();
+                    UpHandler handler = handlers.get(id);
+                    return (handler != null) ? handler.up(evt) : new NoMuxHandler(id);
+                }
+                break;
+            }
+            case Event.GET_APPLSTATE:
+            case Event.GET_STATE_OK: 
+            case Event.STATE_TRANSFER_OUTPUTSTREAM: 
+            case Event.STATE_TRANSFER_INPUTSTREAM: {
+                StateTransferInfo info=(StateTransferInfo)evt.getArg();
+                String state_id=info.state_id;
+                for (StateTransferFilter stup : stateTransferHandlers)
+                {
+                   if (stup.accepts(state_id))
+                   {
+                      return ((UpHandler) stup).up(evt);
+                   }
+                }
+                
+                int numBasic = basicHandlers.size();
+
+                if (numBasic > 0)
+                {
+                   if (numBasic > 1)
+                   {
+                      log.warn("Received state transfer related event with more " +
+                      		"than one basic UpHandler registered. Arbitrarily " +
+                      		"using first handler registered to handle request");
+                   }
+                   
+                   try
+                   {
+                      return basicHandlers.get(0).up(evt);
+                   }
+                   catch (IndexOutOfBoundsException ignored)
+                   {
+                      // must have been removed
+                   }
+                }
+                break;
+            }
+            case Event.VIEW_CHANGE:
+            case Event.SET_LOCAL_ADDRESS: 
+            case Event.SUSPECT: 
+            case Event.BLOCK: 
+            case Event.UNBLOCK:
+            default: {
+                for (UpHandler handler: handlers.values()) {
+                    handler.up(evt);
+                }
+                return null;
+            }
+        }
+        
+        return (defaultHandler != null) ? defaultHandler.up(evt) : null;
+    }
+}

Copied: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/RspFilterAdapter.java (from rev 105343, trunk/cluster/src/main/java/org/jboss/ha/framework/server/RspFilterAdapter.java)
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/RspFilterAdapter.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/RspFilterAdapter.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -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.ha.core.framework.server;
+
+import org.jboss.ha.framework.interfaces.ResponseFilter;
+import org.jgroups.Address;
+import org.jgroups.blocks.RspFilter;
+
+/**
+ * JGroups RspFilter adapter class that delegates work to ResponseFilter, 
+ * Cluster abstraction of RspFilter.
+ * 
+ * @author <a href="mailto:galder.zamarreno at jboss.com">Galder Zamarreno</a>
+ */
+public class RspFilterAdapter implements RspFilter
+{
+   private final ResponseFilter filter;
+
+   private final ClusterNodeFactory factory;
+
+   public RspFilterAdapter(ResponseFilter filter, ClusterNodeFactory factory)
+   {
+      this.filter = filter;
+      this.factory = factory;
+   }
+
+   public boolean isAcceptable(Object response, Address sender)
+   {
+      return filter.isAcceptable(response, factory.getClusterNode(sender));
+   }
+
+   public boolean needMoreResponses()
+   {
+      return filter.needMoreResponses();
+   }
+}

Added: projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/StateTransferFilter.java
===================================================================
--- projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/StateTransferFilter.java	                        (rev 0)
+++ projects/cluster/ha-server-core/trunk/src/main/java/org/jboss/ha/core/framework/server/StateTransferFilter.java	2010-05-28 22:36:13 UTC (rev 105344)
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2010, Red Hat, Inc. 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.ha.core.framework.server;
+
+
+/**
+ *
+ *
+ * @author Brian Stansberry
+ * 
+ * @version $Revision$
+ */
+public interface StateTransferFilter
+{
+   boolean accepts(String state_id);
+}




More information about the jboss-cvs-commits mailing list