/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2006, 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.web.tomcat.tc5.session;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;

import org.apache.catalina.Container;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Manager;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.BatchModeTransactionManagerLookup;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.aop.PojoCache;
import org.jboss.cache.aop.PojoCacheMBean;
import org.jboss.mx.util.MBeanProxyExt;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.web.tomcat.tc5.Tomcat5;

/**
 * A Tomcat <code>Cluster</code> implementation that uses a JBoss
 * <code>TreeCache</code> to support intra-cluster session replication.
 * <p>
 * This class registers a <code>TreeCache</code> in JMX, making it 
 * available to other users who wish to replicate data within the cluster.
 * </p>
 *  
 * @author Brian Stansberry
 * @version $Revision: 57206 $
 */
public class JBossCacheCluster
   implements JBossCacheClusterMBean, Lifecycle 
{
   //  -------------------------------------------------------  Static Fields
   
   protected static final String info = "JBossCacheCluster/2.1";

   public static Log log = LogFactory.getLog(JBossCacheCluster.class);

   public static final String DEFAULT_CLUSTER_NAME = "Tomcat-Cluster";

   /** TreeCache's isolation level */
   public static final String DEFAULT_ISOLATION_LEVEL = "REPEATABLE_READ";
   
   /** TreeCache's cache mode */
   public static final String DEFAULT_CACHE_MODE = "REPL_ASYNC";
   
   /** TreeCache's lock aquisition timeout */
   public static final long DEFAULT_LOCK_TIMEOUT = 15000;
   
   /** TransactionManagerLookup implementation that the TreeCache should use. */
   public static final String DEFAULT_TM_LOOKUP = 
      BatchModeTransactionManagerLookup.class.getName();

   public static final String DEFAULT_CACHE_CONFIG_PATH = "conf/cluster-cache.xml";
   
   //  -------------------------------------------------------  Instance Fields

   /** Parent container of this cluster. */
   private Container container = null;
   
   /** Our JMX Server. */
   private MBeanServer mserver = null;
   
   /** Name under which we are registered in JMX */
   private ObjectName objectName = null;
   
   /** Are we started? */
   private boolean started = false;

   /** The lifecycle event support for this component. */
   private LifecycleSupport lifecycle = new LifecycleSupport(this);

   /** Our tree cache */
   private PojoCacheMBean treeCache = null;
   
   /** Name under which our TreeCache is registered in JMX */
   private String treeCacheObjectName = Tomcat5.DEFAULT_CACHE_NAME;
   
   /** Did we create the tree cache, or was it already registered in JMX? */
   private boolean treeCacheLocal = false;
   
   /** Name of the tree cache's JGroups channel */
   private String clusterName = null;
   
   /** File name, URL or String to use to configure JGroups. */
   private String cacheConfigPath = null;
   
   /**
    * Implementation of Manager to instantiate when 
    * createManager() is called.
    */
   private String managerClassName = JBossCacheManager.class.getName();

   /** Does the Engine in which we are running use mod_jk? */
   private boolean useJK = false;
   
   /** Whether our Managers should use a local cache. */
   private boolean useLocalCache = false;

   /**
    * Default replication trigger to assign to our 
    * Managers that haven't had this property set.
    */
   private String defaultReplicationTrigger = null;

   /**
    * Default replication granularity to assign to our Managers
    * that haven't had this property set.
    */
   private String defaultReplicationGranularity = null;
   
   /**
    * JBossCacheManager's snapshot mode.
    */
   private String snapshotMode = null;
   
   /**
    * JBossCacheManager's snapshot interval.
    */
   private int snapshotInterval = 0;
   
   /** Whether we use batch mode replication for field level granularity */
   private boolean replicationFieldBatchMode;
   
   //  ----------------------------------------------------------  Constructors
   
   /**
    * Default constructor.
    */
   public JBossCacheCluster()
   {
      super();
   }

   //  ------------------------------------------------------------  Properties 

   /**
    * Gets a String representation of the JMX <code>ObjectName</code> under
    * which our <code>TreeCache</code> is registered.
    * <p>
    * If this property is not explicitly set, the <code>TreeCache</code> will
    * be registered under 
    * @{@link Tomcat5.DEFAULT_CACHE_NAME the default name used in 
    * embedded JBoss/Tomcat}.
    * </p>
    * 
    * @jmx.managed-attribute
    */
   public String getCacheObjectName()
   {
      return treeCacheObjectName;
   }

   /**
    * Sets the JMX <code>ObjectName</code> under which our 
    * <code>TreeCache</code> is registered, if already created, or under
    * which it should be registered if this object creates it.
    * 
    * @jmx.managed-attribute
    */
   public void setCacheObjectName(String objectName)
   {
      this.treeCacheObjectName = objectName;
   }

   /**
    * Sets the name of the <code>TreeCache</code>'s JGroups channel.
    * <p>
    * This property is ignored if a <code>TreeCache</code> is already
    * registered under the provided 
    * {@link #setCacheObjectName cache object name}.
    * </p>
    * 
    * @jmx.managed-attribute
    */
   public void setClusterName(String clusterName)
   {
      this.clusterName = clusterName;
   }

   /**
    * Gets the filesystem path, which can either be absolute or a path 
    * relative to <code>$CATALINA_BASE</code>, where a
    * a JBossCache configuration file can be found.
    * 
    * @return  a path, either absolute or relative to 
    *          <code>$CATALINA_BASE</code>.  Will return 
    *          <code>null</code> if no such path was configured.
    * 
    * @jmx.managed-attribute
    */
   public String getCacheConfigPath()
   {
      return cacheConfigPath;
   }

   /** 
    * Sets the filesystem path, which can either be absolute or a path 
    * relative to <code>$CATALINA_BASE</code>, where a
    * a JBossCache configuration file can be found.
    * <p>
    * This property is ignored if a <code>TreeCache</code> is already
    * registered under the provided 
    * {@link #setCacheObjectName cache object name}.
    * </p>
    * 
    * @param cacheConfigPath   a path, absolute or relative to 
    *                          <code>$CATALINA_BASE</code>,
    *                          pointing to a JBossCache configuration file.
    *                            
    * @jmx.managed-attribute
    */
   public void setCacheConfigPath(String cacheConfigPath)
   {
      this.cacheConfigPath = cacheConfigPath;
   }

   /**
    * Gets the name of the implementation of Manager to instantiate when 
    * createManager() is called.
    * 
    * @jmx.managed-attribute
    */
   public String getManagerClassName()
   {
      return managerClassName;
   }

   /**
    * Sets the name of the implementation of Manager to instantiate when 
    * createManager() is called.
    * <p>
    * This should be {@link JBossCacheManager} (the default) or a subclass
    * of it.
    * </p>
    * 
    * @jmx.managed-attribute
    */
   public void setManagerClassName(String managerClassName)
   {
      this.managerClassName = managerClassName;
   }

   /**
    * Gets whether the <code>Engine</code> in which we are running
    * uses <code>mod_jk</code>.
    * 
    * @jmx.managed-attribute
    */
   public boolean isUseJK()
   {
      return useJK;
   }

   /**
    * Sets whether the <code>Engine</code> in which we are running
    * uses <code>mod_jk</code>.
    * 
    * @jmx.managed-attribute
    */
   public void setUseJK(boolean useJK)
   {
      this.useJK = useJK;
   }

   /**
    * Gets the <code>JBossCacheManager</code>'s <code>useLocalCache</code>
    * property.
    * 
    * @jmx.managed-attribute
    */
   public boolean isUseLocalCache()
   {
      return useLocalCache;
   }

   /**
    * Sets the <code>JBossCacheManager</code>'s <code>useLocalCache</code>
    * property.
    * 
    * @jmx.managed-attribute
    */
   public void setUseLocalCache(boolean useLocalCache)
   {
      this.useLocalCache = useLocalCache;
   }

   /**
    * Gets the default granularity of session data replicated across the 
    * cluster; i.e. whether the entire session should be replicated when 
    * replication is triggered, or only modified attributes.
    * <p>
    * The value of this property is applied to <code>Manager</code> instances
    * that did not have an equivalent property explicitly set in 
    * <code>context.xml</code> or <code>server.xml</code>.
    * </p>
    * 
    * @jmx.managed-attribute
    */
   public String getDefaultReplicationGranularity()
   {
      return defaultReplicationGranularity;
   }

   /**
    * Sets the granularity of session data replicated across the cluster.
    * Valid values are:
    * <ul>
    * <li>SESSION</li>
    * <li>ATTRIBUTE</li>
    * <li>FIELD</li>
    * </ul>
    * @jmx.managed-attribute
    */
   public void setDefaultReplicationGranularity(
         String defaultReplicationGranularity)
   {
      this.defaultReplicationGranularity = defaultReplicationGranularity;
   }

   /**
    * Gets the type of operations on a <code>HttpSession</code> that 
    * trigger replication.
    * <p>
    * The value of this property is applied to <code>Manager</code> instances
    * that did not have an equivalent property explicitly set in 
    * <code>context.xml</code> or <code>server.xml</code>.
    * </p>
    *  
    * @jmx.managed-attribute
    */
   public String getDefaultReplicationTrigger()
   {
      return defaultReplicationTrigger;
   }

   /**
    * Sets the type of operations on a <code>HttpSession</code> that 
    * trigger replication.  Valid values are:
    * <ul>
    * <li>SET_AND_GET</li>
    * <li>SET_AND_NON_PRIMITIVE_GET</li>
    * <li>SET</li>
    * </ul>
    * 
    * @jmx.managed-attribute
    */
   public void setDefaultReplicationTrigger(String defaultTrigger)
   {
      this.defaultReplicationTrigger = defaultTrigger;
   }

   /**
    * Gets whether Managers should use batch mode replication.
    * Only meaningful if replication granularity is set to <code>FIELD</code>.
    * 
    * @jmx.managed-attribute
    */
   public boolean getDefaultReplicationFieldBatchMode()
   {
      return replicationFieldBatchMode;
   }
   
   /**
    * Sets whether Managers should use batch mode replication.
    * Only meaningful if replication granularity is set to <code>FIELD</code>.
    * 
    * @jmx.managed-attribute
    */
   public void setDefaultReplicationFieldBatchMode(boolean replicationFieldBatchMode)
   {
      this.replicationFieldBatchMode = replicationFieldBatchMode;
   }

   /**
    * Gets when sessions are replicated to the other nodes.
    * The default value, "instant", synchronously replicates changes
    * to the other nodes. In this case, the "SnapshotInterval" attribute
    * is not used.
    * The "interval" mode, in association with the "SnapshotInterval"
    * attribute, indicates that Tomcat will only replicate modified
    * sessions every "SnapshotInterval" miliseconds at most.
    * 
    * @see #getSnapshotInterval()
    * 
    * @jmx.managed-attribute
    */
   public String getSnapshotMode()
   {
      return snapshotMode;
   }

   /**
    * Sets when sessions are replicated to the other nodes. Valid values are:
    * <ul>
    * <li>instant</li>
    * <li>interval</li> 
    * </ul>
    * 
    * @jmx.managed-attribute
    */
   public void setSnapshotMode(String snapshotMode)
   {
      this.snapshotMode = snapshotMode;
   }

   /**
    * Gets how often session changes should be replicated to other nodes.
    * Only relevant if property {@link #getSnapshotMode() snapshotMode} is 
    * set to <code>interval</code>.
    * 
    * @return the number of milliseconds between session replications.
    * 
    * @jmx.managed-attribute
    */
   public int getSnapshotInterval()
   {
      return snapshotInterval;
   }

   /**
    * Sets how often session changes should be replicated to other nodes.
    * 
    * @param snapshotInterval the number of milliseconds between 
    *                         session replications.
    * @jmx.managed-attribute
    */
   public void setSnapshotInterval(int snapshotInterval)
   {
      this.snapshotInterval = snapshotInterval;
   }
   
   // ----------------------------------------------------------------  Cluster

   /**
    * Gets the name of the <code>TreeCache</code>'s JGroups channel.
    * 
    * @see org.apache.catalina.Cluster#getClusterName()
    */
   public String getClusterName()
   {
      return clusterName;
   }
   
   /* (non-javadoc)
    * @see org.apache.catalina.Cluster#getContainer()
    */
   public Container getContainer()
   {
      return container;
   }
   
   /* (non-javadoc)
    * @see org.apache.catalina.Cluster#setContainer()
    */
   public void setContainer(Container container)
   {
      this.container = container;
   }

   /**
    * @see org.apache.catalina.Cluster#getInfo()
    * 
    * @jmx.managed-attribute access="read-only"
    */
   public String getInfo()
   {
      return info;
   }

   /**
    * @see org.apache.catalina.Cluster#createManager(java.lang.String)
    */
   public Manager createManager(String name)
   {
      if (log.isDebugEnabled())
         log.debug("Creating ClusterManager for context " + name
               + " using class " + getManagerClassName());
      Manager manager = null;
      try
      {
         manager = (Manager) getClass().getClassLoader().loadClass(
               getManagerClassName()).newInstance();
      } 
      catch (Exception x)
      {
         log.error("Unable to load class for replication manager", x);
         manager = new JBossCacheManager();
      } 
      finally
      {
         manager.setDistributable(true);
      }
      
      if (manager instanceof JBossCacheManager)
      {
         configureManager((JBossCacheManager) manager);
      }
      
      return manager;
   }

   /**
    * Does nothing; tracking the status of other members of the cluster is
    * provided by the JGroups layer. 
    * 
    * @see org.apache.catalina.Cluster#backgroundProcess()
    */
   public void backgroundProcess()
   {
      ; // no-op
   }

   // ---------------------------------------------  Deprecated Cluster Methods
   
   /**
    * Returns <code>null</code>; method is deprecated.
    * 
    * @return <code>null</code>, always.
    * 
    * @see org.apache.catalina.Cluster#getProtocol()
    */
   public String getProtocol()
   {
      return null;
   }
   
   /**
    * Does nothing; method is deprecated.
    * 
    * @see org.apache.catalina.Cluster#setProtocol(java.lang.String)
    */
   public void setProtocol(String protocol)
   {
      ; // no-op
   }
   
   /**
    * Does nothing; method is deprecated.
    * 
    * @see org.apache.catalina.Cluster#startContext(java.lang.String)
    */
   public void startContext(String contextPath) throws IOException
   {
      ; // no-op
   }
   
   /**
    * Does nothing; method is deprecated.
    * 
    * @see org.apache.catalina.Cluster#installContext(java.lang.String, java.net.URL)
    */
   public void installContext(String contextPath, URL war)
   {
      ; // no-op
   }
   
   /**
    * Does nothing; method is deprecated.
    * 
    * @see org.apache.catalina.Cluster#stop(java.lang.String)
    */
   public void stop(String contextPath) throws IOException 
   {
      ; // no-op
   }

   // ---------------------------------------------------------  Public Methods
   
   /**
    * Sets the cluster-wide properties of a <code>Manager</code> to
    * match those of this cluster.  Does not override 
    * <code>Manager</code>-specific properties with cluster-wide defaults 
    * if the <code>Manager</code>-specfic properties have already been set.
    */
   public void configureManager(JBossCacheManager manager)
   {
      manager.setSnapshotMode(snapshotMode);
      manager.setSnapshotInterval(snapshotInterval);
      manager.setUseJK(useJK);
      manager.setUseLocalCache(useLocalCache);
      manager.setCacheObjectNameString(treeCacheObjectName);
      
      // Only set replication attributes if they were not
      // already set via a <Manager> element in an XML config file
      
      if (manager.getReplicationGranularityString() == null) 
      {
         manager.setReplicationGranularityString(defaultReplicationGranularity);
      }
      
      if (manager.getReplicationTriggerString() == null) 
      {
         manager.setReplicationTriggerString(defaultReplicationTrigger);
      }
      
      if (manager.isReplicationFieldBatchMode() == null)
      {
         manager.setReplicationFieldBatchMode(replicationFieldBatchMode);
   }
   }

   // ---------------------------------------------------------------  Lifecyle

   /**
    * Finds or creates a {@link TreeCache}; if created, starts the 
    * cache and registers it with our JMX server.
    * 
    * @see org.apache.catalina.Lifecycle#start()
    */
   public void start() throws LifecycleException
   {
      if (started)
      {
         throw new LifecycleException("Cluster already started");
      }

      // Notify our interested LifecycleListeners
      lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, this);
      
      try
      {         
         // Tell the JBoss MBeanServerLocator utility 
         // that Tomcat's MBean server is 'jboss'
         MBeanServerLocator.setJBoss(getMBeanServer());
         
         // Initialize the tree cache
         PojoCacheMBean cache = getTreeCache();
         
         if (treeCacheLocal)
         {
            cache.createService();
            cache.startService();
         }
         
         registerMBeans();

         started = true;
         
         // Notify our interested LifecycleListeners
         lifecycle.fireLifecycleEvent(AFTER_START_EVENT, this);

      }
      catch (LifecycleException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         log.error("Unable to start cluster.", e);
         throw new LifecycleException(e);
      } 
   }

   /**
    * If this object created its own {@link TreeCache}, stops it 
    * and unregisters it with JMX.
    * 
    * @see org.apache.catalina.Lifecycle#stop()
    */
   public void stop() throws LifecycleException
   {
      if (!started)
      {
         throw new IllegalStateException("Cluster not started");
      }
      
      // Notify our interested LifecycleListeners
      lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
      
      if (treeCacheLocal)
      {
         treeCache.stopService();
         treeCache.destroyService();
      }

      started = false;
      // Notify our interested LifecycleListeners
      lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, this);

      unregisterMBeans();
   }

   /* (non-javadoc)           
    * @see org.apache.catalina.Lifecycle#addLifecycleListener()
    */
   public void addLifecycleListener(LifecycleListener listener)
   {
      lifecycle.addLifecycleListener(listener);
   }

   /* (non-javadoc)           
    * @see org.apache.catalina.Lifecycle#findLifecycleListeners()
    */
   public LifecycleListener[] findLifecycleListeners()
   {
      return lifecycle.findLifecycleListeners();
   }

   /* (non-javadoc)           
    * @see org.apache.catalina.Lifecycle#removeLifecycleListener()
    */
   public void removeLifecycleListener(LifecycleListener listener)
   {
      lifecycle.removeLifecycleListener(listener);
   }
   
   // -------------------------------------------------------- Private Methods
 
   /**
    * Gets our TreeCache, either from a local reference or the JMX
    * server.  If one is not found, creates and configures it.
    */
   private PojoCacheMBean getTreeCache() throws Exception
   {
      if (treeCache == null) {
         
         MBeanServer server = getMBeanServer();
         ObjectName objName = new ObjectName(treeCacheObjectName);
         if (server.isRegistered(objName))
         {
            // Get a proxy to the existing TreeCache
            treeCache = (PojoCacheMBean) 
                  MBeanProxyExt.create(PojoCacheMBean.class, objName);
         }
         else
         {
            // Create our own tree cache
            treeCache = new PojoCache();
            
            // See if there is an XML descriptor file to configure the cache
            InputStream configIS = getCacheConfigStream();
            
            if (configIS != null)
            {
               PropertyConfigurator config = new PropertyConfigurator();
               config.configure(treeCache, configIS);
               try 
               {
                  configIS.close();
               }
               catch (IOException io)
               {
                  // ignore
               }
               
               if (clusterName != null)
               {
                  // Override the XML config with the name provided in
                  // server.xml.  Method setClusterName is specified in the
                  // Cluster interface, otherwise we would not do this
                  treeCache.setClusterName(clusterName);
               }
            }
            else
            {
               // User did not try to configure the cache.
               // Configure it using defaults.  Only exception
               // is the clusterName, which user can specify in server.xml.
               String channelName = (clusterName == null) ? DEFAULT_CLUSTER_NAME
                                                          : clusterName;
               treeCache.setClusterName(channelName);
               treeCache.setIsolationLevel(DEFAULT_ISOLATION_LEVEL);
               treeCache.setCacheMode(DEFAULT_CACHE_MODE);
               treeCache.setLockAcquisitionTimeout(DEFAULT_LOCK_TIMEOUT);
               treeCache.setTransactionManagerLookupClass(DEFAULT_TM_LOOKUP);
            }
            
            treeCacheLocal = true;
         }
      }
      return treeCache;
   }   
   
   

   private InputStream getCacheConfigStream() throws FileNotFoundException
   {
      boolean useDefault = (this.cacheConfigPath == null);
      String path = (useDefault) ? DEFAULT_CACHE_CONFIG_PATH : cacheConfigPath;
      // See if clusterProperties points to a file relative
      // to $CATALINA_BASE
      File file = new File(path);
      if (!file.isAbsolute())
      {
         file = new File(System.getProperty("catalina.base"), path);
      }
      
      try
      {
         return new FileInputStream(file);
      }
      catch (FileNotFoundException fnf)
      {
         if (useDefault)
         {
            // Not a problem, just means user did not try to
            // configure the cache.  Return null and let the cache
            // be configured from defaults.
            return null;
         }
         else
         {
            // User provided config was invalid; throw the exception
            log.error("No tree cache config file found at " + 
                      file.getAbsolutePath());
            throw fnf;
         }
      }      
   }

   /**
    * Registers this object and the tree cache (if we created it) with JMX.
    */
   private void registerMBeans()
   {
      try
      {
         MBeanServer server = getMBeanServer();
         
         String domain;
         if (container instanceof ContainerBase)
         {
            domain = ((ContainerBase) container).getDomain();
         }
         else
         {
            domain = server.getDefaultDomain();
         }
         
         String name = ":type=Cluster";
         if (container instanceof Host) {
            name += ",host=" + container.getName();
         }
         else if (container instanceof Engine)
         {            
            name += ",engine=" + container.getName();
         }
         
         ObjectName clusterName = new ObjectName(domain + name);

         if (server.isRegistered(clusterName))
         {
            log.warn("MBean " + clusterName + " already registered");
         }
         else
         {
            this.objectName = clusterName;
            server.registerMBean(this, objectName);
         }

         if (treeCacheLocal)
         {
            // Register the treeCache
            ObjectName treeCacheName = new ObjectName(treeCacheObjectName);
            server.registerMBean(getTreeCache(), treeCacheName);
         }

      }
      catch (Exception ex)
      {
         log.error(ex.getMessage(), ex);
      }
   }   

   /**
    * Unregisters this object and the tree cache (if we created it) with JMX.
    */
   private void unregisterMBeans()
   {
      if (mserver != null)
      {
         try
         {
            if (objectName != null) {
               mserver.unregisterMBean(objectName);
            }
            if (treeCacheLocal)
            {
               mserver.unregisterMBean(new ObjectName(treeCacheObjectName));
            }
         }
         catch (Exception e)
         {
            log.error(e);
         }
      }
   }

   /**
    * Get the current Catalina MBean Server.
    * 
    * @return
    * @throws Exception
    */
   private MBeanServer getMBeanServer() throws Exception
   {
      if (mserver == null)
      {
         ArrayList servers = MBeanServerFactory.findMBeanServer(null);
         if (servers.size() > 0)
         {
            mserver = (MBeanServer) servers.get(0);
         }
         else
         {
            mserver = MBeanServerFactory.createMBeanServer();
         }
      }
      return mserver;
   }

}
