/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright 2006 - 2009 Pentaho Corporation. All rights reserved. * * * Created April 6, 2006 * Updated July, 11, 2008 * * @author mbatchel */ package org.pentaho.platform.plugin.services.cache; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Element; import org.hibernate.cache.Cache; import org.hibernate.cache.CacheException; import org.hibernate.cache.CacheProvider; import org.pentaho.platform.api.engine.ICacheManager; import org.pentaho.platform.api.engine.IPentahoSession; import org.pentaho.platform.api.engine.ISystemSettings; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.services.messages.Messages; import org.pentaho.platform.util.xml.dom4j.XmlDom4JHelper; /** * This class provides an access point for pluggable caching mechanisms. Right * now, it only supports the caching mechanisms implemented in * <code>org.hibernate.cache</code>. * <p> * To use the cache manager, you need to include the following information in * your <code>pentaho.xml</code>. * * <pre> * * <cache-provider> * <class>org.hibernate.cache.xxxxxxxx</class> * <region>pentahoCache</region> * <properties> * <property name="someProperty">someValue</property> * </properties> * </cache-provider> * </pre> * * <p> * The specified class must implement the * <code>org.hibernate.cache.CacheProvider</code> interface. * <p> * Each implementation of the <code>org.hibernate.cache.CacheProvider</code> * has slightly different requirements with respect to the required input * parameters - so, please see the classes in that package for more information * (available from the Sourceforge Hibernate project). Also, some cache * providers (notably the <code>org.hibernate.cache.EhCacheProvider</code>) * completely ignore the passed in properties, and only configure themselves by * locating a configuration file (e.g. ehcache.xml) on the classpath. * * <p> * The cache manager supports session-based caching (that is, caching of data * that is user-specific) as well as global-based caching (that is, caching of * data that is system-wide). To differentiate between session-based and * global-based caching, there are different methods that get called depending * upon the storage type. * * <p> * Data that is cached for user sessions require an <code>IPentahoSession</code> * object to be passed in. The cache manager uses the * <code>IPentahoSession.getId()</code> to classify saved objects underneath a * specific user session. No information is actually stored in the user session * object. For an example of this, see <code><br> * putInSessionCache(IPentahoSession session, String key, Object value)</code> * <p> * Data that is server-wide (i.e. global) uses different methods for * storage/retrieval/management. For an example of this, see <code><br> * getFromGlobalCache(Object key)</code> * <p> * <b>Example Usage:</b> * <p> * * <pre> * String globalCachable = "String to cache"; * String globalCacheKey = "StringKey"; * CacheManager cacheManager = PentahoSystem.getCacheManager(); * cacheManager.putInGlobalCache(globalCacheKey, globalCachable); * </pre> * * <p> * <b>Important Considerations</b> * <ul> * <li>Most caches require objects that go into the cache <i> as well as their * respective object key</i> implement Serializable. It is a safe assumption * that both the Key and the Value should implement Serializable.</li> * <li>Some caches are read-only. Other caches are read-write. What does this * mean? It means that once you put an object in the cache, you can't put an * object into the cache with the same key. You'd need to remove it first</li> * * </ul> * * <p> * * @see org.hibernate.cache.CacheProvider * @see org.hibernate.cache.Cache * * @author mbatchel * */ public class CacheManager implements ICacheManager { protected static final Log logger = LogFactory.getLog(CacheManager.class); // ~ Instance Fields ====================================================== private CacheProvider cacheProvider; private Map<String, Cache> regionCache; private String cacheProviderClassName; private boolean cacheEnabled; private final Properties cacheProperties = new Properties(); // ~ Constructors ========================================================= /** * The constructor performs the following tasks: * <p> * <ul> * <li>Gets the Pentaho System Settings</li> * <li>Reads the <code>cache-provider/class</code> element.</li> * <li>Reads the <code>cache-provider/region</code> element.</li> * <li>Reads in any properties under <code>cache-provider/properties/*</li> * <li>Attempts to instance the cache provider classes specified</li> * <li>Starts the cache (see the <code>org.hibernate.cache.CacheProvider</code> interface)</li> * <li>Calls the buildCache method (see the <code>org.hibernate.cache.CacheProvider</code> interface)</li> * </ul> * <p> * */ public CacheManager() { ISystemSettings settings = PentahoSystem.getSystemSettings(); String s = System.getProperty("java.io.tmpdir"); //$NON-NLS-1$ char c = s.charAt(s.length() - 1); if ((c != '/') && (c != '\\')) { System.setProperty("java.io.tmpdir", s + "/"); //$NON-NLS-1$//$NON-NLS-2$ } if(settings != null) { cacheProviderClassName = settings.getSystemSetting("cache-provider/class", null); //$NON-NLS-1$ if (cacheProviderClassName != null) { Properties cacheProperties = getCacheProperties(settings); setupCacheProvider(cacheProperties); this.cacheEnabled = true; } } PentahoSystem.addLogoutListener(this); } protected void setupCacheProvider(Properties cacheProperties) { Object obj = PentahoSystem.createObject(cacheProviderClassName); if (null != obj) { if (obj instanceof CacheProvider) { this.cacheProvider = (CacheProvider) obj; cacheProvider.start(cacheProperties); regionCache = new HashMap<String, Cache>(); Cache cache = getCacheProvider().buildCache(SESSION, cacheProperties); if (cache == null) { CacheManager.logger.error(Messages.getInstance().getString("CacheManager.ERROR_0005_UNABLE_TO_BUILD_CACHE")); //$NON-NLS-1$ } else { regionCache.put(SESSION, cache); } cache = getCacheProvider().buildCache(GLOBAL, cacheProperties); if (cache == null) { CacheManager.logger.error(Messages.getInstance().getString("CacheManager.ERROR_0005_UNABLE_TO_BUILD_CACHE")); //$NON-NLS-1$ } else { regionCache.put(GLOBAL, cache); } } else { CacheManager.logger.error(Messages.getInstance().getString("CacheManager.ERROR_0002_NOT_INSTANCE_OF_CACHE_PROVIDER")); //$NON-NLS-1$ } } } public void cacheStop() { if (cacheEnabled) { regionCache.clear(); cacheProvider.stop(); } } /** * Returns the underlying cache provider (implements * <code>org.hibernate.cache.CacheProvider</code> * * @return cacheProvider. */ protected CacheProvider getCacheProvider() { return cacheProvider; } /** * Populates the properties object from the pentaho.xml * * @param settings * The Pentaho ISystemSettings object */ private Properties getCacheProperties(final ISystemSettings settings) { Properties cacheProperties = new Properties(); List propertySettings = settings.getSystemSettings("cache-provider/properties/*"); //$NON-NLS-1$ for (int i = 0; i < propertySettings.size(); i++) { Object obj = propertySettings.get(i); Element someProperty = (Element) obj; String propertyName = XmlDom4JHelper.getNodeText("@name", someProperty, null); //$NON-NLS-1$ if (propertyName != null) { String propertyValue = someProperty.getTextTrim(); if (propertyValue != null) { cacheProperties.put(propertyName, propertyValue); } } } return cacheProperties; } public boolean cacheEnabled(String region) { Cache cache = regionCache.get(region); if (cache == null) { return false; } return true; } public void onLogout(final IPentahoSession session) { removeRegionCache(session.getName()); } public boolean addCacheRegion(String region, Properties cacheProperties) { boolean returnValue = false; if (cacheEnabled) { if(!cacheEnabled(region)) { Cache cache = getCacheProvider().buildCache(region, cacheProperties); if (cache == null) { CacheManager.logger.error(Messages.getInstance().getString("CacheManager.ERROR_0005_UNABLE_TO_BUILD_CACHE")); //$NON-NLS-1$ } else { regionCache.put(region, cache); returnValue = true; } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0002_REGION_ALREADY_EXIST", region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return returnValue; } public boolean addCacheRegion(String region) { boolean returnValue = false; if (cacheEnabled) { if(!cacheEnabled(region)) { Cache cache = getCacheProvider().buildCache(region, null); if (cache == null) { CacheManager.logger.error(Messages.getInstance().getString("CacheManager.ERROR_0005_UNABLE_TO_BUILD_CACHE")); //$NON-NLS-1$ } else { regionCache.put(region, cache); returnValue = true; } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0002_REGION_ALREADY_EXIST", region)); //$NON-NLS-1$ returnValue = true; } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return returnValue; } public void clearRegionCache(String region) { if (cacheEnabled) { Cache cache = regionCache.get(region); if(cache != null) { cache.clear(); } else { CacheManager.logger.info(Messages.getInstance().getString("CacheManager.INFO_0001_CACHE_DOES_NOT_EXIST", region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } } public void removeRegionCache(String region) { if (cacheEnabled) { if(cacheEnabled(region)) { clearRegionCache(region); } else { CacheManager.logger.info(Messages.getInstance().getString("CacheManager.INFO_0001_CACHE_DOES_NOT_EXIST",region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } } public void putInRegionCache(String region, Object key, Object value) { if (cacheEnabled) { if (cacheEnabled(region)) { Cache cache = regionCache.get(region); cache.put(key, value); } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0003_REGION_DOES_NOT_EXIST",region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } } public Object getFromRegionCache(String region, Object key) { Object returnValue = null; if (cacheEnabled) { Cache cache = regionCache.get(region); if (cacheEnabled(region)) { returnValue = cache.get(key); } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0003_REGION_DOES_NOT_EXIST",region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return returnValue; } public List getAllValuesFromRegionCache(String region) { List list = new ArrayList<Object>(); if (cacheEnabled) { Cache cache = regionCache.get(region); if (cacheEnabled(region)) { Map cacheMap = cache.toMap(); if (cacheMap != null) { Iterator it = cacheMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); list.add(entry.getValue()); } } } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return list; } public Set getAllKeysFromRegionCache(String region) { Set set = null; if (cacheEnabled) { Cache cache = regionCache.get(region); if (cacheEnabled(region)) { Map cacheMap = cache.toMap(); if (cacheMap != null) { set = cacheMap.keySet(); } } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return set; } public Set getAllEntriesFromRegionCache(String region) { Set set = null; if (cacheEnabled) { Cache cache = regionCache.get(region); if (cacheEnabled(region)) { Map cacheMap = cache.toMap(); if (cacheMap != null) { set = cacheMap.entrySet(); } } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } return set; } public void removeFromRegionCache(String region, Object key) { if (cacheEnabled) { Cache cache = regionCache.get(region); if (cacheEnabled(region)) { cache.remove(key); } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0003_REGION_DOES_NOT_EXIST", region)); //$NON-NLS-1$ } } else { CacheManager.logger.warn(Messages.getInstance().getString("CacheManager.WARN_0001_CACHE_NOT_ENABLED")); //$NON-NLS-1$ } } public boolean cacheEnabled() { return cacheEnabled; } public void clearCache() { if(cacheEnabled) { Iterator it = regionCache.entrySet().iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String key = (entry.getKey()!= null) ? entry.getKey().toString(): ""; //$NON-NLS-1$ if(key != null) { Cache cache = regionCache.get(key); cache.clear(); } } } } public Object getFromGlobalCache(Object key) { return getFromRegionCache(GLOBAL, key); } public Object getFromSessionCache(IPentahoSession session, String key) { return getFromRegionCache(SESSION, getCorrectedKey(session, key)); } public void killSessionCache(IPentahoSession session) { if(cacheEnabled) { Cache cache = regionCache.get(SESSION); if(cache != null) { Map cacheMap = cache.toMap(); if (cacheMap != null) { Set set = cacheMap.keySet(); Iterator it = set.iterator(); while (it.hasNext()) { String key = (String) it.next(); if(key.indexOf(session.getId()) >= 0) { cache.remove(key); } } } } } } public void killSessionCaches() { removeRegionCache(SESSION); } public void putInGlobalCache(Object key, Object value) { putInRegionCache(GLOBAL, key, value); } public void putInSessionCache(IPentahoSession session, String key, Object value) { putInRegionCache(SESSION, getCorrectedKey(session, key), value); } public void removeFromGlobalCache(Object key) { removeFromRegionCache(GLOBAL, key); } public void removeFromSessionCache(IPentahoSession session, String key) { removeFromRegionCache(SESSION, getCorrectedKey(session, key)); } private String getCorrectedKey(final IPentahoSession session, final String key) { String sessionId = session.getId(); if (sessionId != null) { String newKey = sessionId + "\t" + key; //$NON-NLS-1$ return newKey; } else { throw new CacheException(Messages.getInstance().getErrorString("CacheManager.ERROR_0001_NOSESSION")); //$NON-NLS-1$ } } }