/*
* 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.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.net.InetAddress;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
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.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import org.jboss.bootstrap.spi.util.ServerConfigUtil;
import org.jboss.ha.framework.interfaces.ClusterNode;
import org.jboss.ha.framework.interfaces.DistributedReplicantManager;
import org.jboss.ha.framework.interfaces.HAPartition;
import org.jboss.ha.framework.interfaces.ResponseFilter;
import org.jboss.ha.framework.server.deployers.DefaultHAPartitionDependencyCreator;
import org.jboss.ha.framework.server.deployers.HAPartitionDependencyCreator;
import org.jboss.ha.framework.server.spi.HAPartitionCacheHandler;
import org.jboss.ha.framework.server.spi.ManagedDistributedState;
import org.jboss.invocation.MarshalledValueInputStream;
import org.jboss.invocation.MarshalledValueOutputStream;
import org.jboss.kernel.spi.dependency.KernelController;
import org.jboss.kernel.spi.dependency.KernelControllerContext;
import org.jboss.logging.Logger;
import org.jboss.managed.api.ManagedOperation.Impact;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementObjectID;
import org.jboss.managed.api.annotation.ManagementOperation;
import org.jboss.managed.api.annotation.ManagementParameter;
import org.jboss.managed.api.annotation.ManagementProperties;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.naming.NonSerializableFactory;
import org.jboss.system.ServiceMBeanSupport;
import org.jboss.util.threadpool.ThreadPool;
import org.jgroups.Address;
import org.jgroups.Channel;
import org.jgroups.ChannelFactory;
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.Version;
import org.jgroups.View;
import org.jgroups.blocks.GroupRequest;
import org.jgroups.blocks.MethodCall;
import org.jgroups.blocks.RpcDispatcher;
import org.jgroups.stack.IpAddress;
import org.jgroups.util.Rsp;
import org.jgroups.util.RspList;
/**
* {@link HAPartition} implementation based on a
* <a href="http://www.jgroups.com/">JGroups</a> <code>RpcDispatcher</code>
* and a multiplexed <code>JChannel</code>.
*
* @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>.
* @author Scott.Stark@jboss.org
* @author brian.stansberry@jboss.com
* @author Galder Zamarreño
* @version $Revision: 91481 $
*/
@ManagementObject(componentType=@ManagementComponent(type="MCBean", subtype="HAPartition"),
properties=ManagementProperties.CLASS_AND_EXPLICIT,
classProperties={@ManagementProperty(name="stateString",use={ViewUse.STATISTIC})},
isRuntime=true)
public class ClusterPartition
extends ServiceMBeanSupport
implements ExtendedMembershipListener, HAPartition,
AsynchEventHandler.AsynchEventProcessor,
ClusterPartitionMBean
{
public static final String DEFAULT_CACHE_CONFIG = "ha-partition";
private static final byte EOF_VALUE = -1;
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;
/**
* Returned when an RPC call arrives for a service that isn't registered.
*/
public static class NoHandlerForRPC implements Serializable
{
static final long serialVersionUID = -1263095408483622838L;
}
private static class StateStreamEnd implements Serializable
{
/** The serialVersionUID */
private static final long serialVersionUID = -3705345735451504946L;
}
/**
* 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;
}
}
/**
* Used to connect the channel asynchronously from the thread that calls start().
*/
private class ChannelConnectTask implements Runnable
{
private final CountDownLatch latch;
private ChannelConnectTask(CountDownLatch latch)
{
this.latch = latch;
}
public void run()
{
try
{
ClusterPartition.this.channel.connect(ClusterPartition.this.getPartitionName());
}
catch (Exception e)
{
synchronized (ClusterPartition.this.channelLock)
{
ClusterPartition.this.connectException = e;
}
}
finally
{
this.latch.countDown();
}
}
}
// Constants -----------------------------------------------------
// final MethodLookup method_lookup_clos = new MethodLookupClos();
// Attributes ----------------------------------------------------
private HAPartitionCacheHandler cacheHandler;
private String cacheConfigName;
private ChannelFactory channelFactory;
private String stackName;
private String partitionName = ServerConfigUtil.getDefaultPartitionName();
private InetAddress nodeAddress = null;
private long state_transfer_timeout=60000;
private long method_call_timeout=60000;
/** Thread pool used to asynchronously start our channel */
private ThreadPool threadPool;
private final Map<String, Object> rpcHandlers = new ConcurrentHashMap<String, Object>();
private final Map<String, HAPartitionStateTransfer> stateHandlers = new HashMap<String, HAPartitionStateTransfer>();
/** Do we send any membership change notifications synchronously? */
private boolean allowSyncListeners = false;
/** The HAMembershipListener and HAMembershipExtendedListeners */
private final ArrayList<HAMembershipListener> synchListeners = new ArrayList<HAMembershipListener>();
/** The asynch HAMembershipListener and HAMembershipExtendedListeners */
private final ArrayList<HAMembershipListener> asynchListeners = new ArrayList<HAMembershipListener>();
/** The handler used to send membership change notifications asynchronously */
private AsynchEventHandler asynchHandler;
/** The current cluster partition members */
private Vector<ClusterNode> members = null;
private Vector<Address> jgmembers = null;
private final Map<String, WeakReference<ClassLoader>> clmap = new ConcurrentHashMap<String, WeakReference<ClassLoader>>();
private final Vector<String> history = new Vector<String>();
/** The partition members other than this node */
private Vector<ClusterNode> otherMembers = null;
private Vector<Address> jgotherMembers = null;
/** the local JG IP Address */
private Address localJGAddress = null;
/** The cluster transport protocol address string */
private String nodeName;
/** me as a ClusterNode */
private ClusterNode me = null;
/** The JGroups partition channel */
private Channel channel;
/** The cluster replicant manager */
private DistributedReplicantManagerImpl replicantManager;
/** The DistributedState service we manage */
@SuppressWarnings("deprecation")
private org.jboss.ha.framework.interfaces.DistributedState distributedState;
/** The cluster instance log category */
private Logger log = Logger.getLogger(HAPartition.class.getName());;
private Logger clusterLifeCycleLog = Logger.getLogger(HAPartition.class.getName() + ".lifecycle");
/** The current cluster view id */
private long currentViewId = -1;
/** Whether to bind the partition into JNDI */
private boolean bindIntoJndi = true;
private final ThreadGate flushBlockGate = new ThreadGate();
private RpcDispatcher dispatcher = null;
/**
* True if serviceState was initialized during start-up.
*/
protected boolean isStateSet = false;
/**
* An exception occuring upon fetch serviceState.
*/
private Exception setStateException;
/**
* An exception occuring during channel connect
*/
private Exception connectException;
private final Object channelLock = new Object();
private final MessageListenerAdapter messageListener = new MessageListenerAdapter();
private HAPartitionDependencyCreator haPartitionDependencyCreator;
private KernelControllerContext kernelControllerContext;
// Static --------------------------------------------------------
private Channel createChannel()
{
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.getPartitionName());
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
throw new RuntimeException("Failure creating multiplexed Channel", e);
}
}
// Constructors --------------------------------------------------
public ClusterPartition()
{
this.logHistory("Partition object created");
}
// ------------------------------------------------------------ ServiceMBean
// ----------------------------------------------------------------- Service
protected void createService() throws Exception
{
if (this.replicantManager == null)
{
this.replicantManager = new DistributedReplicantManagerImpl(this);
}
// registerDRM();
this.setupLoggers(this.getPartitionName());
this.replicantManager.createService();
if (this.distributedState instanceof ManagedDistributedState)
{
((ManagedDistributedState) this.distributedState).createService();
}
// Create the asynchronous handler for view changes
this.asynchHandler = new AsynchEventHandler(this, "AsynchViewChangeHandler");
// Add a well-known MC alias that other beans can depend on
addCanonicalAlias();
this.log.debug("done initializing partition");
}
protected void startService() throws Exception
{
this.logHistory ("Starting partition");
// Have the handler get the cache
this.cacheHandler.acquireCache();
this.channelFactory = this.cacheHandler.getCacheChannelFactory();
this.stackName = this.cacheHandler.getChannelStackName();
if (this.channel == null || !this.channel.isOpen())
{
this.log.debug("Creating Channel for partition " + this.getPartitionName() +
" using stack " + this.getChannelStackName());
this.channel = this.createChannel();
this.channel.setOpt(Channel.AUTO_RECONNECT, Boolean.TRUE);
this.channel.setOpt(Channel.AUTO_GETSTATE, Boolean.TRUE);
}
this.log.info("Initializing partition " + this.getPartitionName());
this.logHistory ("Initializing partition " + this.getPartitionName());
this.dispatcher = new RpcHandler(this.channel, null, null, new Object(), false);
// Subscribe to events generated by the channel
this.log.debug("setMembershipListener");
this.dispatcher.setMembershipListener(this);
this.log.debug("setMessageListener");
this.dispatcher.setMessageListener(this.messageListener);
this.dispatcher.setRequestMarshaller(new RequestMarshallerImpl());
this.dispatcher.setResponseMarshaller(new ResponseMarshallerImpl());
// Clear any old connectException
this.connectException = null;
CountDownLatch connectLatch = new CountDownLatch(1);
if (this.threadPool == null)
{
this.channel.connect(this.getPartitionName());
connectLatch.countDown();
}
else
{
// Do the channel connect in another thread while this
// thread starts the cache and does that channel connect
ChannelConnectTask task = new ChannelConnectTask(connectLatch);
this.threadPool.run(task);
}
this.cacheHandler.startCache();
try
{
// This will block waiting for any async channel connect above
connectLatch.await();
if (this.connectException != null)
{
throw this.connectException;
}
this.log.debug("Get current members");
this.waitForView();
// get current JG group properties
this.log.debug("get nodeName");
this.localJGAddress = this.channel.getLocalAddress();
this.me = new ClusterNodeImpl((IpAddress) this.localJGAddress);
this.nodeName = this.me.getName();
this.verifyNodeIsUnique();
this.fetchState();
this.replicantManager.startService();
if (this.distributedState instanceof ManagedDistributedState)
{
((ManagedDistributedState) this.distributedState).startService();
}
// Start the asynch listener handler thread
this.asynchHandler.start();
// Register with the service locator
HAPartitionLocator.getHAPartitionLocator().registerHAPartition(this);
// Bind ourself in the public JNDI space if configured to do so
if (this.bindIntoJndi)
{
Context ctx = new InitialContext();
this.bind(HAPartitionLocator.getStandardJndiBinding(this.getPartitionName()),
this, ClusterPartition.class, ctx);
this.log.debug("Bound in JNDI under /HAPartition/" + this.getPartitionName());
}
}
catch (Throwable t)
{
this.log.debug("Caught exception after channel connected; closing channel -- " + t.getLocalizedMessage());
this.channel.close();
this.channel = null;
throw (t instanceof Exception) ? (Exception) t : new RuntimeException(t);
}
}
protected void stopService() throws Exception
{
this.logHistory ("Stopping partition");
this.log.info("Stopping partition " + this.getPartitionName());
try
{
this.asynchHandler.stop();
}
catch( Exception e)
{
this.log.warn("Failed to stop asynchHandler", e);
}
if (this.distributedState instanceof ManagedDistributedState)
{
((ManagedDistributedState) this.distributedState).stopService();
}
this.replicantManager.stopService();
try
{
this.cacheHandler.releaseCache();
}
catch (Exception e)
{
this.log.error("cache release failed", e);
}
// NR 200505 : [JBCLUSTER-38] replace channel.close() by a disconnect and
// add the destroyPartition() step
try
{
if (this.channel != null && this.channel.isConnected())
{
this.channel.disconnect();
}
}
catch (Exception e)
{
this.log.error("channel disconnection failed", e);
}
if (this.bindIntoJndi)
{
String boundName = HAPartitionLocator.getStandardJndiBinding(this.getPartitionName());
InitialContext ctx = null;
try
{
// the following statement fails when the server is being shut down (07/19/2007)
ctx = new InitialContext();
ctx.unbind(boundName);
}
catch (Exception e) {
this.log.error("partition unbind operation failed", e);
}
finally
{
if (ctx != null)
{
ctx.close();
}
}
NonSerializableFactory.unbind(boundName);
}
HAPartitionLocator.getHAPartitionLocator().deregisterHAPartition(this);
this.log.info("Partition " + this.getPartitionName() + " stopped.");
}
protected void destroyService() throws Exception
{
this.log.debug("Destroying HAPartition: " + this.getPartitionName());
removeCanonicalAlias();
if (this.distributedState instanceof ManagedDistributedState)
{
((ManagedDistributedState) this.distributedState).destroyService();
}
this.replicantManager.destroyService();
// unregisterDRM();
try
{
if (this.channel != null && this.channel.isOpen())
{
this.channel.close();
}
}
catch (Exception e)
{
this.log.error("Closing channel failed", e);
}
this.log.info("Partition " + this.getPartitionName() + " destroyed.");
}
/**
* Adds an alias to our controller context -- the concatenation of
* {@link #getAliasPrefix()} and {@link #getPartitionName()}.
* This mechanism allows Ejb2HAPartitionDependencyDeployer to add
* dependencies to deployments based on the partition name specified in
* their metadata, without needing to know the bean name of this partition.
*/
private void addCanonicalAlias()
{
if (kernelControllerContext != null)
{
KernelController kc = (KernelController) kernelControllerContext.getController();
String aliasName = getHaPartitionDependencyCreator().getHAPartitionDependencyName(this.partitionName);
try
{
kc.addAlias(aliasName, kernelControllerContext.getName());
}
catch (Throwable t)
{
log.error("Failed adding alias " + aliasName + " to context " + kernelControllerContext.getName(), t);
}
}
}
/**
* Removes the alias created in {@link #addCanonicalAlias()}
*/
private void removeCanonicalAlias()
{
if (kernelControllerContext != null)
{
KernelController kc = (KernelController) kernelControllerContext.getController();
String aliasName = getHaPartitionDependencyCreator().getHAPartitionDependencyName(this.partitionName);
Set<Object> aliases = kernelControllerContext.getAliases();
if (aliases != null && aliases.contains(aliasName))
{
try
{
kc.removeAlias(aliasName);
}
catch (Throwable t)
{
log.error("Failed removing alias " + aliasName + " from context " + kernelControllerContext.getName(), t);
}
}
}
}
// ---------------------------------------------------------- State Transfer
protected void fetchState() throws Exception
{
this.log.info("Fetching serviceState (will wait for " + this.getStateTransferTimeout() +
" milliseconds):");
long start, stop;
this.isStateSet = false;
start = System.currentTimeMillis();
boolean rc = this.channel.getState(null, this.getStateTransferTimeout());
if (rc)
{
synchronized (this.channelLock)
{
while (!this.isStateSet)
{
if (this.setStateException != null)
{
throw this.setStateException;
}
try
{
this.channelLock.wait();
}
catch (InterruptedException iex)
{
}
}
}
stop = System.currentTimeMillis();
this.log.info("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 (this.members)
{
while (this.members.size() == 0)
{
this.log.debug("waiting on viewAccepted()");
try
{
this.members.wait();
}
catch (InterruptedException iex)
{
}
}
}
if (this.isCurrentNodeCoordinator())
{
this.log.info("State could not be retrieved (we are the first member in group)");
}
else
{
throw new IllegalStateException("Initial serviceState transfer failed: " +
"Channel.getState() returned false");
}
}
}
private void getStateInternal(OutputStream stream) throws IOException
{
MarshalledValueOutputStream mvos = null; // don't create until we know we need it
for (Map.Entry<String, HAPartitionStateTransfer> entry: this.stateHandlers.entrySet())
{
HAPartitionStateTransfer subscriber = entry.getValue();
this.log.debug("getState for " + entry.getKey());
Object state = subscriber.getCurrentState();
if (state != null)
{
if (mvos == null)
{
// This is our first write, so need to write the header first
stream.write(SERIALIZABLE_VALUE);
mvos = new MarshalledValueOutputStream(stream);
}
mvos.writeObject(entry.getKey());
mvos.writeObject(state);
}
}
if (mvos == null)
{
// We never wrote any serviceState, so write the NULL header
stream.write(NULL_VALUE);
}
else
{
mvos.writeObject(new StateStreamEnd());
mvos.flush();
mvos.close();
}
}
private void setStateInternal(InputStream stream) throws IOException, ClassNotFoundException
{
byte type = (byte) stream.read();
if (type == EOF_VALUE)
{
this.log.debug("serviceState stream is empty");
return;
}
else if (type == NULL_VALUE)
{
this.log.debug("serviceState is null");
return;
}
long used_mem_before, used_mem_after;
Runtime rt=Runtime.getRuntime();
used_mem_before=rt.totalMemory() - rt.freeMemory();
MarshalledValueInputStream mvis = new MarshalledValueInputStream(stream);
while (true)
{
Object obj = mvis.readObject();
if (obj instanceof StateStreamEnd)
{
break;
}
String key = (String) obj;
this.log.debug("setState for " + key);
Object someState = mvis.readObject();
HAPartitionStateTransfer subscriber = this.stateHandlers.get(key);
if (subscriber != null)
{
try
{
subscriber.setCurrentState((Serializable)someState);
}
catch (Exception e)
{
// Don't let issues with one subscriber affect others
// unless it is DRM, which is really an internal function
// of the HAPartition
// FIXME remove this once DRM is JBC-based
if (DistributedReplicantManagerImpl.SERVICE_NAME.equals(key))
{
if (e instanceof RuntimeException)
{
throw (RuntimeException) e;
}
throw new RuntimeException(e);
}
this.log.error("Caught exception setting serviceState to " + subscriber, e);
}
}
else
{
this.log.debug("There is no stateHandler for: " + key);
}
}
try
{
stream.close();
}
catch(Exception e)
{
this.log.error("Caught exception closing serviceState stream", e);
}
used_mem_after=rt.totalMemory() - rt.freeMemory();
this.log.debug("received serviceState; expanded memory by " +
(used_mem_after - used_mem_before) + " bytes (used memory before: " + used_mem_before +
", used memory after: " + used_mem_after + ")");
}
private void recordSetStateFailure(Throwable t)
{
this.log.error("failed setting serviceState", t);
if (t instanceof Exception)
{
this.setStateException = (Exception) t;
}
else
{
this.setStateException = new Exception(t);
}
}
private void notifyChannelLock()
{
synchronized (this.channelLock)
{
this.channelLock.notifyAll();
}
}
// org.jgroups.MembershipListener implementation ----------------------------------------------
public void suspect(org.jgroups.Address suspected_mbr)
{
this.logHistory ("Node suspected: " + (suspected_mbr==null?"null":suspected_mbr.toString()));
if (this.isCurrentNodeCoordinator ())
{
this.clusterLifeCycleLog.info ("Suspected member: " + suspected_mbr);
}
else
{
this.log.info("Suspected member: " + suspected_mbr);
}
}
public void block()
{
this.flushBlockGate.close();
this.log.debug("Block processed at " + this.me);
}
public void unblock()
{
this.flushBlockGate.open();
this.log.debug("Unblock processed at " + this.me);
}
/** Notification of a cluster view change. This is done from the JG protocol
* handlder 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 seperate thread.
*
* @param newView
*/
public void viewAccepted(View newView)
{
try
{
// we update the view id
this.currentViewId = newView.getVid().getId();
// Keep a list of other members only for "exclude-self" RPC calls
this.jgotherMembers = (Vector<Address>)newView.getMembers().clone();
this.jgotherMembers.remove (this.channel.getLocalAddress());
this.otherMembers = this.translateAddresses (this.jgotherMembers); // TRANSLATE!
Vector<ClusterNode> translatedNewView = this.translateAddresses ((Vector<Address>)newView.getMembers().clone());
this.logHistory ("New view: " + translatedNewView + " with viewId: " + this.currentViewId +
" (old view: " + this.members + " )");
// Save the previous view and make a copy of the new view
Vector<ClusterNode> oldMembers = this.members;
Vector<Address> newjgMembers = (Vector<Address>)newView.getMembers().clone();
Vector<ClusterNode> newMembers = this.translateAddresses(newjgMembers); // TRANSLATE
this.members = newMembers;
this.jgmembers = newjgMembers;
if (oldMembers == null)
{
// Initial viewAccepted
this.log.debug("ViewAccepted: initial members set for partition " + this.getPartitionName() + ": " +
this.currentViewId + " (" + this.members + ")");
this.log.info("Number of cluster members: " + this.members.size());
for(int m = 0; m > this.members.size(); m ++)
{
Object node = this.members.get(m);
this.log.debug(node);
}
this.log.info ("Other members: " + this.otherMembers.size ());
// Wake up the deployer thread blocking in waitForView
this.notifyChannelLock();
return;
}
int difference = newMembers.size() - oldMembers.size();
if (this.isCurrentNodeCoordinator ())
{
this.clusterLifeCycleLog.info ("New cluster view for partition " + this.getPartitionName() + " (id: " +
this.currentViewId + ", delta: " + difference + ") : " + this.members);
}
else
{
this.log.info("New cluster view for partition " + this.getPartitionName() + ": " +
this.currentViewId + " (" + this.members + " delta: " + difference + ")");
}
// Build a ViewChangeEvent for the asynch listeners
ViewChangeEvent event = new ViewChangeEvent();
event.viewId = this.currentViewId;
event.allMembers = translatedNewView;
event.deadMembers = this.getDeadMembers(oldMembers, event.allMembers);
event.newMembers = this.getNewMembers(oldMembers, event.allMembers);
event.originatingGroups = null;
// if the new view occurs because of a merge, we first inform listeners of the merge
if(newView instanceof MergeView)
{
MergeView mergeView = (MergeView) newView;
event.originatingGroups = mergeView.getSubgroups();
}
this.log.debug("membership changed from " + oldMembers.size() + " to " + event.allMembers.size());
// Put the view change to the asynch queue
this.asynchHandler.queueEvent(event);
// Broadcast the new view to the synchronous view change listeners
if (this.allowSyncListeners)
{
this.notifyListeners(this.synchListeners, event.viewId, event.allMembers,
event.deadMembers, event.newMembers, event.originatingGroups);
}
}
catch (Exception ex)
{
this.log.error("ViewAccepted failed", ex);
}
}
private void waitForView() throws Exception
{
synchronized (this.channelLock)
{
if (this.members == null)
{
if (this.connectException != null)
{
throw this.connectException;
}
try
{
this.channelLock.wait(this.getMethodCallTimeout());
}
catch (InterruptedException iex)
{
}
if (this.connectException != null)
{
throw this.connectException;
}
if (this.members == null)
{
throw new IllegalStateException("No view received from Channel");
}
}
}
}
// HAPartition implementation ----------------------------------------------
@ManagementProperty(use={ViewUse.STATISTIC}, description="The identifier for this node in cluster topology views")
public String getNodeName()
{
return this.nodeName;
}
@ManagementProperty(use={ViewUse.CONFIGURATION}, description="The partition's name")
@ManagementObjectID(type="HAPartition")
public String getPartitionName()
{
return this.partitionName;
}
public void setPartitionName(String newName)
{
this.partitionName = newName;
}
public DistributedReplicantManager getDistributedReplicantManager()
{
return this.replicantManager;
}
@SuppressWarnings("deprecation")
public org.jboss.ha.framework.interfaces.DistributedState getDistributedStateService()
{
return this.distributedState;
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="Identifier for the current topology view")
public long getCurrentViewId()
{
return this.currentViewId;
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="The current cluster topology view")
public Vector<String> getCurrentView()
{
Vector<String> result = new Vector<String>(this.members.size());
for (ClusterNode member: this.members)
{
result.add(member.getName());
}
return result;
}
public ClusterNode[] getClusterNodes ()
{
synchronized (this.members)
{
return this.members.toArray(new ClusterNode[this.members.size()]);
}
}
public ClusterNode getClusterNode ()
{
return this.me;
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="Whether this node is acting as the group coordinator for the partition")
public boolean isCurrentNodeCoordinator ()
{
if(this.members == null || this.members.size() == 0 || this.me == null)
{
return false;
}
return this.members.elementAt (0).equals (this.me);
}
// ***************************
// ***************************
// RPC multicast communication
// ***************************
// ***************************
public void registerRPCHandler(String objName, Object subscriber)
{
this.rpcHandlers.put(objName, subscriber);
}
public void registerRPCHandler(String objName, Object subscriber, ClassLoader classloader)
{
this.registerRPCHandler(objName, subscriber);
this.clmap.put(objName, new WeakReference<ClassLoader>(classloader));
}
public void unregisterRPCHandler(String objName, Object subscriber)
{
this.rpcHandlers.remove(objName);
this.clmap.remove(objName);
}
/**
* This function is an abstraction of RpcDispatcher.
*/
@SuppressWarnings("unchecked")
public ArrayList callMethodOnCluster(String objName, String methodName,
Object[] args, Class[] types, boolean excludeSelf) throws Exception
{
return this.callMethodOnCluster(objName, methodName, args, types, excludeSelf, null);
}
@SuppressWarnings("unchecked")
public ArrayList callMethodOnCluster(String objName, String methodName,
Object[] args, Class[] types, boolean excludeSelf, ResponseFilter filter) throws Exception
{
return this.callMethodOnCluster(objName, methodName, args, types, excludeSelf, this.getMethodCallTimeout(), filter);
}
@SuppressWarnings("unchecked")
public ArrayList callMethodOnCluster(String objName, String methodName,
Object[] args, Class[] types, boolean excludeSelf, long methodTimeout, ResponseFilter filter) throws Exception
{
RspList rsp = null;
boolean trace = this.log.isTraceEnabled();
MethodCall m = new MethodCall(objName + "." + methodName, args, types);
RspFilterAdapter rspFilter = filter == null ? null : new RspFilterAdapter(filter);
if(this.channel.flushSupported())
{
this.flushBlockGate.await(this.getStateTransferTimeout());
}
if (excludeSelf)
{
if( trace )
{
this.log.trace("callMethodOnCluster(true), objName="+objName
+", methodName="+methodName+", members="+this.jgotherMembers);
}
rsp = this.dispatcher.callRemoteMethods(this.jgotherMembers, m, GroupRequest.GET_ALL, methodTimeout, false, false, rspFilter);
}
else
{
if( trace )
{
this.log.trace("callMethodOnCluster(false), objName="+objName
+", methodName="+methodName+", members="+this.members);
}
rsp = this.dispatcher.callRemoteMethods(null, m, GroupRequest.GET_ALL, methodTimeout, false, false, rspFilter);
}
return this.processResponseList(rsp, trace);
}
/**
* Calls method on Cluster coordinator node only. The cluster coordinator node is the first node to join the
* cluster.
* and is replaced
* @param objName
* @param methodName
* @param args
* @param types
* @param excludeSelf
* @return an array of responses from remote nodes
* @throws Exception
*/
@SuppressWarnings("unchecked")
public ArrayList callMethodOnCoordinatorNode(String objName, String methodName,
Object[] args, Class[] types,boolean excludeSelf) throws Exception
{
return this.callMethodOnCoordinatorNode(objName,methodName,args,types,excludeSelf, this.getMethodCallTimeout());
}
/**
* Calls method on Cluster coordinator node only. The cluster coordinator node is the first node to join the
* cluster.
* and is replaced
* @param objName
* @param methodName
* @param args
* @param types
* @param excludeSelf
* @param methodTimeout
* @return an array of responses from remote nodes
* @throws Exception
*/
@SuppressWarnings("unchecked")
public ArrayList callMethodOnCoordinatorNode(String objName, String methodName,
Object[] args, Class[] types,boolean excludeSelf, long methodTimeout) 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);
}
// the first cluster view member is the coordinator
Vector<Address> coordinatorOnly = new Vector<Address>();
// If we are the coordinator, only call ourself if 'excludeSelf' is false
if (false == this.isCurrentNodeCoordinator () ||
false == excludeSelf)
{
coordinatorOnly.addElement(this.jgmembers.elementAt(0));
}
RspList rsp = this.dispatcher.callRemoteMethods(coordinatorOnly, m, GroupRequest.GET_ALL, methodTimeout);
return this.processResponseList(rsp, trace);
}
/**
* Calls method synchrounously on target node only.
* @param serviceName Name of the target service name on which calls are de-multiplexed
* @param methodName name of the Java method to be called on remote services
* @param args array of Java Object representing the set of parameters to be
* given to the remote method
* @param types The types of the parameters
* node of the partition or only on remote nodes
* @param targetNode is the target of the call
* @return the value returned by the target method
* @throws Exception Throws if a communication exception occurs
*/
@SuppressWarnings("unchecked")
public Object callMethodOnNode(String serviceName, String methodName,
Object[] args, Class[] types, long methodTimeout, ClusterNode targetNode) throws Throwable
{
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 rc = this.dispatcher.callRemoteMethod(((ClusterNodeImpl)targetNode).getOriginalJGAddress(), m, GroupRequest.GET_FIRST, methodTimeout);
if (rc != null)
{
Object item = rc;
if (item instanceof Rsp)
{
Rsp response = (Rsp) item;
// Only include received responses
boolean wasReceived = response.wasReceived();
if( wasReceived == true )
{
item = response.getValue();
if (!(item instanceof NoHandlerForRPC))
{
rc = item;
}
}
else if( trace )
{
this.log.trace("Ignoring non-received response: "+response);
}
}
else
{
if (!(item instanceof NoHandlerForRPC))
{
rc = item;
}
else if( trace )
{
this.log.trace("Ignoring NoHandlerForRPC");
}
}
}
return rc;
}
/**
* Calls method on target node only.
* @param serviceName Name of the target service name on which calls are de-multiplexed
* @param methodName name of the Java method to be called on remote services
* @param args array of Java Object representing the set of parameters to be
* given to the remote method
* @param types The types of the parameters
* node of the partition or only on remote nodes
* @param targetNode is the target of the call
* @return none
* @throws Exception Throws if a communication exception occurs
*/
@SuppressWarnings("unchecked")
public void callAsyncMethodOnNode(String serviceName, String methodName,
Object[] args, Class[] types, long methodTimeout, ClusterNode targetNode) throws Throwable
{
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);
}
this.dispatcher.callRemoteMethod(((ClusterNodeImpl)targetNode).getOriginalJGAddress(), m, GroupRequest.GET_NONE, methodTimeout);
}
private ArrayList<Object> processResponseList(RspList rsp, boolean trace)
{
ArrayList<Object> rtn = new ArrayList<Object>();
if (rsp != null)
{
for (Object item : rsp.values())
{
if (item instanceof Rsp)
{
Rsp response = (Rsp) item;
// Only include received responses
boolean wasReceived = response.wasReceived();
if( wasReceived == true )
{
item = response.getValue();
if (!(item instanceof NoHandlerForRPC))
{
rtn.add(item);
}
}
else if( trace )
{
this.log.trace("Ignoring non-received response: "+response);
}
}
else
{
if (!(item instanceof NoHandlerForRPC))
{
rtn.add(item);
}
else if( trace )
{
this.log.trace("Ignoring NoHandlerForRPC");
}
}
}
}
return rtn;
}
/**
* This function is an abstraction of RpcDispatcher for asynchronous messages
*/
@SuppressWarnings("unchecked")
public void callAsynchMethodOnCluster(String objName, String methodName,
Object[] args, Class[] types, boolean excludeSelf) throws Exception
{
boolean trace = this.log.isTraceEnabled();
MethodCall m = new MethodCall(objName + "." + methodName, args, types);
if(this.channel.flushSupported())
{
this.flushBlockGate.await(this.getStateTransferTimeout());
}
if (excludeSelf)
{
if( trace )
{
this.log.trace("callAsynchMethodOnCluster(true), objName="+objName
+", methodName="+methodName+", members="+this.jgotherMembers);
}
this.dispatcher.callRemoteMethods(this.jgotherMembers, m, GroupRequest.GET_NONE, this.getMethodCallTimeout());
}
else
{
if( trace )
{
this.log.trace("callAsynchMethodOnCluster(false), objName="+objName
+", methodName="+methodName+", members="+this.members);
}
this.dispatcher.callRemoteMethods(null, m, GroupRequest.GET_NONE, this.getMethodCallTimeout());
}
}
// *************************
// *************************
// State transfer management
// *************************
// *************************
public void subscribeToStateTransferEvents(String objectName, HAPartitionStateTransfer subscriber)
{
this.stateHandlers.put(objectName, subscriber);
}
public void unsubscribeFromStateTransferEvents(String objectName, HAPartitionStateTransfer subscriber)
{
this.stateHandlers.remove(objectName);
}
// *************************
// *************************
// Group Membership listeners
// *************************
// *************************
public void registerMembershipListener(HAMembershipListener listener)
{
boolean isAsynch = (this.allowSyncListeners == false)
|| (listener instanceof AsynchHAMembershipListener)
|| (listener instanceof AsynchHAMembershipExtendedListener);
if( isAsynch ) {
synchronized(this.asynchListeners) {
this.asynchListeners.add(listener);
}
}
else {
synchronized(this.synchListeners) {
this.synchListeners.add(listener);
}
}
}
public void unregisterMembershipListener(HAMembershipListener listener)
{
boolean isAsynch = (this.allowSyncListeners == false)
|| (listener instanceof AsynchHAMembershipListener)
|| (listener instanceof AsynchHAMembershipExtendedListener);
if( isAsynch ) {
synchronized(this.asynchListeners) {
this.asynchListeners.remove(listener);
}
}
else {
synchronized(this.synchListeners) {
this.synchListeners.remove(listener);
}
}
}
@ManagementProperty(use={ViewUse.CONFIGURATION, ViewUse.RUNTIME},
description="Whether to allow synchronous notifications of topology changes")
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;
}
// AsynchEventHandler.AsynchEventProcessor -----------------------
public void processEvent(Object event)
{
ViewChangeEvent vce = (ViewChangeEvent) event;
this.notifyListeners(this.asynchListeners, vce.viewId, vce.allMembers,
vce.deadMembers, vce.newMembers, vce.originatingGroups);
}
// Public ------------------------------------------------------------------
@SuppressWarnings("deprecation")
public void setDistributedStateImpl(org.jboss.ha.framework.interfaces.DistributedState distributedState)
{
this.distributedState = distributedState;
}
// Protected -----------------------------------------------------
protected 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");
}
}
}
}
/**
* Helper method that binds the partition in the JNDI tree.
* @param jndiName Name under which the object must be bound
* @param who Object to bind in JNDI
* @param classType Class type under which should appear the bound object
* @param ctx Naming context under which we bind the object
* @throws Exception Thrown if a naming exception occurs during binding
*/
protected void bind(String jndiName, Object who, Class<?> classType, Context ctx) throws Exception
{
// Ah ! This service isn't serializable, so we use a helper class
//
NonSerializableFactory.bind(jndiName, who);
Name n = ctx.getNameParser("").parse(jndiName);
while (n.size () > 1)
{
String ctxName = n.get (0);
try
{
ctx = (Context)ctx.lookup (ctxName);
}
catch (NameNotFoundException e)
{
this.log.debug ("creating Subcontext " + ctxName);
ctx = ctx.createSubcontext (ctxName);
}
n = n.getSuffix (1);
}
// The helper class NonSerializableFactory uses address type nns, we go on to
// use the helper class to bind the service object in JNDI
//
StringRefAddr addr = new StringRefAddr("nns", jndiName);
Reference ref = new Reference(classType.getName (), addr, NonSerializableFactory.class.getName (), null);
ctx.rebind (n.get (0), ref);
}
/**
* 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.
*/
protected 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=(Vector<ClusterNode>)oldMembers.clone();
dead.removeAll(newMembers);
this.log.debug("dead members: " + dead);
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
*/
protected 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=(Vector<ClusterNode>)allMembers.clone();
newMembers.removeAll(oldMembers);
return newMembers;
}
protected void notifyListeners(ArrayList<HAMembershipListener> theListeners, long viewID,
Vector<ClusterNode> allMembers, Vector<ClusterNode> deadMembers, Vector<ClusterNode> newMembers,
Vector<View> originatingGroups)
{
this.log.debug("Begin notifyListeners, viewID: "+viewID);
synchronized(theListeners)
{
// JBAS-3619 -- don't hold synch lock while notifying
theListeners = (ArrayList<HAMembershipListener>) theListeners.clone();
}
for (int i = 0; i < theListeners.size(); i++)
{
HAMembershipListener aListener = null;
try
{
aListener = theListeners.get(i);
if(originatingGroups != null && (aListener instanceof HAMembershipExtendedListener))
{
HAMembershipExtendedListener exListener = (HAMembershipExtendedListener) aListener;
exListener.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("HAMembershipListener callback failure: "+aListener, e);
}
}
this.log.debug("End notifyListeners, viewID: "+viewID);
}
/*
* Allows caller to specify whether the partition instance should be bound into JNDI. Default value is true.
* This method must be called before the partition is started as the binding occurs during startup.
*
* @param bind Whether to bind the partition into JNDI.
*/
public void setBindIntoJndi(boolean bind)
{
this.bindIntoJndi = bind;
}
/*
* Allows caller to determine whether the partition instance has been bound into JNDI.
*
* @return true if the partition has been bound into JNDI.
*/
@ManagementProperty(description="Whether this HAPartition should bind itself into JNDI")
public boolean getBindIntoJndi()
{
return this.bindIntoJndi;
}
public ThreadPool getThreadPool()
{
return this.threadPool;
}
public void setThreadPool(ThreadPool threadPool)
{
this.threadPool = threadPool;
}
public synchronized HAPartitionDependencyCreator getHaPartitionDependencyCreator()
{
if (haPartitionDependencyCreator == null)
{
haPartitionDependencyCreator = DefaultHAPartitionDependencyCreator.INSTANCE;
}
return haPartitionDependencyCreator;
}
public synchronized void setHaPartitionDependencyCreator(HAPartitionDependencyCreator haPartitionDependencyCreator)
{
this.haPartitionDependencyCreator = haPartitionDependencyCreator;
}
protected Vector<ClusterNode> translateAddresses(Vector<Address> addresses)
{
if (addresses == null)
{
return null;
}
Vector<ClusterNode> result = new Vector<ClusterNode>(addresses.size());
for (Address address: addresses)
{
result.add(new ClusterNodeImpl((IpAddress) address));
}
return result;
}
public void logHistory (String message)
{
try
{
this.history.add(new SimpleDateFormat().format (new Date()) + " : " + message);
}
catch (Exception ignored){}
}
// --------------------------------------------------- ClusterPartitionMBean
@ManagementOperation(description="Gets a listing of significant events since " +
"the instantiation of this service",
impact=Impact.ReadOnly)
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();
}
@ManagementOperation(description="Gets an XML format listing of significant events since " +
"the instantiation of this service",
impact=Impact.ReadOnly)
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();
}
/**
* Deprecated; always returns <code>false</code>.
*
* @return <code>false</code>
*
* @deprecated will be removed in next major release
*/
@Deprecated
public boolean getDeadlockDetection()
{
return false;
}
/**
* Deprecated; logs a WARN message if invoked.
*
* @param doIt ignored
*
* @deprecated will be removed in next major release
*/
@Deprecated
public void setDeadlockDetection(boolean doit)
{
log.warn("Property deadlockDetection has been deprecated; setting it has no effect");
}
@Deprecated
public HAPartition getHAPartition()
{
return this;
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="The release version of JGroups")
public String getJGroupsVersion()
{
return Version.description + "( " + Version.cvs + ")";
}
// @ManagementProperty(name="distributedReplicantManager", use={ViewUse.STATISTIC}, description="The DistributedReplicantManager")
// @ManagementObjectRef(type="DistributedReplicantManager")
// public String getDRMName()
// {
// return getPartitionName();
// }
public DistributedReplicantManagerImpl getDistributedReplicantManagerImpl()
{
return this.replicantManager;
}
public ChannelFactory getChannelFactory()
{
return this.channelFactory;
}
public HAPartitionCacheHandler getCacheHandler()
{
return this.cacheHandler;
}
public void setCacheHandler(HAPartitionCacheHandler cacheHandler)
{
this.cacheHandler = cacheHandler;
this.cacheConfigName = cacheHandler == null ? null : cacheHandler.getCacheConfigName();
}
@ManagementProperty(use={ViewUse.STATISTIC},
description="Name of the CacheManager configuration used for deriving the JGroups channel stack name")
public String getCacheConfigName()
{
return this.cacheConfigName;
}
@ManagementProperty(use={ViewUse.STATISTIC}, description="Name of the JGroups protocol stack configuration")
public String getChannelStackName()
{
return this.stackName;
}
public InetAddress getNodeAddress()
{
return this.nodeAddress;
}
public void setNodeAddress(InetAddress address)
{
this.nodeAddress = address;
}
@ManagementProperty(description="Time (in ms) to allow for state transfer to finish")
public long getStateTransferTimeout() {
return this.state_transfer_timeout;
}
public void setStateTransferTimeout(long timeout)
{
this.state_transfer_timeout = timeout;
}
@ManagementProperty(use={ViewUse.CONFIGURATION, ViewUse.RUNTIME},
description="Time (in ms) to allow for group RPCs to return")
public long getMethodCallTimeout() {
return this.method_call_timeout;
}
public void setMethodCallTimeout(long timeout)
{
this.method_call_timeout = timeout;
}
// KernelControllerContextAware --------------------------------------------
@Override
public void setKernelControllerContext(KernelControllerContext controllerContext) throws Exception
{
super.setKernelControllerContext(controllerContext);
this.kernelControllerContext = controllerContext;
}
@Override
public void unsetKernelControllerContext(KernelControllerContext controllerContext) throws Exception
{
super.unsetKernelControllerContext(controllerContext);
this.kernelControllerContext = null;
}
// ManagedObject interface for DRM ---------------------------------------
@ManagementOperation(description="List all known DistributedReplicantManager keys and the nodes that have registered bindings",
impact=Impact.ReadOnly)
public String listDRMContent() throws Exception
{
return this.replicantManager == null ? null : this.replicantManager.listContent();
}
@ManagementOperation(description="List in XML format all known DistributedReplicantManager keys and the nodes that have registered bindings",
impact=Impact.ReadOnly)
public String listDRMContentAsXml() throws Exception
{
return this.replicantManager == null ? null : this.replicantManager.listXmlContent();
}
@ManagementOperation(description="Returns the names of the nodes that have registered objects with the DistributedReplicantManager under the given key",
impact=Impact.ReadOnly,
params={@ManagementParameter(name="key",
description="The name of the service")})
@SuppressWarnings("deprecation")
public List<String> lookupDRMNodeNames(String key)
{
return this.replicantManager == null ? null : this.replicantManager.lookupReplicantsNodeNames(key);
}
@ManagementOperation(description="Returns a hash of the list of nodes that " +
"have registered an object with the DistributedReplicantManager under the given key",
impact=Impact.ReadOnly,
params={@ManagementParameter(name="key",
description="The name of the service")})
public int getDRMServiceViewId(String key)
{
return this.replicantManager == null ? null : this.replicantManager.getReplicantsViewId(key);
}
@ManagementOperation(description="Returns whether the DistributedReplicantManager 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 isDRMMasterForService(String key)
{
return this.replicantManager == null ? null : this.replicantManager.isMasterReplica(key);
}
@ManagementOperation(description="Get a collection of the names of all keys for which the DistributedReplicantManager has bindings",
impact=Impact.ReadOnly)
public Collection<String> getDRMServiceNames()
{
return this.replicantManager == null ? null : this.replicantManager.getAllServices();
}
// Protected --------------------------------------------------------------
/**
* Creates an object from a byte buffer
*/
protected 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
*/
protected 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
*/
protected 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
*/
protected 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();
}
// @Override
// public void postRegister(Boolean registrationDone)
// {
// super.postRegister(registrationDone);
// registerDRM();
// }
// Private -------------------------------------------------------
// Inner classes -------------------------------------------------
private class MessageListenerAdapter
implements ExtendedMessageListener
{
public void getState(OutputStream stream)
{
ClusterPartition.this.logHistory ("getState called on partition");
ClusterPartition.this.log.debug("getState called.");
try
{
ClusterPartition.this.getStateInternal(stream);
}
catch (Exception ex)
{
ClusterPartition.this.log.error("getState failed", ex);
}
}
public void getState(String state_id, OutputStream ostream)
{
throw new UnsupportedOperationException("Not implemented; see http://jira.jboss.com/jira/browse/JBAS-3594");
}
public byte[] getState(String state_id)
{
throw new UnsupportedOperationException("Not implemented; see http://jira.jboss.com/jira/browse/JBAS-3594");
}
public void setState(InputStream stream)
{
ClusterPartition.this.logHistory ("setState called on partition");
try
{
if (stream == null)
{
ClusterPartition.this.log.debug("transferred serviceState is null (may be first member in cluster)");
}
else
{
ClusterPartition.this.setStateInternal(stream);
}
ClusterPartition.this.isStateSet = true;
}
catch (Throwable t)
{
ClusterPartition.this.recordSetStateFailure(t);
}
finally
{
// Notify waiting thread that serviceState has been set.
ClusterPartition.this.notifyChannelLock();
}
}
public byte[] getState()
{
ClusterPartition.this.logHistory ("getState called on partition");
ClusterPartition.this.log.debug("getState called.");
try
{
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
ClusterPartition.this.getStateInternal(baos);
return baos.toByteArray();
}
catch (Exception ex)
{
ClusterPartition.this.log.error("getState failed", ex);
}
return null; // This will cause the receiver to get a "false" on the channel.getState() call
}
public void setState(String state_id, byte[] state)
{
throw new UnsupportedOperationException("Not implemented; see http://jira.jboss.com/jira/browse/JBAS-3594");
}
public void setState(String state_id, InputStream istream)
{
throw new UnsupportedOperationException("Not implemented; see http://jira.jboss.com/jira/browse/JBAS-3594");
}
public void receive(org.jgroups.Message msg)
{ /* complete */}
public void setState(byte[] obj)
{
ClusterPartition.this.logHistory ("setState called on partition");
try
{
if (obj == null)
{
ClusterPartition.this.log.debug("transferred serviceState is null (may be first member in cluster)");
}
else
{
ByteArrayInputStream bais = new ByteArrayInputStream(obj);
ClusterPartition.this.setStateInternal(bais);
bais.close();
}
ClusterPartition.this.isStateSet = true;
}
catch (Throwable t)
{
ClusterPartition.this.recordSetStateFailure(t);
}
finally
{
// Notify waiting thread that serviceState has been set.
ClusterPartition.this.notifyChannelLock();
}
}
}
/**
* A simple data class containing the view change event needed to
* notify the HAMembershipListeners
*/
private static class ViewChangeEvent
{
long viewId;
Vector<ClusterNode> deadMembers;
Vector<ClusterNode> newMembers;
Vector<ClusterNode> allMembers;
Vector<View> originatingGroups;
}
private class RequestMarshallerImpl implements org.jgroups.blocks.RpcDispatcher.Marshaller
{
public Object objectFromByteBuffer(byte[] buf) throws Exception
{
return ClusterPartition.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 ClusterPartition.this.objectToByteBufferInternal(new Object[]{serviceName, ClusterPartition.this.objectToByteBufferInternal(obj)});
}
return ClusterPartition.this.objectToByteBufferInternal(obj);
}
}
private class ResponseMarshallerImpl implements org.jgroups.blocks.RpcDispatcher.Marshaller
{
public Object objectFromByteBuffer(byte[] buf) throws Exception
{
boolean trace = ClusterPartition.this.log.isTraceEnabled();
Object retval = ClusterPartition.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 = ClusterPartition.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 )
{
ClusterPartition.this.log.trace("overriding response Thread ContextClassLoader for service " + serviceName);
}
overrideCL = true;
Thread.currentThread().setContextClassLoader(loader);
}
retval = ClusterPartition.this.objectFromByteBufferResponseInternal(payload);
return retval;
}
finally
{
if (overrideCL == true)
{
ClusterPartition.this.log.trace("resetting response classloader");
Thread.currentThread().setContextClassLoader(previousCL);
}
}
}
public byte[] objectToByteBuffer(Object obj) throws Exception
{
return ClusterPartition.this.objectToByteBufferResponseInternal(obj);
}
}
/**
* Overrides RpcDispatcher.Handle so that we can dispatch to many
* different objects.
*/
private class RpcHandler extends RpcDispatcher
{
private RpcHandler(Channel channel, MessageListener l, MembershipListener l2, Object server_obj,
boolean deadlock_detection)
{
super(channel, l, l2, server_obj, deadlock_detection);
}
/**
* 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 " + ClusterPartition.this.getPartitionName() + " received msg");
}
if(req == null || req.getBuffer() == null)
{
this.log.warn("Partition " + ClusterPartition.this.getPartitionName() + " message or message buffer is null!");
return null;
}
try
{
Object wrapper = ClusterPartition.this.objectFromByteBufferInternal(req.getBuffer());
if(wrapper == null || !(wrapper instanceof Object[]))
{
this.log.warn("Partition " + ClusterPartition.this.getPartitionName() + " 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 = ClusterPartition.this.rpcHandlers.get(service);
if (handler == null)
{
if( trace )
{
this.log.trace("Partition " + ClusterPartition.this.getPartitionName() + " no rpc handler registered under service " + service);
}
return new NoHandlerForRPC();
}
}
catch(Exception e)
{
this.log.warn("Partition " + ClusterPartition.this.getPartitionName() + " 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 = ClusterPartition.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 = ClusterPartition.this.objectFromByteBufferInternal(request_bytes);
}
catch (Exception e)
{
this.log.warn("Partition " + ClusterPartition.this.getPartitionName() + " 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 " + ClusterPartition.this.getPartitionName() + " 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 = ClusterPartition.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 " + ClusterPartition.this.getPartitionName() + " rpc call threw exception", t);
}
retval = t;
}
return retval;
}
}
/**
* Adapted from org.jboss.cache.util.concurrent.ReclosableLatch.
* @author Manik Surtani
*/
private static class ThreadGate
{
private static final int OPEN = 1;
private static final int CLOSED = -1;
private static class Sync extends AbstractQueuedSynchronizer
{
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 void await() throws InterruptedException
{
this.sync.acquireSharedInterruptibly(0);
}
public boolean await(long timeout) throws InterruptedException
{
return this.sync.tryAcquireSharedNanos(0, TimeUnit.MILLISECONDS.toNanos(timeout));
}
}
private void setupLoggers(String partitionName)
{
if (partitionName == null)
{
this.log = Logger.getLogger(HAPartition.class.getName());
this.clusterLifeCycleLog = Logger.getLogger(HAPartition.class.getName() + ".lifecycle");
}
else
{
this.log = Logger.getLogger(HAPartition.class.getName() + "." + partitionName);
this.clusterLifeCycleLog = Logger.getLogger(HAPartition.class.getName() + ".lifecycle." + partitionName);
}
}
// private synchronized void registerDRM()
// {
// MBeanServer mbs = getServer();
// if (this.replicantManager != null && mbs != null)
// {
// try
// {
// ObjectName oname = getDRMObjectName();
// mbs.registerMBean(this.replicantManager, oname);
// }
// catch (JMException e)
// {
// log.error("Unable to register DRM in JMX", e);
// }
// }
// }
// private void unregisterDRM()
// {
// MBeanServer mbs = getServer();
// if (this.replicantManager != null && mbs != null)
// {
// try
// {
// ObjectName oname = getDRMObjectName();
// if (mbs.isRegistered(oname))
// {
// mbs.unregisterMBean(oname);
// }
// }
// catch (JMException e)
// {
// log.error("Unable to register DRM in JMX", e);
// }
// }
//
// }
// private ObjectName getDRMObjectName() throws MalformedObjectNameException
// {
// ObjectName oname = new ObjectName(DistributedReplicantManagerImpl.OBJECT_NAME_BASE + ",partitionName=" + getPartitionName());
// return oname;
// }
}