package org.jboss.mx.remote.server;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.util.*;
import javax.management.AttributeChangeNotification;
import javax.management.DynamicMBean;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryExp;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;
import org.jboss.mx.remote.*;
import org.jboss.mx.remote.connector.ConnectorFactory;
import org.jboss.mx.remote.discovery.DetectionNotification;
import org.jboss.mx.remote.event.ClassEventFilter;
import org.jboss.mx.remote.event.CompositeEventFilter;
import org.jboss.mx.remote.event.LocalMBeanEventFilter;
import org.jboss.mx.remote.event.CompositeQueryExp;
import org.jboss.mx.util.ProxyContext;
/**
* CascadingAgent is responsible for exposing Remote MBeanServers and MBeans to the
* local MBeanServer as if they were local MBeans.
*
* @author <a href="mailto:telrod@e2technologies.net">Tom Elrod</a>
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @version $Revision: 1.13 $
*/
public class CascadingAgent extends RemoteServiceMBeanSupport
implements CascadingAgentMBean, NotificationListener
{
private String transports[];
private String transportslist;
private Map partitions = new HashMap(1);
private String partitionslist = DetectionNotification.DEFAULT_PARTITION;
private final Map connectors = Collections.synchronizedMap(new HashMap());
private final Map objectmap = Collections.synchronizedMap(new HashMap());
private final List querylist = Collections.synchronizedList(new ArrayList(2));
private QueryExp query;
private final Object LOCK = new Object();
protected final SynchronizedLong attributeSequence = new SynchronizedLong(0);
public CascadingAgent()
{
partitions.put(DetectionNotification.DEFAULT_PARTITION, DetectionNotification.DEFAULT_PARTITION);
addQuery(Query.not(DomainValueExp.eq("JMImplementation")));
}
public void setPartitions(String list)
{
String old = this.partitionslist;
partitions.clear();
partitions.put(DetectionNotification.DEFAULT_PARTITION, DetectionNotification.DEFAULT_PARTITION);
StringBuffer buf = new StringBuffer(DetectionNotification.DEFAULT_PARTITION);
if (list != null)
{
StringTokenizer tok = new StringTokenizer(list, ",");
buf.append(",");
while (tok.hasMoreTokens())
{
String token = tok.nextToken();
partitions.put(token, token);
buf.append(token);
if (tok.hasMoreTokens())
{
buf.append(",");
}
}
}
partitionslist = buf.toString();
fireAttributeChange("cascadingagent.partition.changed", "Partitions", String.class, old, this.partitionslist);
}
/**
* return a comma-separated list of partitions visible by this cascading agent
*
* @return partition list string
*/
public String getPartitions()
{
return partitionslist;
}
/**
* set one or more comma-separated QueryExp by classname
*
* @param classname
*/
public void setQuery(String classname)
throws Exception
{
StringTokenizer tok=new StringTokenizer(classname,",");
while(tok.hasMoreTokens())
{
String cl = tok.nextToken();
QueryExp query=(QueryExp)Thread.currentThread().getContextClassLoader().loadClass(cl).newInstance();
addQuery(query);
}
}
/**
* return a comma-separated list of QueryExp class names
*
* @return list of class names
*/
public String getQuery()
{
Iterator i = querylist.iterator();
StringBuffer buf=new StringBuffer();
while(i.hasNext())
{
QueryExp query=(QueryExp)i.next();
buf.append(query.getClass().getName());
if (i.hasNext())
{
buf.append(",");
}
}
return buf.toString();
}
private void rebuildQuery ()
{
if (querylist==null || querylist.isEmpty())
{
query=null;
return;
}
QueryExp exp[]=new QueryExp[querylist.size()];
Iterator i = querylist.iterator();
int c=0;
while(i.hasNext())
{
exp[c]=(QueryExp)i.next();
if (log.isDebugEnabled())
{
log.debug(" Query ["+c+"] = "+exp.getClass().getName());
}
c++;
}
// reset filter
query=new CompositeQueryExp(exp);
}
/**
* add a filter to the cascading agent that will be used when querying remote mbeans
*
* @param query
*/
public void addQuery (QueryExp query)
{
querylist.add(query);
rebuildQuery();
}
/**
* remove a previously added QueryExp
*
* @param query
*/
public void removeQuery (QueryExp query)
{
querylist.remove(query);
rebuildQuery();
}
/**
* get all the QueryExp that are used when querying remote mbeans
*
* @return query expressions
*/
public Collection getAllQueries()
{
return querylist;
}
protected void startService() throws Exception
{
super.startService();
// add a notification listener for all events related to mbeanserver
getServer().addNotificationListener(JMXUtil.getMBeanServerObjectName(), this, null, null);
// add listeners
Set beans = JMXUtil.queryLocalMBeans(getServer(), new ObjectName("jmx.remoting:type=Detector,*"), null);
Iterator iter = beans.iterator();
while (iter.hasNext())
{
ObjectInstance instance = (ObjectInstance) iter.next();
if (log.isDebugEnabled())
{
log.debug("registering Detector: " + instance.getObjectName());
}
getServer().addNotificationListener(instance.getObjectName(), this, new LocalMBeanEventFilter(), null);
}
}
protected void stopService() throws Exception
{
super.stopService();
// remove a notification listener for all events related to mbeanserver
getServer().removeNotificationListener(JMXUtil.getMBeanServerObjectName(), this);
// remove listeners
Set beans = JMXUtil.queryLocalMBeans(getServer(), new ObjectName("jmx.remoting:type=Detector,*"), null);
Iterator iter = beans.iterator();
while (iter.hasNext())
{
ObjectInstance instance = (ObjectInstance) iter.next();
getServer().removeNotificationListener(instance.getObjectName(), this);
}
synchronized(connectors)
{
// remove all connectors
iter = connectors.values().iterator();
while (iter.hasNext())
{
Connector con = (Connector) iter.next();
iter.remove();
unregisterRemoteServer(con.key, con);
}
}
}
/**
* helper method for firing an attribute change event
*
* @param event
* @param attribute
* @param type
* @param oldVal
* @param newVal
*/
protected void fireAttributeChange(String event, String attribute, Class type, Object oldVal, Object newVal)
{
if ((oldVal == null && newVal != null) ||
(oldVal != null && newVal == null) ||
(oldVal.equals(newVal) == false))
{
sendNotification(
new AttributeChangeNotification(
this,
attributeSequence.increment(),
System.currentTimeMillis(),
event,
attribute,
type.getName(),
oldVal,
newVal));
}
}
/**
* return the event types for the MBean
*
* @return notification metadata
*/
public MBeanNotificationInfo[] getNotificationInfo()
{
MBeanNotificationInfo ni[] = new MBeanNotificationInfo[1];
ni[0] = new MBeanNotificationInfo(new String[]{"cascadingagent.transports.changed", "cascadingagent.partition.changed"}, AttributeChangeNotification.class.getName(), "attribute change notification");
return ni;
}
/**
* set a comma-separated list of transports to use
*
* @param list
*/
public void setTransports(String list)
{
String old = transportslist;
if (list == null || list.trim().length() == 0)
{
transports = null;
return;
}
StringTokenizer tok = new StringTokenizer(list, ",");
transports = new String[tok.countTokens()];
int c = 0;
while (tok.hasMoreTokens())
{
String transport = tok.nextToken();
transports[c++] = transport;
}
this.transportslist = list;
fireAttributeChange("cascadingagent.transports.changed", "Transports", String.class, old, this.transportslist);
}
/**
* get a set of transports used as a comma separated list
*
* @return list of supported transports
*/
public String getTransports()
{
StringBuffer buf = new StringBuffer();
if (transports != null)
{
for (int c = 0; c < transports.length; c++)
{
buf.append(transports[c]);
if (c + 1 < transports.length)
{
buf.append(",");
}
}
}
return buf.toString();
}
protected boolean isInterestedTransport(String trans)
{
if (transports == null)
{
return true;
}
for (int c = 0; c < transports.length; c++)
{
if (transports[c].equalsIgnoreCase(trans))
{
return true;
}
}
return false;
}
private final class Connector
{
ObjectName remoteName;
ObjectName localName;
MBeanServer server;
String serverId;
String instanceId;
InetAddress ip;
String key;
String transport;
}
private final class MBean
{
ObjectName localName;
ObjectName remoteName;
Connector connector;
DynamicMBean mbean;
}
/**
* returns true if the ObjectName already exists on the current server
*
* @param obj
* @return true if object name exists; false otherwise
*/
protected boolean alreadyExists(ObjectName obj)
{
return getServer().queryMBeans(obj, null).iterator().hasNext();
}
/**
* register a remote server
*
* @param con
* @throws Exception
*/
protected void registerRemoteServer(String k, Connector con)
throws Exception
{
synchronized (LOCK)
{
if (log.isDebugEnabled())
{
log.debug("Found a new server with id: "+con.serverId+" at: "+con.ip);
}
// register the remote mbean server as a local view under the RemoteJMImplementation domain
ObjectName rmbs = JMXRemotingObjectName.create(con.serverId, con.ip, new ObjectName("RemoteJMImplementation:type=MBeanServerDelegate"), null, con.instanceId);
if (alreadyExists(rmbs) == false)
{
NotificationJMXInvocationHandler nh = new NotificationJMXInvocationHandler(con.server, JMXUtil.getMBeanServerObjectName());
Object obj = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{ProxyContext.class, DynamicMBean.class}, nh);
getServer().registerMBean(obj, rmbs);
}
// add a remote notification listener for the remote mbean server
con.server.addNotificationListener(JMXUtil.getMBeanServerObjectName(), this, new LocalMBeanEventFilter(), k);
// add a remote notification listener for the remote connector mbean
CompositeEventFilter filter = new CompositeEventFilter(new NotificationFilter[]{new ClassEventFilter(AttributeChangeNotification.class), new LocalMBeanEventFilter()});
con.server.addNotificationListener(con.localName, this, filter, null);
// query all beans on the remote MBeanServer, excluding any mbeans under the JMImplementation domain (the MBeanServerDelegate)
// and that aren't private
Set beans = JMXUtil.queryLocalMBeans(con.server, new ObjectName("*:*"), query);
Iterator iter = beans.iterator();
while (iter.hasNext())
{
ObjectInstance instance = (ObjectInstance) iter.next();
ObjectName ro = instance.getObjectName();
if (query.apply(ro))
{
registerMBean(con, ro);
}
}
}
}
/**
* registered a remote mbean
*
* @param con
* @param ro
* @throws Exception
*/
protected void registerMBean(Connector con, ObjectName ro)
throws Exception
{
synchronized (LOCK)
{
ObjectName on = JMXRemotingObjectName.create(con.serverId, con.ip, ro, null, con.instanceId);
if (alreadyExists(on))
{
return;
}
/*
//FIXME - this should be moved into the QueryExp above so we don't pull across MBeans
//and then ignore them ... (JGH)
String partition = ro.getKeyProperty("partition");
if (partition != null)
{
if (partitions.containsKey(partition) == false)
{
if (log.isDebugEnabled())
{
log.debug(" ... skipping since partition (" + partitions + ") isn't part of (" + partitionslist + ")");
}
return;
}
}
try
{
String p = (String) con.server.getAttribute(ro, "Partitions");
if (p != null)
{
// check and see if our partition is contained
StringTokenizer tok = new StringTokenizer(p, ",");
while (tok.hasMoreTokens())
{
String token = (String) tok.nextToken();
if (partitions.containsKey(token) == false)
{
if (log.isDebugEnabled())
{
log.debug(" ... ignoring mbean because it isn't part of our partition set [" + token + "], ours [" + partitionslist + "]");
}
return;
}
}
}
}
catch (Exception ex)
{
// maybe we don't have that attribute, that's OK since it's
// not mandatory
} */
MBean mbean = new MBean();
mbean.localName = on;
mbean.remoteName = ro;
mbean.connector = con;
if (log.isDebugEnabled())
{
log.debug("adding remote MBean... server: " + con.server + ", LocalObjectName: " + on + ", RemoteObjectName: " + ro);
}
// create a proxy to the remote mbean
mbean.mbean = (DynamicMBean) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{ProxyContext.class, DynamicMBean.class}, new NotificationJMXInvocationHandler(con.server, ro));
// put the mbean in our local store so we can map info back to it (use the local name)
objectmap.put(on, mbean);
// register the mbean
getServer().registerMBean(mbean.mbean, on);
String t = mbean.localName.getKeyProperty("type");
if (t != null && t.equals("Detector"))
{
// add a special filter/listener for receiving attribute changes on detectors
CompositeEventFilter filter = new CompositeEventFilter(new NotificationFilter[]{new ClassEventFilter(AttributeChangeNotification.class), new LocalMBeanEventFilter()});
con.server.addNotificationListener(ro, this, filter, null);
}
}
}
/**
* unregister any associated MBeans for the connector
*
* @param con
* @throws Exception
*/
protected void unregisterRemoteServer(String key, Connector con)
throws Exception
{
synchronized (LOCK)
{
if (log.isDebugEnabled())
{
log.debug("unregisterRemoteServer -> " + con);
}
ObjectName rmbs = JMXRemotingObjectName.create(con.serverId, con.ip, new ObjectName("RemoteJMImplementation:type=MBeanServerDelegate"), null, con.instanceId);
if (alreadyExists(rmbs))
{
try
{
getServer().unregisterMBean(rmbs);
}
catch (Throwable ex)
{
// ignore exception
if (log.isDebugEnabled())
{
log.debug("Exception unregistering MBean: " + rmbs, ex);
}
}
}
try
{
con.server.removeNotificationListener(JMXUtil.getMBeanServerObjectName(), this);
}
catch (Throwable ex)
{
// ignore,since it will almost always fail (connector is down...)
// but just in case we are stopping
}
try
{
con.server.removeNotificationListener(con.localName, this);
}
catch (Throwable ex)
{
// ignore, since it almost always fail
}
// destroy connector
ConnectorFactory.destroyConnector(con.serverId);
connectors.remove(con.key);
String serverId = con.serverId;
Iterator iter = objectmap.values().iterator();
// remove all remote MBeans from the local MBeanServer
// that match our serverid
while (iter.hasNext())
{
MBean mbean = (MBean) iter.next();
if (mbean.connector.serverId.equals(serverId))
{
try
{
unregisterMBean(con, mbean);
}
catch (Throwable ex)
{
// ignore
if (log.isDebugEnabled())
{
log.debug("Error unregistering MBean: " + mbean, ex);
}
}
iter.remove();
}
}
}
}
/**
* search from a specific MBean using the remote ObjectName
* and return the internal MBean holder class or null if not found
*
* @param remoteObjectName
* @return mbean or null
*/
protected MBean findMBean(ObjectName remoteObjectName)
{
Iterator i = objectmap.values().iterator();
while (i.hasNext())
{
MBean mbean = (MBean) i.next();
if (mbean.remoteName.equals(remoteObjectName))
{
return mbean;
}
}
return null;
}
protected MBean findMBean(Connector con, ObjectName remoteObjectName)
{
if (log.isDebugEnabled())
{
log.debug("searching for: " + remoteObjectName + ", in: " + con.serverId);
}
Iterator i = objectmap.values().iterator();
while (i.hasNext())
{
MBean mbean = (MBean) i.next();
if (con.serverId.equals(mbean.connector.serverId) &&
mbean.localName.equals(remoteObjectName))
{
return mbean;
}
}
return null;
}
/**
* unregister an MBean
*
* @param con
* @param mbean
* @throws Exception
*/
protected void unregisterMBean(Connector con, MBean mbean)
throws Exception
{
synchronized (LOCK)
{
if (mbean.connector.serverId.equals(con.serverId))
{
if (log.isDebugEnabled())
{
log.debug(" ... Removing ... " + mbean.localName);
}
try
{
// unregister from local mbeanserver
getServer().unregisterMBean(mbean.localName);
}
catch (Exception ex)
{
if (ex instanceof InstanceNotFoundException)
{
// NO OP
}
else
{
if (log.isDebugEnabled())
{
log.debug("Error removing mbean: " + mbean.localName, ex);
}
}
}
String t = mbean.localName.getKeyProperty("type");
if (t != null && t.equals("Detector"))
{
try
{
con.server.removeNotificationListener(mbean.remoteName, this);
}
catch (Exception ex)
{
boolean display=true;
if (ex instanceof IllegalStateException)
{
if (ex.getMessage().equals("socket has been closed"))
{
display=false;
}
}
if (display && log.isDebugEnabled())
{
log.debug("Error removing notification listener: " + mbean.remoteName, ex);
}
}
}
}
}
}
/**
* called when detection activity is made on a remote connector
*
* @param detectionMsg
*/
public void detectionPerformed(DetectionNotification detectionMsg)
{
if (log.isDebugEnabled())
{
log.debug("detection: " + detectionMsg);
}
if (detectionMsg.getType() == DetectionNotification.STARTUP &&
isInterestedTransport(detectionMsg.getTransport()) == false)
{
if (log.isDebugEnabled())
{
log.debug("ignoring detection: " + detectionMsg + " ... not interested in this transport");
}
// filter out transports we aren't interested in
return;
}
try
{
if (JMXUtil.getServerId(getServer()).equals(detectionMsg.getServerId()))
{
// shouldn't normally get here unless some issue in our logic
if (log.isDebugEnabled())
{
log.debug("ignoring detection from ourself: " + detectionMsg);
}
return;
}
}
catch (Exception ex)
{
log.error("Error getting our own JMImplementation MBeanServerId attribute", ex);
}
if (detectionMsg.getType().equals(DetectionNotification.STARTUP))
{
try
{
MBeanServerConnection conn = new MBeanServerConnection (detectionMsg);
MBeanServer server = ConnectorFactory.createConnector(conn);
// unique key is the server id + connector type
String key = detectionMsg.getServerId() + ":" + detectionMsg.getTransport();
Connector con = new Connector();
con.remoteName = new ObjectName(detectionMsg.getLocalName());
con.localName = new ObjectName(detectionMsg.getRemoteName());
con.server = server;
con.serverId = detectionMsg.getServerId();
con.instanceId = detectionMsg.getInstanceId();
con.transport = detectionMsg.getTransport();
con.ip = detectionMsg.getIPAddress();
con.key = key;
//log.debug("create new Connector with key ... " + key + " to ip: " + con.ip);
// add to connectors
connectors.put(key, con);
registerRemoteServer(key, con);
}
catch (Exception ex)
{
log.warn("Error creating Connector from detection message: " + detectionMsg, ex);
}
}
else if (detectionMsg.getType().equals(DetectionNotification.FAILURE))
{
String key = detectionMsg.getServerId() + ":" + detectionMsg.getTransport();
//log.debug("removing key ... "+key);
Connector con = (Connector) connectors.get(key);
if (con != null)
{
try
{
unregisterRemoteServer(key, con);
}
catch (Exception ex)
{
log.warn("Error removing Connector from detection message: " + detectionMsg, ex);
}
}
else
{
if (log.isDebugEnabled())
{
log.debug("Couldn't find ... " + key);
}
}
}
}
/**
* JMX Notification handler
*
* @param notification
* @param o
*/
public void handleNotification(Notification notification, Object o)
{
if (log.isDebugEnabled())
{
log.debug("Notification: " + notification + ", handback: " + o+", type: "+notification.getType());
}
if (notification instanceof DetectionNotification)
{
detectionPerformed((DetectionNotification) notification);
return;
}
Object src = notification.getSource();
if (src instanceof ObjectName)
{
ObjectName source = (ObjectName) src;
try
{
ObjectName mbs = JMXUtil.getMBeanServerObjectName();
// log.debug("mbs="+mbs+", src="+src);
if (mbs.equals((ObjectName) src))
{
MBeanServerNotification mn = (MBeanServerNotification) notification;
String type = notification.getType();
// MBeanServer notification
if (o != null)
{
// remove MBeanServer notification
String key = (String) o;
Connector con = (Connector) connectors.get(key);
if (con != null)
{
if (type.equals("JMX.mbean.registered"))
{
// remote mbean is registered
ObjectName on = mn.getMBeanName();
if (on.getKeyProperty("MBeanServerId") == null)
{
//FIXME: double check this...
registerMBean(con, (ObjectName) mn.getMBeanName());
return;
}
}
else if (type.equals("JMX.mbean.unregistered"))
{
// remove mbean is unregistered
MBean mbean = findMBean(con, (ObjectName) mn.getMBeanName());
if (mbean != null)
{
log.debug("found mbean: " + mbean + ", unregistering...");
unregisterMBean(con, mbean);
return;
}
}
}
}
else
{
// a connector is added, removed
if (type.equals("JMX.mbean.registered"))
{
// new connector -- add notification listener
getServer().addNotificationListener(source, this, new LocalMBeanEventFilter(), null);
return;
}
else if (type.equals("JMX.mbean.unregistered"))
{
// removed connector -- remove listener
getServer().removeNotificationListener(source, this);
return;
}
}
}
else
{
if (notification instanceof AttributeChangeNotification)
{
AttributeChangeNotification ac = (AttributeChangeNotification) notification;
String msg = notification.getMessage();
if (msg != null && msg.equals("detector.partition.changed"))
{
// detector update
Object nv = ac.getNewValue();
if (partitions.containsKey(nv.toString()) == false)
{
if (log.isDebugEnabled())
{
log.debug("Detector changed partition to: " + nv + " from: " + nv);
}
// new partition doesn't equal our partition, we need
// to unexpose these
MBean mbean = findMBean(source);
if (mbean != null)
{
// unregister this connector and associated mbeans
unregisterRemoteServer(mbean.connector.key, mbean.connector);
return;
}
}
}
}
}
}
catch (Exception ex)
{
log.warn("Error on handleNotification for: " + notification, ex);
}
}
}
}