package org.jboss.seam.jmx;
import static org.jboss.seam.ScopeType.APPLICATION;
import static org.jboss.seam.annotations.Install.BUILT_IN;
import java.util.Iterator;
import java.util.Vector;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.servlet.ServletContext;
import org.jboss.seam.Seam;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
/**
* The purpose of this component is to detect a clustered environment and
* to inform the HttpSessionListener whether the origin of a session destroyed
* event is the failover of a session from one node to the next. If a node
* is failing over, we don't want the SFSBs referenced by the session to be
* destroyed.
*
* @author Dan Allen
*/
@Name("org.jboss.seam.jmx.jbossClusterMonitor")
@BypassInterceptors
@Scope(APPLICATION)
@Startup
@Install(precedence=BUILT_IN, classDependencies="org.jgroups.MembershipListener")
public class JBossClusterMonitor
{
private static Log log = Logging.getLog(JBossClusterMonitor.class);
private MBeanServer jbossMBeanServer;
private boolean clustered;
private ObjectName clusteringCacheObjectName;
private ObjectName serverObjectName;
@Create
public void create()
{
jbossMBeanServer = locateJBoss();
if (!isJBoss())
{
return;
}
try
{
clusteringCacheObjectName = new ObjectName("jboss.cache:service=TomcatClusteringCache");
serverObjectName = new ObjectName("jboss.system:type=Server");
}
catch (MalformedObjectNameException e)
{
log.warn("Invalid JMX name: " + e.getMessage());
}
try
{
jbossMBeanServer.getMBeanInfo(clusteringCacheObjectName);
clustered = true;
log.info("JBoss cluster detected");
}
catch (Exception e) {}
}
public boolean isClustered()
{
return clustered;
}
/**
* Consults the jboss.system:type=Server MBean to determine if this instance
* of JBoss AS is currently being shutdown. Note that the flag only returns
* true if the shutdown() method on this MBean is used. It does not detect a
* force halt via a process signal (i.e., CTRL-C).
*/
public boolean nodeIsShuttingDown()
{
if (!isJBoss())
{
return false;
}
try
{
return (Boolean) jbossMBeanServer.getAttribute(serverObjectName, "InShutdown");
}
catch (Exception e)
{
return false;
}
}
public boolean isLastNode()
{
if (!clustered)
{
return true;
}
// other options
// object name => jboss.jgroups:cluster=DefaultPartition,type=channel
// object name => jboss.jgroups:cluster=Tomcat-Cluster,type=channel
// attribute => NumberOfTasksInTimer
try
{
return ((Vector) jbossMBeanServer.getAttribute(clusteringCacheObjectName, "Members")).size() == 1;
}
catch (Exception e) {
log.warn("Could not determine number of members in cluster", e);
return true;
}
}
public boolean failover()
{
return isClustered() && nodeIsShuttingDown() && !isLastNode();
}
public boolean isJBoss()
{
return jbossMBeanServer != null;
}
protected MBeanServer locateJBoss()
{
for (Object o: MBeanServerFactory.findMBeanServer(null)) {
MBeanServer server = (MBeanServer) o;
if ("jboss".equals(server.getDefaultDomain())) {
return server;
}
}
return null;
}
// FIXME my sense is that this could lookup could be more elegant or conforming
public static JBossClusterMonitor getInstance(ServletContext ctx)
{
return (JBossClusterMonitor) ctx.getAttribute(Seam.getComponentName(JBossClusterMonitor.class));
}
}