/*!
* 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 (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.plugin.services.cache;
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.cache.ICacheExpirationRegistry;
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;
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;
/**
* 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();
private ICacheExpirationRegistry cacheExpirationRegistry;
// ~ 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 );
cacheExpirationRegistry = PentahoSystem.get( ICacheExpirationRegistry.class, null );
if ( null != obj ) {
if ( obj instanceof CacheProvider ) {
this.cacheProvider = (CacheProvider) obj;
cacheProvider.start( cacheProperties );
regionCache = new HashMap<String, Cache>();
Cache cache = 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 = 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 = 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 = 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 ) {
try {
cache.clear();
} catch ( CacheException e ) {
CacheManager.logger.error( Messages.getInstance().getString(
"CacheManager.ERROR_0006_CACHE_EXCEPTION", e.getLocalizedMessage() ) ); //$NON-NLS-1$
}
} 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$
}
}
private LastModifiedCache buildCache( String key, Properties cacheProperties ) {
if ( getCacheProvider() != null ) {
Cache cache = getCacheProvider().buildCache( key, cacheProperties );
LastModifiedCache lmCache = new LastModifiedCache( cache );
if ( cacheExpirationRegistry != null ) {
cacheExpirationRegistry.register( lmCache );
} else {
logger.warn( Messages.getInstance().getErrorString( "CacheManager.WARN_0003_NO_CACHE_EXPIRATION_REGISTRY" ) );
}
return lmCache;
} else {
logger.error( Messages.getInstance().getErrorString( "CacheManager.ERROR_0004_CACHE_PROVIDER_NOT_AVAILABLE" ) );
return null;
}
}
@Override
public long getElementCountInRegionCache( String region ) {
if ( cacheEnabled ) {
Cache cache = regionCache.get( region );
if ( cache != null ) {
try {
long memCnt = cache.getElementCountInMemory();
long discCnt = cache.getElementCountOnDisk();
return memCnt + discCnt;
} catch ( Exception ignored ) {
return -1;
}
} else {
return -1;
}
} else {
return -1;
}
}
@Override
public long getElementCountInSessionCache() {
return getElementCountInRegionCache( SESSION );
}
@Override
public long getElementCountInGlobalCache() {
return getElementCountInRegionCache( GLOBAL );
}
}