/*
* 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.mx.remoting.tracker;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.management.AttributeChangeNotification;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MBeanServerNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcaster;
import javax.management.NotificationFilter;
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import org.jboss.logging.Logger;
import org.jboss.mx.remoting.JMXUtil;
import org.jboss.mx.remoting.MBeanLocator;
import org.jboss.mx.remoting.MBeanServerLocator;
import org.jboss.mx.remoting.event.ClassQueryExp;
import org.jboss.mx.remoting.event.CompositeEventFilter;
import org.jboss.mx.remoting.event.CompositeQueryExp;
import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.ident.Identity;
import org.jboss.remoting.network.NetworkInstance;
import org.jboss.remoting.network.NetworkNotification;
import org.jboss.remoting.network.NetworkRegistryFinder;
import org.jboss.remoting.network.NetworkRegistryMBean;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;
/**
* MBeanTracker is a utility class that will track MBeans on behalf of a user object.
*
* @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
* @version $Revision: 81023 $
*/
public class MBeanTracker implements NotificationListener
{
private static final boolean logEvents = Boolean.getBoolean("jboss.mx.tracker.debug");
private static final transient Logger log = Logger.getLogger(MBeanTracker.class.getName());
private final QueryExp query;
private final boolean localOnly;
private final boolean wantNotifications;
private final NotificationFilter filter;
private final SynchronizedInt count = new SynchronizedInt(0);
private final Map mbeans = new HashMap();
private final String classes[];
private final List actions = new ArrayList(1);
private final ObjectName networkRegistry;
private final MBeanServer myserver;
public MBeanTracker(MBeanServer myserver, Class cl[], QueryExp query, boolean localOnly, MBeanTrackerAction action)
throws Exception
{
this(myserver, cl, query, localOnly, null, false, new MBeanTrackerAction[]{action});
}
public MBeanTracker(MBeanServer myserver, Class cl[], QueryExp query, boolean localOnly, MBeanTrackerAction actions[])
throws Exception
{
this(myserver, cl, query, localOnly, null, false, actions);
}
public MBeanTracker(MBeanServer myserver, Class cl[], boolean localOnly, MBeanTrackerAction action)
throws Exception
{
this(myserver, cl, null, localOnly, null, false, new MBeanTrackerAction[]{action});
}
public MBeanTracker(MBeanServer myserver, Class cl[], boolean localOnly, MBeanTrackerAction actions[])
throws Exception
{
this(myserver, cl, null, localOnly, null, false, actions);
}
public MBeanTracker(MBeanServer myserver, Class cl[], QueryExp query, boolean localOnly, NotificationFilter filter, boolean wantNotifications, MBeanTrackerAction action)
throws Exception
{
this(myserver, cl, query, localOnly, filter, wantNotifications, new MBeanTrackerAction[]{action});
}
public MBeanTracker(MBeanServer myserver, Class cl[], QueryExp query, boolean localOnly, NotificationFilter filter, boolean wantNotifications)
throws Exception
{
this(myserver, cl, query, localOnly, filter, wantNotifications, (MBeanTrackerAction[]) null);
}
/**
* create a tracker
*
* @param myserver local mbean server
* @param cl array of classes that mbeans implement that you want to track, or null to not look at class interfaces
* @param query query expression to apply when selecting mbeans or null to not use a query expression
* @param localOnly true to only search the local mbeanserver, false to search the entire network of mbeans servers
* @param filter filter to apply for receiving notifications or null to apply no filter
* @param wantNotifications if true, will also track notifications by the mbeans being tracked
* @param actions array of actions to automatically register as listeners, or null if none
* @throws Exception raised on exception
*/
public MBeanTracker(MBeanServer myserver, Class cl[], QueryExp query, boolean localOnly, NotificationFilter filter, boolean wantNotifications, MBeanTrackerAction actions[])
throws Exception
{
this.localOnly = localOnly;
this.wantNotifications = wantNotifications;
this.filter = filter;
this.myserver = myserver;
if(log.isTraceEnabled())
{
StringBuffer buf = new StringBuffer("creating an MBeanTracker with the following parameters:\n");
buf.append("==========================================\n");
buf.append("MBeanServer: " + myserver + "\n");
if(cl == null)
{
buf.append("classes: none\n");
}
else
{
for(int c = 0; c < cl.length; c++)
{
buf.append("classes[" + c + "] " + cl[c].getName() + "\n");
}
}
log.debug("QueryExp: " + query + "\n");
log.debug("localOnly: " + localOnly + "\n");
log.debug("filter: " + filter + "\n");
log.debug("notifications: " + wantNotifications + "\n");
if(actions == null)
{
log.debug("actions: none\n");
}
else
{
for(int c = 0; c < actions.length; c++)
{
log.debug("actions[" + c + "]: " + actions[c] + "\n");
}
}
buf.append("==========================================\n");
log.debug(buf.toString());
}
// add actions
if(actions != null)
{
for(int c = 0; c < actions.length; c++)
{
if(actions[c] != null)
{
addActionListener(actions[c]);
}
}
}
if(cl != null)
{
this.classes = new String[cl.length];
for(int c = 0; c < cl.length; c++)
{
classes[c] = cl[c].getName();
}
}
else
{
this.classes = null;
}
if(query == null && cl != null)
{
this.query = new ClassQueryExp(cl);
}
else
{
if(cl != null)
{
this.query = new CompositeQueryExp(new QueryExp[]{new ClassQueryExp(cl, ClassQueryExp.OR), query});
}
else
{
this.query = query;
}
}
// add ourself as a listener to the NetworkRegistry
networkRegistry = NetworkRegistryFinder.find(myserver);
if(networkRegistry == null)
{
throw new Exception("NetworkRegistryMBean not found - MBeanTracker has a dependency on this MBean");
}
foundMBeanServer(new MBeanServerLocator(Identity.get(myserver)));
if(this.localOnly == false)
{
// add ourself as a listener for network changes
myserver.addNotificationListener(networkRegistry, this, null, null);
// find any instances we already have registered
NetworkInstance instances[] = (NetworkInstance[]) myserver.getAttribute(networkRegistry, "Servers");
if(instances != null)
{
for(int c = 0; c < instances.length; c++)
{
foundMBeanServer(new MBeanServerLocator(instances[c].getIdentity()));
}
}
}
}
/**
* add a action listener. this method will automatically call register to your action on
* all the mbeans that are contained within it before this method returns.
*
* @param action
*/
public void addActionListener(MBeanTrackerAction action)
{
addActionListener(action, true);
}
/**
* add a action listener. this method will automatically call register to your action on
* all the mbeans that are contained within it before this method returns.
*
* @param action
*/
public void addActionListener(MBeanTrackerAction action, boolean autoinitialregister)
{
if(log.isTraceEnabled())
{
log.debug("adding action: " + action + ", autoinitialregister:" + autoinitialregister);
}
synchronized(actions)
{
actions.add(action);
}
if(autoinitialregister)
{
Set set = getMBeans();
Iterator iter = set.iterator();
while(iter.hasNext())
{
MBeanLocator locator = (MBeanLocator) iter.next();
fireRegister(locator);
}
}
}
/**
* remove a action listener
*
* @param action
*/
public void removeActionListener(MBeanTrackerAction action)
{
if(log.isTraceEnabled())
{
log.debug("removing action: " + action);
}
Iterator iter = actions();
while(iter.hasNext())
{
MBeanTrackerAction _action = (MBeanTrackerAction) iter.next();
if(_action.equals(action))
{
iter.remove();
}
}
}
private NotificationFilter createFilterForServer(String id)
{
NotificationFilter serverfilter = null;
NotificationFilter nfilter = new MBeanTrackerFilter(id, classes, wantNotifications);
if(filter == null)
{
serverfilter = nfilter;
}
else
{
serverfilter = new CompositeEventFilter(new NotificationFilter[]{nfilter, filter});
}
return serverfilter;
}
protected void finalize() throws Throwable
{
destroy();
super.finalize();
}
/**
* called to stop tracking and clean up internally held resources
*/
public void destroy()
{
if(log.isTraceEnabled())
{
log.debug("destroy");
}
try
{
myserver.removeNotificationListener(networkRegistry, this);
}
catch(Throwable ex)
{
}
}
/**
* returns true if no mbeans are found that are being tracked
*
* @return
*/
public final boolean isEmpty()
{
return count() <= 0;
}
/**
* return the number of mbeans being tracked
*
* @return
*/
public final int count()
{
return count.get();
}
/**
* return a copy of the internal mbeans being tracked
*
* @return
*/
public final Set getMBeans()
{
Set set = new HashSet();
synchronized(mbeans)
{
Iterator iter = mbeans.values().iterator();
while(iter.hasNext())
{
Set beans = (Set) iter.next();
set.addAll(beans);
}
}
return set;
}
/**
* return an iterator to a copy of the internal mbeans being tracked
*
* @return
*/
public final Iterator iterator()
{
return getMBeans().iterator();
}
private void tryAddListener(MBeanServerLocator server, ObjectName mbean)
{
try
{
if(server.getMBeanServer().isInstanceOf(mbean, NotificationBroadcaster.class.getName()) &&
server.getMBeanServer().isInstanceOf(mbean, NetworkRegistryMBean.class.getName()) == false)
{
server.getMBeanServer().addNotificationListener(mbean, this, createFilterForServer(server.getServerId()), server);
if(log.isTraceEnabled())
{
log.debug("added notification listener to: " + mbean + " on server: " + server);
}
}
}
catch(Throwable e)
{
log.error("Error registering listener for server:" + server + " and mbean:" + mbean, e);
}
}
/**
* try and remove a listener
*
* @param server
* @param mbean
*/
private void tryRemoveListener(MBeanServerLocator server, ObjectName mbean)
{
try
{
if(server.getMBeanServer() == null)
{
return;
}
if(server.getMBeanServer().isInstanceOf(mbean, NotificationBroadcaster.class.getName()) &&
server.getMBeanServer().isInstanceOf(mbean, NetworkRegistryMBean.class.getName()) == false)
{
server.getMBeanServer().removeNotificationListener(mbean, this);
if(log.isTraceEnabled())
{
log.debug("removed notification listener to: " + mbean + " on server: " + server);
}
}
}
catch(javax.management.InstanceNotFoundException nf)
{
//this is OK, since it means we're trying to remove a listener from an
// unregsitered mbean - which in most cases it is
}
catch(ConnectionFailedException cnf)
{
// this is OK
}
catch(Exception e)
{
if(e instanceof UndeclaredThrowableException)
{
UndeclaredThrowableException ut = (UndeclaredThrowableException) e;
if(ut.getUndeclaredThrowable() instanceof ReflectionException)
{
ReflectionException re = (ReflectionException) ut.getUndeclaredThrowable();
if(re.getTargetException() instanceof InstanceNotFoundException ||
re.getTargetException() instanceof ConnectionFailedException)
{
// these are OK
return;
}
}
else if(ut.getUndeclaredThrowable() instanceof MBeanException)
{
MBeanException mbe = (MBeanException) ut.getUndeclaredThrowable();
if(mbe.getTargetException() instanceof ConnectionFailedException)
{
// this is OK
return;
}
}
}
if(e instanceof MBeanException)
{
MBeanException mbe = (MBeanException) e;
if(mbe.getTargetException() instanceof ConnectionFailedException)
{
// this is OK
return;
}
}
log.warn("Error removing listener for server:" + server + " and mbean:" + mbean, e);
}
}
/**
* called for each notification
*
* @param notification
* @param o
*/
public void handleNotification(Notification notification, Object o)
{
if(log.isTraceEnabled())
{
log.debug("tracker received notification=" + notification + " with handback=" + o);
}
try
{
if(notification instanceof MBeanServerNotification && JMXUtil.getMBeanServerObjectName().equals(notification.getSource()))
{
MBeanServerNotification n = (MBeanServerNotification) notification;
String type = n.getType();
ObjectName mbean = n.getMBeanName();
if(type.equals(MBeanServerNotification.REGISTRATION_NOTIFICATION))
{
addMBean((MBeanServerLocator) o, mbean);
}
else
{
// unreg a specific MBean
removeMBean((MBeanServerLocator) o, mbean);
}
return;
}
else if(notification instanceof NetworkNotification)
{
NetworkNotification nn = (NetworkNotification) notification;
String type = nn.getType();
if(type.equals(NetworkNotification.SERVER_ADDED))
{
// found a server
Identity ident = nn.getIdentity();
MBeanServerLocator l = new MBeanServerLocator(ident);
foundMBeanServer(l);
}
else if(type.equals(NetworkNotification.SERVER_REMOVED))
{
// lost a server
Identity ident = nn.getIdentity();
MBeanServerLocator l = new MBeanServerLocator(ident);
lostMBeanServer(l);
}
return;
}
else if(notification instanceof AttributeChangeNotification)
{
AttributeChangeNotification ch = (AttributeChangeNotification) notification;
if(ch.getAttributeName().equals("State") && hasActions())
{
MBeanServerLocator server = (MBeanServerLocator) o;
Object src = ch.getSource();
if(src instanceof ObjectName)
{
ObjectName obj = (ObjectName) src;
// indicate the state changed
fireStateChange(new MBeanLocator(server, obj), ((Integer) ch.getOldValue()).intValue(), ((Integer) ch.getNewValue()).intValue());
return;
}
else if(src instanceof MBeanLocator)
{
fireNotification((MBeanLocator) src, notification, o);
return;
}
}
}
if(wantNotifications && hasActions())
{
// fire notification to listener
MBeanServerLocator server = (MBeanServerLocator) o;
if(server != null)
{
Object src = notification.getSource();
if(src instanceof ObjectName)
{
ObjectName obj = (ObjectName) src;
MBeanLocator locator = new MBeanLocator(server, obj);
fireNotification(locator, notification, o);
return;
}
else if(src instanceof MBeanLocator)
{
fireNotification((MBeanLocator) src, notification, o);
return;
}
else
{
log.debug("Unknown source type for notification: " + src);
}
}
}
}
catch(Exception e)
{
log.warn("Error encountered receiving notification: " + notification, e);
}
}
/**
* returns true if we have any actions
*
* @return
*/
private boolean hasActions()
{
synchronized(actions)
{
return actions.isEmpty() == false;
}
}
/**
* fire a notification to actions
*
* @param locator
* @param n
* @param o
*/
protected void fireNotification(MBeanLocator locator, Notification n, Object o)
{
Iterator iter = actions();
while(iter.hasNext())
{
MBeanTrackerAction action = (MBeanTrackerAction) iter.next();
if(wantNotifications && log.isTraceEnabled())
{
log.debug("forwarding tracker notification: " + n + " to action: " + action + " for tracker: " + this);
}
action.mbeanNotification(locator, n, o);
}
}
/**
* fire a state changed event to actions
*
* @param locator
* @param ov
* @param nv
*/
protected void fireStateChange(MBeanLocator locator, int ov, int nv)
{
Iterator iter = actions();
while(iter.hasNext())
{
MBeanTrackerAction action = (MBeanTrackerAction) iter.next();
if(wantNotifications && log.isTraceEnabled())
{
log.debug("forwarding tracker state change: " + nv + " [" + ov + "] to action: " + action + " for tracker: " + this);
}
action.mbeanStateChanged(locator, ov, nv);
}
}
/**
* return an Iterator to a unmodifiable Iterator so that you can avoid having to synchronize
* on traversing the action list
*
* @return
*/
private final Iterator actions()
{
synchronized(actions)
{
if(actions.isEmpty())
{
return Collections.EMPTY_LIST.iterator();
}
return new ArrayList(actions).iterator();
}
}
/**
* fire unregister event to listeners
*
* @param locator
*/
protected void fireUnregister(MBeanLocator locator)
{
int c = 0;
Iterator iter = actions();
while(iter.hasNext())
{
MBeanTrackerAction action = (MBeanTrackerAction) iter.next();
if(logEvents && log.isTraceEnabled())
{
log.debug("firing unregister to action [" + (++c) + "] => " + action + " for locator => " + locator);
}
action.mbeanUnregistered(locator);
}
}
/**
* fire register event to listeners
*
* @param locator
*/
protected void fireRegister(MBeanLocator locator)
{
int c = 0;
Iterator iter = actions();
while(iter.hasNext())
{
MBeanTrackerAction action = (MBeanTrackerAction) iter.next();
if(logEvents && log.isTraceEnabled())
{
log.debug("firing register to action [" + (++c) + "] => " + action + " for locator => " + locator);
}
action.mbeanRegistered(locator);
}
}
/**
* fired when an MBeanServer is found
*
* @param theserver
*/
public void foundMBeanServer(MBeanServerLocator theserver)
{
synchronized(mbeans)
{
// already found him
if(mbeans.containsKey(theserver))
{
return;
}
mbeans.put(theserver, new HashSet());
}
for(int c = 0; c < 3; c++)
{
try
{
theserver.getMBeanServer().addNotificationListener(JMXUtil.getMBeanServerObjectName(), this, createFilterForServer(theserver.getServerId()), theserver);
Set beans = theserver.getMBeanServer().queryMBeans(new ObjectName("*:*"), query);
if(beans.isEmpty() == false)
{
Iterator iter = beans.iterator();
while(iter.hasNext())
{
addMBean(theserver, ((ObjectInstance) iter.next()).getObjectName());
}
}
else
{
if(log.isTraceEnabled())
{
log.debug("Queried server: " + theserver + ", but found 0 mbeans matching query");
}
}
break;
}
catch(ConnectionFailedException ce)
{
if(log.isTraceEnabled())
{
log.debug("while trying to add a listener and get info for: " + theserver + ", i lost it", ce);
}
if(c >= 3)
{
if(log.isTraceEnabled())
{
log.debug("giving up on connection failed after " + c + " attempts... " + theserver);
}
// lost mbean server
lostMBeanServer(theserver);
}
}
catch(Exception ex)
{
log.warn("Exception adding mbeans from server: " + theserver, ex);
}
}
}
/**
* add an mbean
*
* @param server
* @param mbean
*/
private void addMBean(MBeanServerLocator server, ObjectName mbean)
{
if(log.isTraceEnabled())
{
log.debug("addMBean called: " + server + ", mbean: " + mbean);
}
MBeanLocator locator = new MBeanLocator(server, mbean);
boolean found = false;
synchronized(mbeans)
{
Set set = (Set) mbeans.get(server);
if(set != null)
{
if(set.add(locator))
{
count.increment();
found = true;
}
}
}
if(!found)
{
return;
}
tryAddListener(server, mbean);
if(hasActions())
{
fireRegister(locator);
}
}
/**
* called to remove an mbean
*
* @param server
* @param mbean
*/
private void removeMBean(MBeanServerLocator server, ObjectName mbean)
{
if(log.isTraceEnabled())
{
log.debug("removeMBean called: " + server + ", mbean: " + mbean);
}
MBeanLocator locator = new MBeanLocator(server, mbean);
synchronized(mbeans)
{
Set set = (Set) mbeans.get(server);
if(set != null)
{
if(set.remove(locator))
{
// only if we found the dude
count.decrement();
}
else
{
// we didn't find him, just return
return;
}
}
}
tryRemoveListener(server, mbean);
if(hasActions())
{
fireUnregister(locator);
}
}
/**
* fired when we lose an MBeanServer
*
* @param server
*/
public void lostMBeanServer(MBeanServerLocator server)
{
if(wantNotifications && log.isTraceEnabled())
{
log.debug("lostMBeanServer: " + server + " for tracker: " + this);
}
Collection list = null;
synchronized(mbeans)
{
list = (Set) mbeans.remove(server);
}
if(list != null)
{
if(log.isTraceEnabled())
{
log.debug("lost mbean server = " + server + ", list = " + list);
}
Iterator iter = list.iterator();
while(iter.hasNext())
{
MBeanLocator locator = (MBeanLocator) iter.next();
removeMBean(server, locator.getObjectName());
}
list.clear();
list = null;
}
}
}