/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Collections;
import java.util.Collection;
import java.lang.reflect.UndeclaredThrowableException;

import javax.management.*;

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.CompositeQueryExp;
import org.jboss.mx.remoting.event.CompositeEventFilter;
import org.jboss.mx.remoting.JMXUtil;

import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.ident.Identity;
import org.jboss.remoting.network.NetworkRegistryFinder;
import org.jboss.remoting.network.NetworkNotification;
import org.jboss.remoting.network.NetworkInstance;
import org.jboss.logging.Logger;

import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;
import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean;
import EDU.oswego.cs.dl.util.concurrent.LinkedQueue;

/**
 * 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: 1.1.2.8 $
 */
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 static final SynchronizedInt counter=new SynchronizedInt(0);
    private final QueryExp query;
    private final boolean localOnly;
    private final boolean wantNotifications;
    private boolean asyncEvents;
    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;
    private final SynchronizedBoolean running=new SynchronizedBoolean(true);
    private final int myid = counter.increment();
    private final LinkedQueue actionQueue = new LinkedQueue();
    private NotificationDispatcher dispatcher=new NotificationDispatcher();

    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, MBeanTrackerAction action, boolean async)
        throws Exception
    {
        this(myserver,cl,query,localOnly,filter,wantNotifications,new MBeanTrackerAction[]{action},async);
    }
    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(myserver,cl,query,localOnly,filter,wantNotifications,actions,false);
    }
    /**
     * 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
     * @param asyncEvents true if internal events should be sent on its own dispatch thread
     * @throws Exception raised on exception
     */
    public MBeanTracker (MBeanServer myserver,
                         Class cl[],
                         QueryExp query,
                         boolean localOnly,
                         NotificationFilter filter,
                         boolean wantNotifications,
                         MBeanTrackerAction actions[],
                         boolean asyncEvents)
        throws Exception
    {
        this.localOnly = localOnly;
        this.wantNotifications = wantNotifications;
        this.filter    = filter;
        this.myserver = myserver;
        this.asyncEvents = asyncEvents;

        if (log.isDebugEnabled())
        {
            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");
                }
            }
            buf.append("QueryExp:       "+query+"\n");
            buf.append("localOnly:      "+localOnly+"\n");
            buf.append("filter:         "+filter+"\n");
            buf.append("notifications:  "+wantNotifications+"\n");
            buf.append("asyncEvents:    "+asyncEvents+"\n");

            if (actions==null)
            {
                buf.append("actions: none\n");
            }
            else
            {
                for (int c=0;c<actions.length;c++)
                {
                    buf.append("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");
        }

        if (asyncEvents)
        {
            // start the dispatcher
            dispatcher.start();
        }

        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()));
              }
            }
        }
    }

    /**
     * sets async eventing
     *
     * @param async
     */
    public synchronized void setAsyncEvents (boolean async)
    {
        this.asyncEvents = async;
        if (asyncEvents && dispatcher.isAlive()==false)
        {
            dispatcher.start();
        }
    }

    public synchronized boolean isAsyncEvents ()
    {
        return this.asyncEvents;
    }

    public String toString ()
    {
        return "MBeanTracker["+myid+"] - query:"+query+",localOnly:"+localOnly+",filter:"+filter+",wantsNotif:"+wantNotifications+"]";
    }

    /**
     * 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.isDebugEnabled())
        {
            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.isDebugEnabled())
        {
            log.debug("removing action: "+action);
        }

        synchronized (actions)
        {
            Iterator iter=actions.iterator();
            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
    {
        if (!isDestroyed())
        {
            destroy();
        }
        super.finalize();
    }
    /**
     * return true if this tracker has been destroyed and should not longer be used
     *
     * @return
     */
    public boolean isDestroyed ()
    {
        return running.get()==false;
    }
    /**
     * called to stop tracking and clean up internally held resources
     *
     */
    public void destroy ()
    {
        if (running.get())
        {
            if (log.isDebugEnabled())
            {
                log.debug("destroy",new Exception("called from...."));
            }
            running.set(false);
            // stop the dispatcher
            dispatcher.interrupt();
            try
            {
                myserver.removeNotificationListener(networkRegistry,this);
            }
            catch (Throwable ex) {}
            Collection list = getMBeans();
            if (list.isEmpty()==false)
            {
                Iterator iter=list.iterator();
                while(iter.hasNext())
                {
                    MBeanLocator locator=(MBeanLocator)iter.next();
                    try
                    {
                        removeMBean(locator.getServerLocator(),locator.getObjectName());
                    }
                    catch (Throwable ex)
                    {

                    }
                }
            }
            mbeans.clear();
            actions.clear();
            while(actionQueue.isEmpty()==false)
            {
                try
                {
                    // drain the queue of objects so they can be GC
                    actionQueue.poll(10);
                }
                catch (InterruptedException ex)
                {
                    break;
                }
            }
            try
            {
                // wait for it to finish
                dispatcher.join(100);
            }
            catch (Exception ig) { }
            dispatcher=null;
        }
    }

    /**
     * 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().addNotificationListener(mbean,this,createFilterForServer(server.getServerId()),server);
                if (log.isDebugEnabled())
                {
                    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().removeNotificationListener(mbean,this);
                if (log.isDebugEnabled())
                {
                    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;
                    }
                    else if (mbe.getTargetException() instanceof InstanceNotFoundException)
                    {
                        // this is OK
                        return;
                    }
                }
            }
            if (e instanceof MBeanException)
            {
                MBeanException mbe=(MBeanException)e;
                if (mbe.getTargetException() instanceof ConnectionFailedException)
                {
                    // this is OK
                    return;
                }
                else if (mbe.getTargetException() instanceof InstanceNotFoundException)
                {
                    // 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 (final Notification notification, final Object o)
    {
        if (log.isDebugEnabled())
        {
            log.debug("tracker received notification="+notification+" with handback="+o);
        }
        Runnable action = new Runnable ()
        {
            public void run ()
            {
                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);
                        }
                        else if (type.equals(NetworkNotification.DOMAIN_CHANGED))
                        {
                            Identity ident = nn.getIdentity();
                            MBeanServerLocator l=new MBeanServerLocator(ident);
                            updateMBeanServerDomain(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);
                            }
                        }
                        else
                        {
                            if (log.isDebugEnabled()) log.debug("received notification: "+notification+" and wantNotifications="+wantNotifications+", hasActions()="+hasActions()+" -- not forwarding");
                        }
                    }
                }
                catch (Exception e)
                {
                    log.warn("Error encountered receiving notification: "+notification,e);
                    return;
                }
            }
        };
        dispatchAction(action);
    }
    /**
     * 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 (final MBeanLocator locator, final Notification n, final Object o)
    {
        Runnable action=new Runnable ()
        {
          public void run ()
          {
              Iterator iter=actions();
              while(iter.hasNext())
              {
                  MBeanTrackerAction action=(MBeanTrackerAction)iter.next();
                  if (wantNotifications && log.isDebugEnabled()) log.debug("forwarding tracker notification: "+n+" to action: "+action+" for tracker: "+this);
                  action.mbeanNotification(locator,n,o);
              }
          }
        };
        dispatchAction(action);
    }
    /**
     * fire a state changed event to actions
     *
     * @param locator
     * @param ov
     * @param nv
     */
    protected void fireStateChange (final MBeanLocator locator, final int ov, final int nv)
    {
        Runnable action = new Runnable ()
        {
            public void run ()
            {
                Iterator iter=actions();
                while(iter.hasNext())
                {
                    MBeanTrackerAction action=(MBeanTrackerAction)iter.next();
                    if (wantNotifications && log.isDebugEnabled()) log.debug("forwarding tracker state change: "+nv+" ["+ov+"] to action: "+action+" for tracker: "+this);
                    action.mbeanStateChanged(locator,ov,nv);
                }
            }
        };
        dispatchAction(action);
    }
    /**
     * 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 (final MBeanLocator locator)
    {
        Runnable action=new Runnable ()
        {
            public void run ()
            {
                int c=0;
                Iterator iter=actions();
                while(iter.hasNext())
                {
                    MBeanTrackerAction action=(MBeanTrackerAction)iter.next();
                    if (logEvents && log.isDebugEnabled())
                    {
                        log.debug("firing unregister to action ["+(++c)+"] => "+action+" for locator => "+locator);
                    }
                    action.mbeanUnregistered(locator);
                }
            }
        };
        dispatchAction(action);
    }
    /**
     * fire register event to listeners
     *
     * @param locator
     */
    protected void fireRegister (final MBeanLocator locator)
    {
        Runnable action=new Runnable ()
        {
            public void run ()
            {
                int c = 0;
                Iterator iter=actions();
                while(iter.hasNext())
                {
                    MBeanTrackerAction action=(MBeanTrackerAction)iter.next();
                    if (logEvents && log.isDebugEnabled())
                    {
                        log.debug("firing register to action ["+(++c)+"] => "+action+" for locator => "+locator);
                    }
                    action.mbeanRegistered(locator);
                }
            }
        };
        dispatchAction(action);
    }
    /**
     * fired when an MBeanServer is found
     *
     * @param theserver
     */
    private void foundMBeanServer (final MBeanServerLocator theserver)
    {
        Runnable action = new Runnable ()
        {
          public void run ()
          {
              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(),MBeanTracker.this,createFilterForServer(theserver.getServerId()),theserver);
                      Set beans=theserver.getMBeanServer().queryMBeans(new ObjectName("*:*"),query);
                      if (beans.isEmpty()==false)
                      {
                          if (log.isDebugEnabled())
                          {
                              log.debug("Queried server: "+theserver+", and found "+beans.size()+" mbeans matching query: "+query);
                          }
                          Iterator iter=beans.iterator();
                          while(iter.hasNext())
                          {
                              addMBean(theserver,((ObjectInstance)iter.next()).getObjectName());
                          }
                      }
                      else
                      {
                          if (log.isDebugEnabled())
                          {
                              log.debug("Queried server: "+theserver+", but found 0 mbeans matching query: "+query);
                          }
                      }

                      break;
                  }
                  catch (ConnectionFailedException ce)
                  {
                      if (log.isDebugEnabled())
                      {
                          log.debug("while trying to add a listener and get info for: "+theserver+", i lost it to: "+ce.getMessage());
                      }
                      if (c>=3)
                      {
                          if (log.isDebugEnabled())
                          {
                              log.debug("giving up on connection failed after "+c+" attempts... "+theserver);
                          }
                          // lost mbean server
                          lostMBeanServer(theserver);
                      }
                  }
                  catch (Exception ex)
                  {
                      if (ex instanceof UndeclaredThrowableException)
                      {
                          UndeclaredThrowableException udt=(UndeclaredThrowableException)ex;
                          Throwable target = udt.getUndeclaredThrowable();
                          if (target instanceof org.jboss.remoting.SubsystemNotSupported)
                          {
                              log.warn("Target JBoss VM at: "+theserver.getIdentity()+" doesn't support JMX, removing him from the list of servers");
                              // lost mbean server
                              lostMBeanServer(theserver);
                              break;
                          }
                      }
                      log.warn("Exception adding mbeans from server: "+theserver,ex);
                  }
              }
          }
        };

        dispatchAction(action);
    }

    /**
     * add an mbean
     *
     * @param server
     * @param mbean
     */
    private void addMBean (final MBeanServerLocator server, final ObjectName mbean)
    {
        if (log.isDebugEnabled())
        {
            log.debug("addMBean called: "+server+", mbean: "+mbean);
        }

        Runnable action = new Runnable ()
        {
            public void run ()
            {
                final 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);
                }
            }
        };

        dispatchAction(action);
    }

    /**
     * called to remove an mbean
     *
     * @param server
     * @param mbean
     */
    private void removeMBean (final MBeanServerLocator server, final ObjectName mbean)
    {
        removeMBean(server,mbean,false);
    }
    /**
     * called to remove an mbean
     *
     * @param server
     * @param mbean
     * @param sync
     */
    private void removeMBean (final MBeanServerLocator server, final ObjectName mbean, boolean sync)
    {
        if (log.isDebugEnabled())
        {
            log.debug("removeMBean called: "+server+", mbean: "+mbean);
        }

        final MBeanLocator locator = new MBeanLocator(server,mbean);

        Runnable action = new Runnable()
        {
            public void run ()
            {
                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);
                }
            }
        };
        dispatchAction(action,sync);
    }

    /**
     * update for domain change
     * 
     * @param server
     */
    private void updateMBeanServerDomain (final MBeanServerLocator server)
    {
        if (log.isDebugEnabled()) log.debug("updateMBeanServerDomain: "+server+" for tracker: "+this);

        Runnable action = new Runnable ()
        {
            public void run ()
            {
                Collection list = null;
                synchronized(mbeans)
                {
                    Iterator iter = mbeans.keySet().iterator();
                    while(iter.hasNext())
                    {
                        MBeanServerLocator ml=(MBeanServerLocator)iter.next();
                        if (ml.getInstanceId().equals(server.getInstanceId()) &&
                            ml.getAddress().equals(server.getAddress()))
                        {
                            // found it, don't check the JMX identity because it might
                            // have changed
                            // remove it, then re-add with the updated locator
                            list = (Collection)mbeans.remove(ml);
                            if (log.isDebugEnabled()) log.debug("found mbeans ["+list+"] for old locator: "+ml+", re-adding with new locator: "+server+" because of domain change:"+server.getIdentity().getDomain());
                            mbeans.put(server,list);
                            break;
                        }
                    }
                }
            }
        };
        dispatchAction(action);
    }

    /**
     * fired when we lose an MBeanServer
     *
     * @param server
     */
    private void lostMBeanServer (final MBeanServerLocator server)
    {
        if (log.isDebugEnabled()) log.debug("lostMBeanServer: "+server+" for tracker: "+this);

        Runnable action = new Runnable ()
        {
            public void run ()
            {
                Collection list = null;
                synchronized(mbeans)
                {
                    list = (Set)mbeans.remove(server);
                }
                if (list!=null && isDestroyed()==false)
                {
                    final Collection fList = list;
                    if (log.isDebugEnabled())
                    {
                        log.debug("lost mbean server = "+server+", list = "+fList);
                    }
                    Iterator iter=fList.iterator();
                    while(iter.hasNext())
                    {
                        MBeanLocator locator=(MBeanLocator)iter.next();
                        removeMBean(server,locator.getObjectName());
                    }
                    fList.clear();
                }
            }
        };
        dispatchAction(action);
    }

    /**
     * main entry point for dispatching actions - IMPORTANT: all actions (whether async or sync) must
     * go through this method for the dynamic async eventing behavior / configuration to work
     *
     * @param action
     */
    private void dispatchAction (Runnable action)
    {
        dispatchAction(action,!isAsyncEvents());
    }
    /**
     * main entry point for dispatching actions - IMPORTANT: all actions (whether async or sync) must
     * go through this method for the dynamic async eventing behavior / configuration to work
     *
     * @param action
     * @param sync
     */
    private void dispatchAction (Runnable action, boolean sync)
    {
        if (isDestroyed()==false)
        {
            // if we're executing an action that dispatches another action, just
            // continue executing the action instead of putting at the end of the list
            // or if we're not setup to do async events, just execute on the same caller thread
            if (sync || isAsyncEvents()==false || Thread.currentThread().equals(dispatcher.thread))
            {
                try
                {
                    action.run();
                }
                catch (Throwable err)
                {
                    log.error("error caught dispatching action",err);
                }
            }
            else
            {
                try
                {
                    actionQueue.put(action);
                }
                catch (InterruptedException ignore)
                {
                    // ignore
                }
            }
        }
        else
        {
            if (log.isDebugEnabled()) log.debug("dispatch action called, but we're destroyed");
        }
    }
    /**
     * set the internal dispatch thread's priority - default is <tt>Thread.NORM_PRIORITY - 1</tt>
     *
     * @param pri
     */
    public void setDispatcherPriority (int pri)
    {
        if (dispatcher!=null)
        {
            dispatcher.setPriority(pri);
        }
    }
    /**
     * return the internal dispatch thread's priority or -1 if not running
     *
     * @return
     */
    public int getDispatcherPriority ()
    {
        return dispatcher!=null ? dispatcher.getPriority() : -1;
    }
    /**
     * background dispatcher thread will dispatch internal tracker actions
     * on a separate thread as they are received so that incoming notifications
     * do not block on dispatch
     */
    private final class NotificationDispatcher extends Thread
    {
        Thread thread;
        NotificationDispatcher ()
        {
             super("MBeanTracker["+myid+"]-NotificationDispatcher");
             setPriority(Thread.NORM_PRIORITY-1);
        }
        public void run ()
        {
            thread = Thread.currentThread();
            while (running.get() && isAsyncEvents())
            {
                try
                {
                    Runnable action = (Runnable)actionQueue.poll(250);
                    if (action!=null)
                    {
                        action.run();
                    }
                }
                catch (InterruptedException ex)
                {
                    break;
                }
                catch (Throwable err)
                {
                    log.error("MBeanTracker attempted to dispatch an action and it raised an unexpected error",err);
                }
            }
        }
    }

}