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