/*
 * JBoss, the OpenSource WebOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.web.tomcat.tc5.session;

import java.util.List;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.InitialContext;
import javax.transaction.Status;
import javax.transaction.TransactionManager;

import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.Session;
import org.apache.catalina.core.ContainerBase;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;
import org.jboss.metadata.WebMetaData;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.util.NestedRuntimeException;
import org.jboss.web.tomcat.tc5.Tomcat5;

/**
 * Implementation of a clustered session manager for
 * catalina using JBossCache replication.
 *
 * @author Ben Wang
 * @version $Revision: 1.3.2.15 $
 */
public class JBossCacheManager
   extends JBossManager
{

  /**
    * Informational name for this Catalina component
    */
   private static final String info_ = "JBossCacheManager/1.0";

   // -- Class attributes ---------------------------------

   /**
    * Proxy-object for the JBossCacheService
    */
   private JBossCacheService proxy_;
   
   /** Our TreeCache's ObjectName */
   private String cacheObjectNameString_ = Tomcat5.DEFAULT_CACHE_NAME;

   /**
    * If set to true, will add a JvmRouteFilter to the request.
    */
   private boolean useJK_ = false;
   
   /** Are we running embedded in JBoss? */
   private boolean embedded_ = false;
   
   /** Our JMX Server */
   private MBeanServer mserver_ = null;
   
   /** Our ClusteredSessionValve's snapshot mode. */
   private String snapshotMode_ = null;
   
   /** Our ClusteredSessionValve's snapshot interval. */
   private int snapshotInterval_ = 0;
   
   /** String form of invalidateSessionPolicy_ */
   private String replTriggerString_ = null;
   
   /** String form of replGranularityString_ */
   private String replGranularityString_ = null;

   /**
    * The transaction manager.
    */
   protected TransactionManager tm;

   //  ----------------------------------------------------------  Constructors
   
   public JBossCacheManager()
   {
      super();
   }

   /**
    * Initializes this Manager when running in embedded mode.
    * <p>
    * <strong>NOTE:</strong> This method should not be called when
    * running unembedded.
    * </p>
    */
   public void init(String name, WebMetaData webMetaData, 
                    boolean useJK, boolean useLocalCache)
      throws ClusteringNotSupportedException
   {
      super.init(name, webMetaData, useJK, useLocalCache);
      this.useJK_ = useJK;
      try
      {
         proxy_ = new JBossCacheService();
      }
      catch (Throwable t)
      {
         String str = "JBossCacheService to Tomcat clustering not found";
         log_.error(str);
         throw new ClusteringNotSupportedException(str);
      }
      
      embedded_ = true;
   }

   // -------------------------------------------------------------  Properties
   
   public JBossCacheService getCacheService()
   {
      return proxy_;
   } 

   /**
    * Gets a String representation of the JMX <code>ObjectName</code> under
    * which our <code>TreeCache</code> is registered.
    */
   public String getCacheObjectNameString()
   {
      return cacheObjectNameString_;
   }

   /**
    * Sets the JMX <code>ObjectName</code> under which our 
    * <code>TreeCache</code> is registered.
    */
   public void setCacheObjectNameString(String treeCacheObjectName)
   {
      this.cacheObjectNameString_ = treeCacheObjectName;
   }

   public String getSnapshotMode()
   {
      return snapshotMode_;
   }

   public void setSnapshotMode(String snapshotMode)
   {
      this.snapshotMode_ = snapshotMode;
   }

   public int getSnapshotInterval()
   {
      return snapshotInterval_;
   }

   public void setSnapshotInterval(int snapshotInterval)
   {
      this.snapshotInterval_ = snapshotInterval;
   }
   
   public boolean getUseJK()
   {
      return useJK_;
   }
   
   public void setUseJK(boolean useJK)
   {
      this.useJK_ = useJK;
   }

   /**
    * Gets the 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.
    */   
   public String getReplicationGranularityString()
   {
      return replGranularityString_;
   }

   /**
    * Sets the granularity of session data replicated across the cluster.
    * Valid values are:
    * <ul>
    * <li>SESSION</li>
    * <li>ATTRIBUTE</li>
    * </ul>
    */
   public void setReplicationGranularityString(String granularity)
   {
      this.replGranularityString_ = granularity;
   }

   /**
    * Gets the type of operations on a <code>HttpSession</code> that 
    * trigger replication.
    */   
   public String getReplicationTriggerString()
   {
      return this.replTriggerString_;
   }

   /**
    * 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>
    */
   public void setReplicationTriggerString(String trigger)
   {
      this.replTriggerString_ = trigger;
   }
   
   public void setUseLocalCache(boolean useLocalCache)
   {
      this.useLocalCache_ = useLocalCache;
   }

   public TransactionManager getTransactionManager()
   {
      return tm;
   }

   public void setTransactionManager(TransactionManager manager)
   {	  
      this.tm = manager;
   }

   // Manager-methods -------------------------------------
   
   /**
    * Start this Manager
    *
    * @throws org.apache.catalina.LifecycleException
    *
    */
   public void start() throws LifecycleException
   {
      if (embedded_) 
      {
         startEmbedded();
      }
      else
      {
         startUnembedded();
      }
   }

   public void stop() throws LifecycleException
   {
      clearSessions();
      if (embedded_)
      {
         stopEmbedded();
      }
      else
      {
         stopUnembedded();
      }
   }

   /**
    * Clear the underlying cache store.
    */
   protected void clearSessions()
   {
      Session[] sessions = findSessions();
      for(int i=0; i < sessions.length; i++)
      {
         Session ses = sessions[i];
         if (log_.isDebugEnabled())
         {
             log_.debug("clearSessions(): clear session by expiring: " + ses);
         }
         ses.expire();
      }
   }

   /**
    * Create a new session. Note this does not mean the session is active yet.
    */
   public Session createSession()
   {
      return createSession(null);
   }
       
   /**
    * Create a new session. Note this does not mean the session is active yet.
    */
   public Session createSession(String sessionId)
   {
      ClusteredSession session = null;
      if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE)
      {
         session = new AttributeBasedClusteredSession(this);
      }
      else if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_SESSION)
      {
         session = new SessionBasedClusteredSession(this);
      }

      session.setNew(true);
      session.setCreationTime(System.currentTimeMillis());
      session.setMaxInactiveInterval(this.maxInactiveInterval_);

      if (sessionId == null)
      {
          sessionId = this.getNextId();
          
          // We are using mod_jk for load balancing. Append the JvmRoute.
          if (useJK_)
          {
              if (log_.isDebugEnabled())
              {
                  log_.debug("createSession(): useJK is true. Will append JvmRoute: " + this.getJvmRoute());
              }
              sessionId += "." + this.getJvmRoute();
          }
      }

      session.setValid(true);
      session.setId(sessionId);
      if (log_.isDebugEnabled())
      {
         log_.debug("Creating an ClusteredSession with id: " + session.getId());
      }

      createdCounter_++;
      return session;
   }

   public boolean storeSession(Session baseSession)
   {
      // is this session of the right type?
      if(baseSession == null)
      {
         throw new IllegalArgumentException("storeSession(): session is null");
      }

      if (!(baseSession instanceof ClusteredSession))
      {
         throw new IllegalArgumentException("You can only add ClusteredSessions to this Manager. Session class name: "
         + baseSession.getClass().getName());
      }

      ClusteredSession session = (ClusteredSession) baseSession;
      if (session == null)
      {
         return false;
      }

      if (session.isValid())
      {
         // put it in the local store as well.
         sessions_.put(getRealId(session.getId()), session);

         String id = session.getId();
         long beginPassivate = System.currentTimeMillis();
         // TODO Notify all session attributes that they get serialized (SRV 7.7.2). Not supported yet.
         session.passivate();
         long endPassivate = System.currentTimeMillis();
         stats_.updatePassivationStats(id, (endPassivate - beginPassivate));

         if (log_.isDebugEnabled())
         {
            log_.debug("check to see if needs to store and replicate session with id " + id);
         }

         long beginReplication = System.currentTimeMillis();
         processSessionRepl(session);
         long endReplication = System.currentTimeMillis();
         stats_.updateReplicationStats(id, (endReplication - beginReplication));
         return true;
      }
      else
      {
         return false;
      }
   }

   public void add(Session session)
   {
      if (session == null)
         return;

      if (!session.isValid())
      {
         log_.error("Cannot add session with id=" + session.getId() + " because it is invalid");
         return;
      }

      // maxActive_ -1 is unlimited
      if (maxActive_ != -1 && activeCounter_ >= maxActive_)
      {
         // Exceeds limit. We will need to reject it.
         rejectedCounter_++;
         // Catalina api does not specify what heppens but we will throw a runtime exception for now.
         throw new IllegalStateException("JBossCacheManager.add(): number of active sessions exceeds the maximum limit: " +
            maxActive_ + " when trying to add session id " + session.getId());
      }

      if (storeSession(session))
      {
         activeCounter_++;
         if (log_.isDebugEnabled())
         {
            log_.debug("Session with id=" + session.getId() + " added. Current active sessions " + activeCounter_);
         }
      }
   }

   public Session createEmptySession()
   {
      // We will simply return new ClusteredSession instance now.
      ClusteredSession session = null;
      if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE)
      {
         session = new AttributeBasedClusteredSession(this);
      }
      else if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_SESSION)
      {
         session = new SessionBasedClusteredSession(this);
      }

      if (log_.isDebugEnabled())
      {
         log_.debug("Creating an empty ClusteredSession: " + session);
      }

      // TODO this method is now only called when we need a session object to
      // hydrate from the cache.  Does this count as a "creation"?
      // If not, remove the next line and have createSession(String) call this
      // method to create it's session instance as well (avoid code duplication)
      createdCounter_++;
      return session;
   }

   public Session findSession(String id)
   {
      String realId = getRealId(id);
      ClusteredSession session = findLocalSession(realId);
      // Find it from the local store first.
      if (session != null && !session.isOutdated() )
      {
         return session;
      }
      else
      {
         return loadSession(realId);
      }
   }

   /**
    * Return the sessions. We will find it from the in-memory local ones first. 
    * If not found, we search for the underlying store as well just to be sure.
    * @return
    */
   public Session[] findSessions()
   {
      // Will need to find from the underlying store
      List ids = proxy_.getNewSessionsInStore();
      if(ids.size() ==0)
      {
         Session[] sess = new Session[0];
         sess = (Session[]) sessions_.values().toArray(sess);
         return sess;
      }

      if(log_.isDebugEnabled()) {
         log_.debug("findSessions: find ids from cache store: " + ids);
      }


      // Is there a better way to do this?
      for(int i=0; i < ids.size(); i++) {
         Session session = loadSession((String)ids.get(i));
         if( session == null )
         {
            // This can happen now if the node has been removed before it has been called.

            // Something is wrong with session. Should not have id but null session!
//            log_.warn("Has session id: " +ids.get(i) + " but session is null. Will remove this from map");
//            sessions_.remove(ids.get(i));
//            proxy_.removeSession((String)ids.get(i));
            continue;
         }
         sessions_.put(ids.get(i), session);   // Populate local copy as well.
      }

      Session[] sess = new Session[0];
      sess = (Session[]) sessions_.values().toArray(sess);
      return sess;
   }

   public ClusteredSession[] findLocalSessions()
   {
      ClusteredSession[] sess = new ClusteredSession[0];

      sess = (ClusteredSession[]) sessions_.values().toArray(sess);
      return sess;
   }

   public ClusteredSession findLocalSession(String realId)
   {
      ClusteredSession session = (ClusteredSession) sessions_.get(realId);
      return session;
   }

   public void remove(Session session)
   {
      String id = session.getId();
      if (id == null) return;
      // Let's do it in brute force.
      if (log_.isDebugEnabled())
      {
         log_.debug("Removing session from store with id: " + id);
      }
      ((ClusteredSession) session).removeMyself();
      sessions_.remove(getRealId(session.getId()));

      activeCounter_--;
   }

   public void removeLocal(Session session)
   {
      String id = session.getId();
      if (id == null) return;
      // Let's do it in brute force.
      if (log_.isDebugEnabled())
      {
         log_.debug("Removing session from local store with id: " + id);
      }
      ((ClusteredSession) session).removeMyselfLocal();
      sessions_.remove(getRealId(session.getId()));
      // It's a bit ad-hoc to do it here. But since we currently call this when session expires ...
      expiredCounter_++;
      activeCounter_--;
   }   

   /**
    * Load a session from the distributed store
    *
    * @param realId The session-id for the session to load without JvmRoute
    * @return the session or null if the session cannot be found in the distributed store
    */
   protected Session loadSession(String realId)
   {
      ClusteredSession session = null;

      if (realId == null)
      {
         return null;
      }

      // TODO We will need to determine if we want stats for loading since there will be a lot of them
//      long begin = System.currentTimeMillis();
      session = (ClusteredSession) sessions_.get(realId);
      if (session == null)
      {
         // TODO createSession should specify it returns ClusteredSession
         // in its signature
         session = (ClusteredSession) createEmptySession();
      }
      
      session = proxy_.loadSession(realId, session);
      
      // Will need to initialize.
      if (session != null)
      {
         session.initAfterLoad(this);
         /* Put the session into the local map or else other calls to find
         the session after the request completes will wipe out the dirty state
         due to any mods made by the web app.
         */
         sessions_.put(getRealId(session.getId()), session);
//      long end = System.currentTimeMillis();
//      stats_.updateLoadStats(id, (end - begin));
      }

      if (log_.isDebugEnabled())
      {
         log_.debug("loadSession(): id= " + realId + ", session=" + session);
      }

      return session;
   }

   protected void processSessionRepl(ClusteredSession session)
   {
      boolean doTx = false;
      try
      {
         // We need transaction so all the replication are sent in batch.
         // Don't do anything if there is already transaction context associated with this thread.
         if(tm.getTransaction() == null)
            doTx = true;

         if(doTx)
            tm.begin();

         session.processSessionRepl();
      }
      catch (Exception ex)
      {
         log_.error("processSessionRepl: failed with exception: " + ex);
         try
         {
//            if(doTx)
               // Let's set it no matter what.
               tm.setRollbackOnly();
         }
         catch (Exception exn)
         {
            exn.printStackTrace();
         }
         // We will need to alert Tomcat of this exception.
         throw new NestedRuntimeException("JBossCacheManager.processSessionRepl(): failed to replicate session.", ex);
      }
      finally
      {
         if(doTx)
            endTransaction();
      }
   }

   protected void endTransaction()
   {
      try {
         if(tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK)
         {
            tm.commit();
         } else
         {
            tm.rollback();
         }
      } catch (Exception e) {
         e.printStackTrace();
         throw new NestedRuntimeException("TreeCacheAop.endTransaction(): ", e);
      }
   }

   /**
    * Go through all sessions and look if they have expired. Note this overrides the method in JBossManager.
    */
   protected void processExpires()
   {
      if (log_.isTraceEnabled())
      {
         log_.trace("Looking for sessions that have expired ...");
      }

      // Get all sessions
      // Does not really need tx. But just to comform with the cache usage.
      try
      {
         Session sessions[] = findSessions();
         for (int i = 0; i < sessions.length; ++i)
         {
            ClusteredSession session = (ClusteredSession) sessions[i];
            if(session == null)
            {
               log_.warn("processExpires(): processing null session at index " +i);
               continue;
            }

            // We only look at valid sessions. This will remove session if not valid already.
            boolean doTx = false;
            try
            {
               // We need transaction so all the replication are sent in batch.
               // Don't do anything if there is already transaction context associated with this thread.
               if(tm.getTransaction() == null)
                  doTx = true;

               if(doTx)
                  tm.begin();

               if (!session.isValid()) continue;
            }
            catch (Exception ex)
            {
               log_.error("processSessionExpire: failed with exception: " + ex);
               try
               {
//            if(doTx)
                     // Let's set it no matter what.
                     tm.setRollbackOnly();
               }
               catch (Exception exn)
               {
                  exn.printStackTrace();
               }
               // We will need to alert Tomcat of this exception.
               throw new NestedRuntimeException("JBossCacheManager.processSessionExpire(): failed to expire session.", ex);
            }
            finally
            {
               if(doTx)
                  endTransaction();
            }
         }
      }
      catch (Exception ex)
      {
         log_.error("processExpires: failed with exception: " + ex);
         ex.printStackTrace();
      }
   }

   // Get the id without JvmRoute.
   private String getRealId(String id)
   {
      // TODO May need optimization
      if (!useJK_) return id;

      int index = id.indexOf(".");
      if (index > 0)
      {
         return id.substring(0, id.indexOf("."));
      }
      else
      {
         return id;
      }
   }
   
   /**
    * Creates a JvmRouteFilter and installs it in our parent Context.
    */
   private void installJvmRouteValve() throws Exception
   {
      log_.info("We are using mod_jk(2) for load-balancing. Will add JvmRouteValve.");

      // Add the session snapshot valve to the Context
      JvmRouteValve valve = new JvmRouteValve(this);
      // In embedded mode, TomcatDeployer installs the valve via JMX
      // but we can't do that in unembedded mode because we are called
      // before our Context is registered with the MBean server
      if (embedded_ && getContextObjectName() != null) {
         getMBeanServer().invoke(getContextObjectName(), "addValve",
                                 new Object[]{valve},
                                 new String[]{"org.apache.catalina.Valve"});
      }
      else if (container_ instanceof ContainerBase)
      {
         // Add the valve via ContainerBase.addValve, which is
         // consistent with how it is done when we are embedded
         ((ContainerBase) container_).addValve(valve);
      }
      else
      {
         // No choice; have to add it to the context's pipeline
         container_.getPipeline().addValve(valve);
      }
   }
   
   // ----------------------------------------------------  Lifecyle Unembedded
   
   /**
    * Start this Manager when running embedded in JBoss AS.
    *
    * @throws org.apache.catalina.LifecycleException
    */
   private void startEmbedded() throws LifecycleException
   {
      super.start();

      // Obtain the transaction manager
      try
      {
         tm =(TransactionManager)new InitialContext().lookup("java:/TransactionManager");
      }
      catch (Exception e)
      {
         log_.error("Cannot get a reference to the transaction manager", e);
         throw new LifecycleException(e);
      }
      
      try
      {         
         // Adding JvmRouteFilter if needed
         if (useJK_)
         {
            installJvmRouteValve();
         }

         // Add our own snapshot valve
         installSnapshotValve();

         // Find JBossCacheService
         // Will need to pass the classloader that is associated with this web app so de-serialization will work correctly.
         ClassLoader tcl = super.getContainer().getLoader().getClassLoader();
         proxy_.start(tcl, this);
         
         if (log_.isDebugEnabled())
         {
            log_.debug("start(): JBossCacheService started");
         }
      }
      catch (Exception e)
      {
      	log_.error("Exception starting JBossCacheManager", e);
         throw new LifecycleException(e);
      }
   }

   /**
    * Stop this Manager when running embedded in JBoss AS.
    */
   private void stopEmbedded() throws LifecycleException
   {
      super.stop();
      proxy_.stop();
      tm = null;
   }
   
   // -----------------------------------------------  Lifecyle When Unembedded

   /**
    * Start this Manager when running in standalone Tomcat.
    */
   private void startUnembedded() throws LifecycleException
   {
      if (started_)
      {
         return;
      }
      
      if (log_.isInfoEnabled())
      {
         log_.info("Manager is about to start");
      }

      // Notify our interested LifecycleListeners
      lifecycle_.fireLifecycleEvent(BEFORE_START_EVENT, this);
      
      if (tm == null) 
      {
         // We were not instantiated by a JBossCacheCluster, so we need to
         // find one and let it configure our cluster-wide properties
         try 
         {
            JBossCacheCluster cluster = (JBossCacheCluster) container_.getCluster();
            cluster.configureManager(this);
         }
         catch (ClassCastException e)
         {
            String msg = "Cluster is not an instance of JBossCacheCluster";
            log_.error(msg, e);
            throw new LifecycleException(msg, e);
         }
      }      
      
      // Validate attributes
      
      if ("SET_AND_GET".equalsIgnoreCase(replTriggerString_))         
         this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET_AND_GET;
      else if ("SET_AND_NON_PRIMITIVE_GET".equalsIgnoreCase(replTriggerString_))
         this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET;
      else if ("SET".equalsIgnoreCase(replTriggerString_))
         this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET;
      else
         throw new LifecycleException("replication-trigger value set to a " +
                                       "non-valid value: '" + 
                                       replTriggerString_ + 
                                       "' (should be ['SET_AND_GET', " +
                                       "'SET_AND_NON_PRIMITIVE_GET', 'SET'])");
      
      if ("SESSION".equalsIgnoreCase(replGranularityString_))
         this.replicationGranularity_ = WebMetaData.REPLICATION_GRANULARITY_SESSION;
      else if ("ATTRIBUTE".equalsIgnoreCase(replGranularityString_))
         this.replicationGranularity_ = WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE;
      else
         throw new LifecycleException("replication-granularity value set to " +
                                       "a non-valid value: '" + 
                                       replGranularityString_ +
                                       "' (should be ['SESSION', " +
                                       "'ATTRIBUTE'])");
      
      // Create the JBossCacheService
      try
      {
         proxy_ = new JBossCacheService(cacheObjectNameString_);
         // We need to pass the classloader that is associated with this 
         // web app so de-serialization will work correctly.
         ClassLoader cl = container_.getLoader().getClassLoader();
         proxy_.start(cl, this);
      }
      catch (Throwable t)
      {
         String str = "JBossCacheService to Tomcat clustering not found";
         log_.error(str);
         throw new LifecycleException(str, t);
      }
      
      try
      {
         // Add JvmRouteFilter to Context if needed
         if (useJK_)
         {
            installJvmRouteValve();
         }

         // We must add our own snapshot valve since unembedded
         // Tomcat will not do it for us
         installSnapshotValve();

         started_ = true;
         
         // Notify our interested LifecycleListeners
         lifecycle_.fireLifecycleEvent(AFTER_START_EVENT, this);
         
         if (log_.isDebugEnabled())
         {
            log_.debug("start(): JBossCacheService started");
         }
      } 
      catch (Exception e)
      {
         log_.error("Unable to start manager.", e);
         throw new LifecycleException(e);
      }
      
      try
      {
         registerMBeans();
      }
      catch (Exception e)
      {
         log_.error("Could not register ManagerMBean with MBeanServer", e);
      }
   }

   /**
    * Stop this Manager when running in standalone Tomcat.
    */
   private void stopUnembedded()
   {
      resetStats();
      
      if (!started_)
      {
         throw new IllegalStateException("Manager not started");
      }
      
      // Notify our interested LifecycleListeners
      lifecycle_.fireLifecycleEvent(BEFORE_STOP_EVENT, this);
      
      proxy_.stop();
      tm = null;
      
      started_ = false;
      
      // Notify our interested LifecycleListeners
      lifecycle_.fireLifecycleEvent(AFTER_STOP_EVENT, this);
      
      try
      {
         unregisterMBeans();
      }
      catch (Exception e)
      {
         log_.error("Could not unregister ManagerMBean from MBeanServer", e);
      }
   }

   /**
    * Register this Manager with JMX.
    */
   private void registerMBeans() 
   {
      try
      {
         MBeanServer server = getMBeanServer();

         String domain;
         if (container_ instanceof ContainerBase)
         {
            domain = ((ContainerBase) container_).getDomain();
         }
         else
         {
            domain = server.getDefaultDomain();
         }
         String hostName = ((Host) container_.getParent()).getName();
         hostName = (hostName == null) ? "localhost" : hostName;
         ObjectName clusterName = new ObjectName(domain
               + ":service=ClusterManager,WebModule=//" + hostName
               + ((Context) container_).getPath());

         if (server.isRegistered(clusterName))
         {
            log_.warn("MBean " + clusterName + " already registered");
            return;
         }

         objectName_ = clusterName;
         server.registerMBean(this, clusterName);

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

   /**
    * Unregister this Manager from the JMX server.
    */
   private void unregisterMBeans()
   {
      if (mserver_ != null)
      {
         try
         {
            mserver_.unregisterMBean(objectName_);
         }
         catch (Exception e)
         {
            log_.error(e);
         }
      }
   }

   /**
    * Get the current MBean Server.
    * 
    * @return
    * @throws Exception
    */
   private MBeanServer getMBeanServer() throws Exception
   {
      if (mserver_ == null)
      {
         mserver_ = MBeanServerLocator.locateJBoss();
      }
      return (mserver_);
   }

   /**
    * Instantiate a SnapshotManager and ClusteredSessionValve and add 
    * the valve to our parent Context's pipeline.
    * 
    * @throws Exception
    */
   private void installSnapshotValve() throws Exception
   {
      String ctxPath = ((Context) container_).getPath();
      
      // choose the snapshot manager
      SnapshotManager snap = null;
      if ("instant".equals(snapshotMode_))
      {
         snap = new InstantSnapshotManager(this, ctxPath);
      }
      else if ("interval".equals(snapshotMode_))
      {
         snap = new IntervalSnapshotManager(this, ctxPath, snapshotInterval_);
      }
      else
      {
         log_.error("Snapshot mode must be 'instant' or 'interval' - " +
                    "using 'instant'");
         snap = new InstantSnapshotManager(this, ctxPath);
      }
      
      // Add the session snapshot valve to the Context
      ClusteredSessionValve valve = new ClusteredSessionValve(snap);
      // In embedded mode, TomcatDeployer installs the valve via JMX
      // but we can't do that in unembedded mode because we are called
      // before our Context is registered with the MBean server
      if (embedded_ && getContextObjectName() != null) {
         getMBeanServer().invoke(getContextObjectName(), "addValve",
                                 new Object[]{valve},
                                 new String[]{"org.apache.catalina.Valve"});
      }
      else if (container_ instanceof ContainerBase) 
      {
         // Add the valve via ContainerBase.addValve, which is
         // consistent with how it is done when we are embedded
         ((ContainerBase) container_).addValve(valve);
      }
      else 
      {
         // No choice; have to add it to the context's pipeline
         container_.getPipeline().addValve(valve);
      }
   }
   
   private ObjectName getContextObjectName()
   {
      String oname = container_.getObjectName();
      try
      {         
         return (oname == null) ? null : new ObjectName(oname);
      }
      catch (MalformedObjectNameException e)
      {
         log_.warn("Error creating object name from string " + oname, e);
         return null;
      }
   }

}
