[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