/*
* 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.framework.server;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
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.bootstrap.spi.util.ServerConfigUtil;
import org.jboss.ha.framework.server.managed.OpenChannelsMapper;
import org.jboss.ha.framework.server.managed.ProtocolStackConfigurationsMapper;
import org.jboss.logging.Logger;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementProperties;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.metatype.api.annotations.MetaMapping;
import org.jboss.system.ServiceMBean;
import org.jboss.util.loading.ContextClassLoaderSwitcher;
import org.jgroups.Channel;
import org.jgroups.ChannelException;
import org.jgroups.ChannelFactory;
import org.jgroups.ChannelListenerAdapter;
import org.jgroups.Event;
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.IpAddress;
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@jboss.com">Galder Zamarreno</a>
*
* @version $Revision: 94383 $
*/
@ManagementObject(name="JChannelFactory",
componentType=@ManagementComponent(type="MCBean", subtype="JGroupsChannelFactory"),
properties=ManagementProperties.EXPLICIT,
isRuntime=true)
public class JChannelFactory
implements ChannelFactory, JChannelFactoryMBean, MBeanRegistration
{
protected 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 int CREATED = ServiceMBean.CREATED;
private static final int STARTING = ServiceMBean.STARTING;
private static final int STARTED = ServiceMBean.STARTED;
private static final int STOPPING = ServiceMBean.STOPPING;
private static final int STOPPED = ServiceMBean.STOPPED;
private static final int DESTROYED = ServiceMBean.DESTROYED;
private static final int FAILED = ServiceMBean.FAILED;
private InetAddress nodeAddress;
private String nodeName;
private int namingServicePort = -1;
private int state = ServiceMBean.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.
*/
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="The domain portion of the JMX ObjectName to use when registering channels and protocols")
public String getDomain()
{
return domain == null ? "jgroups" : domain;
}
public void setDomain(String domain)
{
this.domain = domain;
this.domainSet = true;
}
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether to expose channels we create via JMX")
public boolean isExposeChannels()
{
return expose_channels;
}
public void setExposeChannels(boolean expose_channels)
{
this.expose_channels=expose_channels;
}
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether to expose protocols via JMX as well if we 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>.
*/
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="The cluster-unique logical name of this node")
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)
*/
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether this factory should assign a logical address for this node to all channels")
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>.
*/
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether this factory should update the standard JGroups thread factories to ensure classloader leaks do not occur")
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>.
*/
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="Whether this factory should update the standard JGroups thread pools to ensure classloader leaks do not occur")
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>.
*/
@ManagementProperty(use={ViewUse.CONFIGURATION},
description="Whether this factory should create a synthetic singleton name attribute for a channel's transport protocol if one isn't configured")
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.
*/
@ManagementProperty(use={ViewUse.STATISTIC},
description="Information on channels created by this factory that are currently open",
readOnly=true)
@MetaMapping(value=OpenChannelsMapper.class)
public Set<ChannelInfo> getOpenChannels()
{
return new HashSet<ChannelInfo>(registeredChannels.values());
}
@ManagementProperty(use={ViewUse.CONFIGURATION, ViewUse.RUNTIME},
description="Protocol stack configurations available for use")
@MetaMapping(value=ProtocolStackConfigurationsMapper.class)
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 == ServiceMBean.UNREGISTERED)
{
state = ServiceMBean.REGISTERED;
}
}
public void preDeregister() throws Exception
{
}
public void postDeregister()
{
setServer(null);
if (state == ServiceMBean.DESTROYED)
state = ServiceMBean.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 != ServiceMBean.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 ServiceMBean.states[state];
}
private void setChannelUniqueId(Channel channel)
{
IpAddress address = (IpAddress) channel.getLocalAddress();
if (address == 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("Passing unique node id " + nodeName + " to the channel as additional data");
HashMap<String, byte[]> staticNodeName = new HashMap<String, byte[]>();
staticNodeName.put("additional_data", this.nodeName.getBytes());
channel.down(new Event(Event.CONFIG, staticNodeName));
}
else if (address.getAdditionalData() == null)
{
if (channel.isConnected())
{
throw new IllegalStateException("Underlying JChannel was " +
"connected before additional_data was set");
}
}
else if (this.nodeName == null || "".equals(this.nodeName))
{
this.nodeName = new String(address.getAdditionalData());
log.warn("Field nodeName was not set but mux channel already had " +
"additional data -- setting nodeName to " + nodeName);
}
}
private 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 = ServerConfigUtil.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 = address.getHostAddress();
}
// 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;
}
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);
getServer().registerMBean(new org.jgroups.jmx.JChannel(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)
{
org.jgroups.jmx.Protocol p=null;
try {
String prot_name = prot.getClass().getName();
String clname = prot_name.replaceFirst("org.jgroups.", "org.jgroups.jmx.");
Class<?> cl = Util.loadClass(clname, JmxConfigurator.class);
if (cl != null)
{
p = (org.jgroups.jmx.Protocol) cl.newInstance();
}
}
catch(ClassNotFoundException e)
{
// ignore;
}
catch(Throwable e) {
log.error("failed creating a JMX wrapper instance for " + prot, e);
p = null;
}
if(p == null)
{
// Use default
p = new org.jgroups.jmx.Protocol(prot);
}
else
{
p.attachProtocol(prot);
}
ObjectName prot_name=new ObjectName(baseName + ",protocol=" + prot.getName());
server.registerMBean(p, 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);
}
}
}