org.jboss.web.tomcat.service.session.JBossCacheCluster.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.web.tomcat.service.session.JBossCacheCluster.java

Source

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.web.tomcat.service.session;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;

import javax.management.MBeanServer;
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.apache.tomcat.util.modeler.Registry;
import org.jboss.metadata.web.jboss.ReplicationGranularity;
import org.jboss.metadata.web.jboss.ReplicationTrigger;
import org.jboss.metadata.web.jboss.SnapshotMode;
import org.jboss.mx.util.MBeanServerLocator;
import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributedCacheManagerFactoryFactory;
import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData;
import org.jboss.web.tomcat.service.session.distributedcache.spi.TomcatClusterConfig;
import org.jboss.web.tomcat.service.session.distributedcache.spi.TomcatClusterDistributedCacheManagerFactory;

/**
 * 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: 87304 $
 */
public class JBossCacheCluster implements TomcatClusterConfig, 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_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;

    /** The factory we use to create our DistributedCacheManager */
    private TomcatClusterDistributedCacheManagerFactory factory = null;

    /** Are we started? */
    private boolean started = false;

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

    /** Name under which our TreeCache is registered in JMX */
    private String pojoCacheObjectName = "jboss.cache:service=TomcatClusteringCache";

    /** 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 ReplicationTrigger defaultReplicationTrigger = null;

    /**
     * Default replication granularity to assign to our Managers
     * that haven't had this property set.
     */
    private ReplicationGranularity defaultReplicationGranularity = null;

    /**
     * JBossCacheManager's snapshot mode.
     */
    private SnapshotMode snapshotMode = null;

    /**
     * JBossCacheManager's snapshot interval.
     */
    private int snapshotInterval = 0;

    /** Whether we use batch mode replication for field level granularity */
    private boolean replicationFieldBatchMode = true;

    //  ----------------------------------------------------------  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 Tomcat6.DEFAULT_CACHE_NAME the default name used in 
     * embedded JBoss/Tomcat}.
     * </p>
     * 
     * @jmx.managed-attribute
     */
    public String getCacheObjectName() {
        return pojoCacheObjectName;
    }

    /**
     * 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.pojoCacheObjectName = 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;
    }

    /**
     * Get the current Catalina MBean Server.
     * 
     * @return the mbean server
     */
    public MBeanServer getMBeanServer() {
        if (mserver == null) {
            mserver = Registry.getRegistry(null, null).getMBeanServer();
        }
        return mserver;
    }

    /**
     * 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;
    }

    public void registerManager(Manager arg0) {
        // TODO tie this into the managerClassName      
    }

    public void removeManager(Manager arg0) {
        // TODO tie this into the 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 == null ? null : defaultReplicationGranularity.toString();
    }

    /**
     * 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 == null ? null
                : ReplicationGranularity.fromString(defaultReplicationGranularity.toUpperCase()));
    }

    /**
     * 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 == null ? null : defaultReplicationTrigger.toString();
    }

    /**
     * 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 == null ? null
                : ReplicationTrigger.fromString(defaultTrigger.toUpperCase()));
    }

    /**
     * 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 == null ? null : snapshotMode.toString();
    }

    /**
     * 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 == null ? null : SnapshotMode.fromString(snapshotMode.toUpperCase()));
    }

    /**
     * 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)
     */
    @SuppressWarnings("unchecked")
    public Manager createManager(String name) {
        if (log.isDebugEnabled())
            log.debug("Creating ClusterManager for context " + name + " using class " + getManagerClassName());
        Manager manager = null;

        String mgrClass = getManagerClassName();
        if (mgrClass != null && !JBossCacheManager.class.getName().equals(mgrClass)) {
            try {
                manager = (Manager) getClass().getClassLoader().loadClass(mgrClass).newInstance();
            } catch (Exception x) {
                log.error("Unable to load class " + mgrClass + " for replication manager; using JBossCacheManager",
                        x);
            }
        }

        if (manager == null) {
            if (factory == null)
                throw new IllegalStateException("PojoCache not initialized");
            manager = new JBossCacheManager(factory);
        }

        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<? extends OutgoingDistributableSessionData> manager) {
        manager.setSnapshotMode(snapshotMode);
        manager.setSnapshotInterval(snapshotInterval);
        manager.setUseJK(useJK);

        // Only set replication attributes if they were not
        // already set via a <Manager> element in an XML config file

        if (manager.getReplicationGranularity() == null) {
            manager.setReplicationGranularity(defaultReplicationGranularity);
        }

        if (manager.getReplicationTrigger() == null) {
            manager.setReplicationTrigger(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'
            // JBAS-4623 Only do this if there isn't already a 'jboss' server
            try {
                MBeanServerLocator.locateJBoss();
            } catch (IllegalStateException ise) {
                // This is the expected condition when running in standalone Tomcat
                MBeanServerLocator.setJBoss(getMBeanServer());
            }

            // Initialize the tree cache

            factory = DistributedCacheManagerFactoryFactory.getInstance()
                    .getTomcatClusterDistributedCacheManagerFactory(this);

            registerMBeans();

            factory.start();

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

        try {
            factory.stop();
        } catch (Exception e) {
            throw new LifecycleException("Failed to stop DistributedCacheManagerFactory", e);
        }

        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

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

        if (file.exists()) {
            return file;
        } else {
            // User provided config was invalid; throw the exception
            String msg = "No tree cache config file found at " + file.getAbsolutePath();
            log.error(msg);
            throw new IllegalStateException(msg);
        }
    }

    /**
     * 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);
            }

        } 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);
                }
            } catch (Exception e) {
                log.error(e);
            }
        }
    }

}