util.DiarynetTreeCache.java Source code

Java tutorial

Introduction

Here is the source code for util.DiarynetTreeCache.java

Source

/**
* Copyright (c) 2001-2012 "Redbasin Networks, INC" [http://redbasin.org]
*
* This file is part of Redbasin OpenDocShare community project.
*
* Redbasin OpenDocShare is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package util;

// All the JBoss classes
import org.jboss.cache.TreeCache;
import org.jboss.cache.Node;
import org.jboss.cache.Fqn;
import org.jboss.cache.lock.IsolationLevel;
import org.jboss.cache.lock.LockingException;
import org.jboss.cache.lock.TimeoutException;
import org.jboss.cache.PropertyConfigurator;

// for standard log4j logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

// for all java classes
import java.util.Date;
import java.util.Set;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeMap;

import model.Stringifier;

/**
 * This is a simple LRU implementation of JBoss tree cache client code.
 * This code is primarily for the use of clients or webapps.<p>
 *
 * This class was designed to be used outside JSOM and depends
 * (directly and indirectly) on only a few application classes,
 * ObjectUtil, ModelCacheKey, Stringifier, BasicCacheException, ObjException.<p>
 *
 * The implementation is thread safe and needs to be configured before being
 * used. Most configuration can be initiated from Spring but doesn't
 * have to be.<p>
 *
 * An example below indicates the initialization process (or you can set
 * using spring IOC):<p>
 *
 * DiarynetTreeCache diarynetTreeCache = new DiarynetTreeCache();<br>
 * diarynetTreeCache.setObjUtil(objUtil);<br>
 * diarynetTreeCache.setCacheMode("LOCAL");<br>
 * diarynetTreeCache.setIsolationLevel("REPEATABLE_READ");<br>
 * diarynetTreeCache.setMaxItems(100000);<br>
 * diarynetTreeCache.setSweepItems(20000);<br>
 * diarynetTreeCache.setClusterName("MyCluster");<br>
 * diarynetTreeCache.setConfigFile("myjboss-config.xml");<br>
 * diarynetTreeCache.init();<p>
 *
 * When you are done using the object, you must call:<p>
 *
 * diarynetTreeCache.destroy();<p>
 *
 * Please note, in case of hot war deployments, the destroy() needs to
 * automatically happen. In Spring, we do this by using a destroy-method
 * directive in the spring xml file.
 *
 * @author Smitha Gudur (smitha@redbasin.com)
 * @version $Revision: 1.1 $
*/
public class DiarynetTreeCache {

    // variables initialized by spring, volatile not really required
    // but since one time initialization, price of volatile is not high
    private volatile String cacheMode = "LOCAL"; // default overridden by spring
    private volatile String isolationLevel = "NONE"; // default overridden by spring
    private volatile boolean cachingDisabled = false;
    private volatile boolean eopEnabled = false;
    private volatile CacheApi cacheUtil;
    private volatile BasicMathUtil mathUtil;
    private volatile int cacheSize = 500; // default overridden by spring
    private volatile ObjectUtil objUtil;
    private volatile int maxItems = 1000; // default overridden by spring
    private volatile int sweepItems = 300; // default overridden by spring
    private volatile String clusterName = "Default"; // default overridden by spring
    private volatile String configFile = "jboss-config.xml";

    // hold on (religiously) to the JBoss tree cache
    private volatile TreeCache treeCache;

    /** Logger for this class and subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    private volatile long totalTraffic = 0;
    private volatile long cacheHits = 0;
    private volatile long expiryInMin = 1;

    /**
     * Ignition for the JBoss TreeCache service. This method is typically
     * called by spring config at startup. But if spring config is not
     * around, you must call it explicitly! This method depends on the
     * config file being set.
     *
     * @exception BasicCacheException If we have a problem interpreting the data or the data is missing or incorrect
    */
    public void init() throws BasicCacheException {
        logger.info("Initializing JBoss cache...");
        try {
            treeCache = new TreeCache();
            PropertyConfigurator config = new PropertyConfigurator();
            config.configure(treeCache, configFile);
            //treeCache.setTransactionManagerLookupClass("org.jboss.cache.DummyTransactionManagerLookup");
            if (cacheMode.equalsIgnoreCase("LOCAL")) {
                treeCache.setCacheMode(TreeCache.LOCAL);
            } else {
                // TODO: for now just don't do anything here
                treeCache.setCacheMode(TreeCache.REPL_ASYNC);
                treeCache.setClusterName(clusterName);
            }
            if (isolationLevel.equalsIgnoreCase("REPEATABLE_READ")) {
                treeCache.setIsolationLevel(IsolationLevel.REPEATABLE_READ);
            } else {
                // TODO: for now just don't do anything here
                logger.warn("The isolation level has not been set!!");
            }
            treeCache.createService();
            treeCache.startService();
        } catch (Exception e) {
            throw new BasicCacheException("Could not initialize STreeCache bean from spring", e);
        }
        logger.info("...successfully initialized JBoss cache!");
    }

    /**
     * GC can call this optionally. We don't rely on it. Additionally
     * destroy() is called from spring that calls this method.
     */
    public void finalize() {
        logger.info("Destroying JBoss treeCache!");
        try {
            if (treeCache != null) {
                treeCache.stopService();
                treeCache.destroyService();
            }
            treeCache = null;
        } catch (Exception e) {
            logger.warn("Could not stop and destroy treeCache service!", e);
        }
        totalTraffic = 0;
        cacheHits = 0;
    }

    /**
     * This method gets called usually from spring. But if spring is
     * not being used, you must ensure that this is called when the
     * webapp gets restarted due to a hot deploy.
     */
    public void destroy() {
        finalize();
    }

    /**
     * Get the actual JBoss tree cache bean. Not recommended for use,
     * unless you are testing.
     *
     * @return TreeCache An instance of the TreeCache bean.
     */
    public TreeCache getTreeCache() {
        return treeCache;
    }

    /**
     * Put the object into cache based on the id. The id will
     * be used to identify the object.<p>
     *
     * This method is potentially called by multiple threads.
     *
     * @param fqn this is the fqn of the node
     * @param id id of the object to be put in cache
     * @param obj object to be put in the cache
     * @exception BasicCacheException run time exception in case there is a problem
     */
    public synchronized void put(Fqn fqn, Object id, Object obj) throws BasicCacheException {
        if (isCachingDisabled())
            return;
        //logger.info("Enter put(): fqn = " + fqn.toString());
        Stringifier jobj = new Stringifier();
        jobj.setObject("object", obj);
        Long t = new Long(new Date().getTime());
        jobj.setObject("pubdate", t);
        jobj.setObject("accessDate", new Long(new Date().getTime()));

        try {
            // TODO: this is a performance penalty!
            Node node = treeCache.get(fqn);
            if (node != null) { // check for cache overflow
                Map map = node.getData();
                if (map != null) {
                    int nodeSize = map.size();
                    if (nodeSize >= maxItems) { // TODO: this is expensive!
                        TreeMap treeMap = new TreeMap();
                        Iterator iter = map.keySet().iterator();
                        while (iter.hasNext()) {
                            Stringifier stringifier = (Stringifier) map.get(iter.next());
                            if (stringifier != null) {
                                if (stringifier.getObject("accessDate") != null) {
                                    ModelCacheKey cacheKey = new ModelCacheKey(
                                            ((Long) stringifier.getObject("accessDate")).longValue(), false);
                                    treeMap.put((Object) cacheKey, (Object) stringifier);
                                }
                            }
                        }
                        Iterator iter1 = treeMap.keySet().iterator();

                        // sweep oldest cache entries
                        int cntr = 0;
                        logger.info(fqn.toString() + " sweeping items...");
                        while (iter1.hasNext() && (cntr < sweepItems)) {
                            Object key = iter1.next();
                            Object rbobj = treeMap.get(key);
                            if (rbobj != null) {
                                Stringifier stringifier = (Stringifier) rbobj;
                                Object rbid = stringifier.getObject("id");
                                if (rbobj != null) {
                                    if (exists(fqn, rbid)) {
                                        remove(fqn, rbid); // from treeCache!
                                        cntr++;
                                    }
                                }
                            }
                        }
                        logger.info("...done sweeping " + cntr + " items.");
                        Node node1 = treeCache.get(fqn);
                        if (node1 != null) {
                            Map map1 = node1.getData();
                            if (map1 != null) {
                                int nodeSize1 = map1.size();
                                if ((fqn != null) && (fqn.toString() != null)) {
                                    logger.warn("Max Items = " + maxItems + ", Size = " + nodeSize1 + ", Removed "
                                            + cntr + " items from cache. sweepItems is " + sweepItems);
                                }
                            }
                        }
                    }
                }
            }
            treeCache.put(fqn, id, (Object) jobj);
        } catch (LockingException e) {
            throw new BasicCacheException("Exception occurred while putting content into cache. id = " + id, e);
        } catch (TimeoutException e1) {
            throw new BasicCacheException("Exception occurred while fetching content from cache. id = " + id, e1);
        } catch (org.jboss.cache.CacheException e2) {
            throw new BasicCacheException("Exception occurred while fetching content from cache. id = " + id, e2);
        }
        //logger.info("put(): before yield = " + fqn.toString());
        Thread.currentThread().yield();
        //logger.info("Exit put(): fqn = " + fqn.toString());
    }

    /**
     * Remove an object from the cache.
     * The id will be used to identify the object<p>
     *
     * This method is potentially called by multiple threads. Uses
     * an instance variable, so it is synchronized.
     *
     * @param fqn the fqn of the node
     * @param id id of the object to be removed from cache
     * @exception BasicCacheException run time exception in case there is a problem
     */
    public synchronized void remove(Fqn fqn, Object id) throws BasicCacheException {
        if (isCachingDisabled())
            return;
        try {
            treeCache.remove(fqn, id);
        } catch (LockingException e) {
            throw new BasicCacheException("Exception occurred while removing content from cache. id = " + id, e);
        } catch (TimeoutException e1) {
            throw new BasicCacheException("Exception occurred while removing content from cache. id = " + id, e1);
        } catch (org.jboss.cache.CacheException e2) {
            throw new BasicCacheException("Exception occurred while removing content from cache. id = " + id, e2);
        }
    }

    public boolean isExpired(Fqn fqn, Object id, long eim) {
        boolean expired = true;
        try {
            Object obj = (Object) treeCache.get(fqn, id);
            if (obj != null) {
                long pubdate = ((Long) ((Stringifier) obj).getObject("pubdate")).longValue();
                long ctime = new Date().getTime();
                if (((double) (ctime - pubdate) / (double) (1000)) > (double) (eim * 60)) {
                    expired = true;
                } else
                    expired = false;
            }
        } catch (Exception e) {
        }
        if (expired) {
            remove(fqn, id);
            //logger.info("Expired fqn: " + fqn.toString() + ", id: " + id);
        } else {
            //logger.info("Not expiring fqn: " + fqn.toString() + ", id: " + id);
        }
        return expired;
    }

    public boolean isExpired(Fqn fqn, Object id) {
        return isExpired(fqn, id, expiryInMin);
    }

    /**
     * Tests if an object exists in the cache.
     *
     * This method is potentially called by multiple threads. But it
     * uses only stack variables, and doesn't need to be synchronized.
     *
     * @param fqn the fqn of the node
     * @param id id of the object to be removed from cache
     * @return boolean flag if id exists in cache
     * @exception BasicCacheException run time exception in case there is a problem
     */
    public boolean exists(Fqn fqn, Object id) throws BasicCacheException {
        return exists(fqn, id, expiryInMin);
    }

    public boolean exists(Fqn fqn, Object id, long eim) throws BasicCacheException {
        return ((!isExpired(fqn, id, eim)) && treeCache.exists(fqn, id));
    }

    /**
     * Return the map of all children of the corresponding node. The
     * id will be used to identify the object.<p>
     *
     * This method is potentially called by multiple threads. It uses
     * instance variables, so we will synchronize it.
     *
     * @param fqn the fqn of the node
     * @return Map returns a Map bean if data found or returns null
     * @exception BasicCacheException run time exception in case there is a problem
     */
    public synchronized Map getNodeMap(Fqn fqn) throws BasicCacheException {
        if (isCachingDisabled())
            return null;
        try {
            Node node = treeCache.get(fqn);
            if (node != null) {
                return node.getData();
            } else
                return null;
        } catch (LockingException e) {
            throw new BasicCacheException("Exception occurred while fetching content from cache.", e);
        } catch (TimeoutException e1) {
            throw new BasicCacheException("Exception occurred while fetching content from cache.", e1);
        } catch (org.jboss.cache.CacheException e2) {
            throw new BasicCacheException("Exception occurred while fetching content from cache.", e2);
        }
    }

    /**
     * Return the data corresponding to this id. The
     * id will be used to identify the object.<p>
     *
     * The client must cast the returned Object with the required
     * type. The id must contain the exact id, otherwise, this
     * method will return a null.
     *
     * This method is potentially called by multiple threads.
     *
     * @param fqn fqn of the node
     * @param id id must contain exact asset id
     * @param eim expiry in minutes
     * @exception BasicCacheException run time exception in case there is a problem
     * @return Object returns the object from cache
     */
    public synchronized Object get(Fqn fqn, Object id, long eim) throws BasicCacheException {
        if (isCachingDisabled())
            return null;
        //logger.info(dumpStats(fqn));
        totalTraffic++;
        try {
            Object obj = (Object) treeCache.get(fqn, id);
            if (obj != null) {
                // update the access date!
                long pubdate = ((Long) ((Stringifier) obj).getObject("pubdate")).longValue();
                long ctime = new Date().getTime();
                if (((double) (ctime - pubdate) / (double) (1000)) > (double) (eim * 60)) {
                    if (exists(fqn, id)) {
                        remove(fqn, id);
                        //logger.info("Expired fqn: " + fqn.toString() + ", id: " + id);
                    } //else logger.warn("Could not find expired id " + id + " to remove from cache");
                    return null;
                }
                cacheHits++;
                ((Stringifier) obj).setObject("accessDate", new Long(new Date().getTime()));
                Object innerObj = ((Stringifier) obj).getObject("object");
                if (innerObj != null) {
                    //logger.info("Found bean in treeCache " + innerObj.getClass().getName() + "(" + id + ")");
                }
                return innerObj;
            } else
                return null;
        } catch (LockingException e) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e);
        } catch (TimeoutException e1) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e1);
        } catch (org.jboss.cache.CacheException e2) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e2);
        }
    }

    public synchronized Object get(Fqn fqn, Object id) throws BasicCacheException {
        return get(fqn, id, expiryInMin);
    }

    /**
     * Dump some basic client side stats for a node
     */
    public String dumpStats(Fqn fqn) throws BasicCacheException {
        if (isCachingDisabled())
            return "";
        StringBuffer sb = new StringBuffer();

        sb.append("fqn: " + fqn.toString());
        sb.append("traffic: " + totalTraffic + "\n");
        sb.append("hits: " + cacheHits + "\n");
        if (totalTraffic != 0)
            sb.append(
                    "efficiency: " + new Float((((float) cacheHits / (float) totalTraffic)) * (float) 100) + "\n");

        int items = 0;
        //long mapSize = 0;
        try {
            Node node = treeCache.get(fqn);
            if (node != null) {
                Map map = node.getData();
                //mapSize = getMapSize(map);
                items = map.size();
            }
            sb.append("items: " + items + "\n");
            //sb.append("size: " + mapSize + "\n");
        } catch (Exception e) {
            logger.error("Could not get cache stats ", e);
        }

        return sb.toString();
    }

    /**
     * Get the summation of size of each object in this map.
     * This is an expensive method. For now, no choice, as per JBoss
     * support's response!
     *
     * @param map the map to scan for serialization
     * @return long return a really cute long
     * @exception BasicCacheException if something goes awry
     */
    private synchronized long getMapSize(Map map) throws BasicCacheException {
        Iterator iter = map.keySet().iterator();
        long l = 0;
        while (iter.hasNext()) {
            l += objUtil.objectSize(map.get(iter.next()));
        }
        return l;
    }

    /**
     * Get a clone instead of the object reference from the cache.
     * This method usually used by tools programmers who manipulate
     * the bean directly that they get from cache, inside the tool.
     *
     * @param fqn the fqn of the node
     * @param id the id of the object
     * @return Object get the object clone
     * @exception BasicCacheException
     */
    public synchronized Object getClone(Fqn fqn, Object id) throws BasicCacheException {
        return objUtil.clone(get(fqn, id));
    }

    /**
     * Peek the data corresponding to this id. The
     * id will be used to identify the object.<p>
     *
     * CacheMan tools that need to use this method instead of the regular
     * get method.<p>
     *
     * The client must cast the returned Object with the required
     * type.
     *
     * This method is potentially called by multiple threads.
     *
     * @param fqn the fqn of the node
     * @param id id must contain exact asset id
     * @exception BasicCacheException run time exception in case there is a problem
     * @return Object returns the asset object
     */
    public synchronized Object peek(Fqn fqn, Object id) throws BasicCacheException {
        if (isCachingDisabled())
            return null;
        try {
            return treeCache.peek(fqn, id);
        } catch (LockingException e) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e);
        } catch (TimeoutException e1) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e1);
        } catch (org.jboss.cache.CacheException e2) {
            throw new BasicCacheException("Exception occurred while getting content from cache. id = " + id, e2);
        }
    }

    /**
     * This property is set by the spring framework.
     *
     * @param objUtil the object util
     */
    public void setObjUtil(ObjectUtil objUtil) {
        this.objUtil = objUtil;
    }

    /**
     * This property is set by the spring framework.
     * Set the max items per leaf Node.
     * If this limit is reached, additional caching will
     * behave as if it's disabled.
     *
     * @param maxItems the cache size
     */
    public void setMaxItems(Integer maxItems) {
        this.maxItems = maxItems.intValue();
    }

    /**
     * This property is set by the spring framework.
     * When maxItems is reached, sweep this # sweepItems oldest items
     * from the end of queue. Oldest items are by age of access.
     * The logic will sweep the minimum of currentSize and sweepItems,
     * that is whichever is lesser.
     *
     * @param sweepItems the number of items to sweep
     */
    public void setSweepItems(Integer sweepItems) {
        this.sweepItems = sweepItems.intValue();
    }

    /**
     * This is a JBoss config property.
     *
     * @param cacheMode The cacheMode defaults to <code>TreeCache.LOCAL</code>
     */
    public void setCacheMode(String cacheMode) {
        this.cacheMode = cacheMode;
    }

    /**
     * This is a JBoss config property.
     *
     * @param isolationLevel The isolationLevel defaults to <code>IsolationLevel.NONE</code>
     */
    public void setIsolationLevel(String isolationLevel) {
        this.isolationLevel = isolationLevel;
    }

    /**
     * The cluster name of the Jboss clients who would like to share
     * a single distributed cache.
     *
     * @param clusterName The cluster name used by the application
     */
    public void setClusterName(String clusterName) {
        this.clusterName = clusterName;
    }

    /**
     * The jboss config file usually in the WEB-INF/classes directory.
     *
     * @param configFile The config file used by the application
     */
    public void setConfigFile(String configFile) {
        this.configFile = configFile;
    }

    /**
     * This property is set by the spring framework.
     *
     * @param cacheUtil the cache util object.
     */
    public void setCacheUtil(CacheApi cacheUtil) {
        this.cacheUtil = cacheUtil;
    }

    /**
     * This property is set by the spring framework.
     *
     * @param mathUtil the math util
     */
    public void setMathUtil(BasicMathUtil mathUtil) {
        this.mathUtil = mathUtil;
    }

    /**
     * This property is set by the spring framework.
     * Set the cache size in Kb for each leaf Node.
     * If this limit is reached, additional caching will
     * behave as if it's disabled.
     *
     * @param cacheSize the cache size
     */
    public void setCacheSize(Integer cacheSize) {
        this.cacheSize = cacheSize.intValue();
    }

    /**
    * This method can be called by spring. Use it to set the
    * reference to the HeapStrategy bean.
    *
    * @param heapStrategy
    */
    public void setHeapStrategy(HeapStrategy heapStrategy) {
        this.eopEnabled = heapStrategy.getEopEnabled();
        logger.warn("It is " + isCachingDisabled() + " that the JBoss caching is disabled!");
    }

    /**
     * The Daos can call this method to find out if the cache is disabled.
     *
     * @return boolean
     */
    public boolean isCachingDisabled() {
        if (eopEnabled)
            return true;
        else
            return cachingDisabled;
    }

    /**
     * This property is set by spring.
     *
     * @param cachingDisabled
     */
    public void setCachingDisabled(boolean cachingDisabled) {
        this.cachingDisabled = cachingDisabled;
    }

    /**
      * This property is set by the spring framework.
      *
      * @param expiryInMin expiry of cached objects in minutes
      */
    public void setExpiryInMin(long expiryInMin) {
        this.expiryInMin = expiryInMin;
    }
}