CacheService.java
- /*
- * Copyright (c) 2002-2022, City of Paris
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice
- * and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice
- * and the following disclaimer in the documentation and/or other materials
- * provided with the distribution.
- *
- * 3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- *
- * License 1.0
- */
- package fr.paris.lutece.portal.service.cache;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.lang.management.ManagementFactory;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Properties;
- import javax.management.MBeanServer;
- import fr.paris.lutece.portal.service.datastore.DatastoreService;
- import fr.paris.lutece.portal.service.util.AppLogService;
- import fr.paris.lutece.portal.service.util.AppPathService;
- import fr.paris.lutece.portal.service.util.AppPropertiesService;
- import net.sf.ehcache.Cache;
- import net.sf.ehcache.CacheManager;
- import net.sf.ehcache.config.CacheConfiguration;
- import net.sf.ehcache.config.Configuration;
- import net.sf.ehcache.config.ConfigurationFactory;
- import net.sf.ehcache.management.ManagementService;
- /**
- * Provides cache object for cacheable services
- */
- public final class CacheService
- {
- private static final String ERROR_NUMERIC_PROP = "Invalid numeric property : {} {} = {}";
- private static final String PROPERTY_PATH_CONF = "path.conf";
- private static final String PROPERTY_IS_ENABLED = ".enabled";
- private static final String FILE_CACHES_STATUS = "caches.dat";
- // Cache configuration properties
- private static final String PROPERTY_MAX_ELEMENTS = ".maxElementsInMemory";
- private static final String PROPERTY_ETERNAL = ".eternal";
- private static final String PROPERTY_TIME_TO_IDLE = ".timeToIdleSeconds";
- private static final String PROPERTY_TIME_TO_LIVE = ".timeToLiveSeconds";
- private static final String PROPERTY_OVERFLOW_TO_DISK = ".overflowToDisk";
- private static final String PROPERTY_DISK_PERSISTENT = ".diskPersistent";
- private static final String PROPERTY_DISK_EXPIRY = ".diskExpiryThreadIntervalSeconds";
- private static final String PROPERTY_MAX_ELEMENTS_DISK = ".maxElementsOnDisk";
- private static final String PROPERTY_STATISTICS = ".statistics";
- // Datastore
- private static final String KEY_PREFIX = "core.cache.status.";
- // JMX monitoring properties
- private static final String PROPERTY_JMX_MONITORING = "lutece.cache.jmx.monitoring.enabled";
- private static final String PROPERTY_MONITOR_CACHE_MANAGER = "lutece.cache.jmx.monitorCacheManager";
- private static final String PROPERTY_MONITOR_CACHES = "lutece.cache.jmx.monitorCaches";
- private static final String PROPERTY_MONITOR_CACHE_CONFIGURATIONS = "lutece.cache.jmx.monitorCacheConfiguration";
- private static final String PROPERTY_MONITOR_CACHE_STATISTICS = "lutece.cache.jmx.monitorCacheStatistics";
- private static final String FALSE = "false";
- private static final String TRUE = "true";
- private static final String ENABLED = "1";
- private static final String DISABLED = "0";
- private static final String NOT_FOUND = "NOT FOUND";
- private static final String PREFIX_DEFAULT = "lutece.cache.default";
- private static final String LUTECE_CACHEMANAGER_NAME = "LuteceCacheManager";
- private static CacheService _singleton;
- private static CacheManager _manager;
- private static List<CacheableService> _listCacheableServicesRegistry = new ArrayList<>( );
- private int _nDefaultMaxElementsInMemory;
- private boolean _bDefaultEternal;
- private long _lDefaultTimeToIdle;
- private long _lDefaultTimeToLive;
- private boolean _bDefaultOverflowToDisk;
- private boolean _bDefaultDiskPersistent;
- private long _lDefaultDiskExpiry;
- private int _nDefaultMaxElementsOnDisk;
- private boolean _bDefaultStatistics;
- /**
- * Creates a new instance of CacheService
- */
- private CacheService( )
- {
- }
- /**
- * Gets the unique instance of the CacheService
- *
- * @return The unique instance of the CacheService
- */
- public static synchronized CacheService getInstance( )
- {
- if ( _singleton == null )
- {
- _singleton = new CacheService( );
- _singleton.init( );
- Configuration configuration = ConfigurationFactory.parseConfiguration( );
- configuration.setName( LUTECE_CACHEMANAGER_NAME );
- _manager = CacheManager.create( configuration );
- }
- return _singleton;
- }
- /**
- * Itializes the service by creating a manager object with a given configuration file.
- */
- private void init( )
- {
- loadDefaults( );
- loadCachesConfig( );
- boolean bJmxMonitoring = AppPropertiesService.getProperty( PROPERTY_JMX_MONITORING, FALSE ).equals( TRUE );
- if ( bJmxMonitoring )
- {
- initJmxMonitoring( );
- }
- }
- /**
- * Init JMX monitoring configuration
- */
- private void initJmxMonitoring( )
- {
- MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer( );
- boolean bRegisterCacheManager = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_MANAGER, FALSE ).equals( TRUE );
- boolean bRegisterCaches = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHES, FALSE ).equals( TRUE );
- boolean bRegisterCacheConfigurations = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_CONFIGURATIONS, FALSE ).equals( TRUE );
- boolean bRegisterCacheStatistics = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_STATISTICS, FALSE ).equals( TRUE );
- ManagementService.registerMBeans( _manager, mBeanServer, bRegisterCacheManager, bRegisterCaches, bRegisterCacheConfigurations,
- bRegisterCacheStatistics );
- }
- /**
- * Create a cache for a given Service
- *
- * @param strCacheName
- * The Cache/Service name
- * @return A cache object
- */
- public Cache createCache( String strCacheName )
- {
- Cache cache = new Cache( getCacheConfiguration( strCacheName ) );
- _manager.addCache( cache );
- return _manager.getCache( strCacheName );
- }
- /**
- * Reset all caches
- */
- public static void resetCaches( )
- {
- // Reset cache
- for ( CacheableService cs : _listCacheableServicesRegistry )
- {
- cs.resetCache( );
- }
- }
- /**
- * Shutdown the cache service and the cache manager. Should be called when the webapp is stopped.
- */
- public void shutdown( )
- {
- CacheService.storeCachesStatus( );
- _manager.shutdown( );
- }
- /**
- * Registers a new CacheableService
- *
- * @param cs
- * The CacheableService
- */
- public static void registerCacheableService( CacheableService cs )
- {
- _listCacheableServicesRegistry.add( cs );
- // read cache status from file "caches.dat"
- cs.enableCache( getStatus( cs ) );
- }
- /**
- * Returns all registered Cacheable services
- *
- * @return A collection containing all registered Cacheable services
- */
- public static List<CacheableService> getCacheableServicesList( )
- {
- return _listCacheableServicesRegistry;
- }
- /**
- * Stores cache status
- */
- public static void storeCachesStatus( )
- {
- for ( CacheableService cs : _listCacheableServicesRegistry )
- {
- String strKey = getDSKey( cs.getName( ), PROPERTY_IS_ENABLED );
- DatastoreService.setInstanceDataValue( strKey, cs.isCacheEnable( ) ? ENABLED : DISABLED );
- }
- }
- /**
- * Returns cache config
- *
- * @param cache
- * The cache
- * @return Cache infos
- */
- static String getInfos( Cache cache )
- {
- StringBuilder sbInfos = new StringBuilder( );
- sbInfos.append( PROPERTY_MAX_ELEMENTS ).append( "=" ).append( cache.getCacheConfiguration( ).getMaxElementsInMemory( ) ).append( "\n" );
- sbInfos.append( PROPERTY_ETERNAL ).append( "=" ).append( cache.getCacheConfiguration( ).isEternal( ) ).append( "\n" );
- sbInfos.append( PROPERTY_TIME_TO_IDLE ).append( "=" ).append( cache.getCacheConfiguration( ).getTimeToIdleSeconds( ) ).append( "\n" );
- sbInfos.append( PROPERTY_TIME_TO_LIVE ).append( "=" ).append( cache.getCacheConfiguration( ).getTimeToLiveSeconds( ) ).append( "\n" );
- sbInfos.append( PROPERTY_OVERFLOW_TO_DISK ).append( "=" ).append( cache.getCacheConfiguration( ).isOverflowToDisk( ) ).append( "\n" );
- sbInfos.append( PROPERTY_DISK_PERSISTENT ).append( "=" ).append( cache.getCacheConfiguration( ).isDiskPersistent( ) ).append( "\n" );
- sbInfos.append( PROPERTY_DISK_EXPIRY ).append( "=" ).append( cache.getCacheConfiguration( ).getDiskExpiryThreadIntervalSeconds( ) ).append( "\n" );
- sbInfos.append( PROPERTY_MAX_ELEMENTS_DISK ).append( "=" ).append( cache.getCacheConfiguration( ).getMaxElementsOnDisk( ) ).append( "\n" );
- sbInfos.append( PROPERTY_STATISTICS ).append( '=' ).append( cache.getCacheConfiguration( ).getStatistics( ) ).append( "\n" );
- return sbInfos.toString( );
- }
- /**
- * Load defaults configuration parameters
- */
- private void loadDefaults( )
- {
- _nDefaultMaxElementsInMemory = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS, 10000 );
- _bDefaultEternal = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_ETERNAL, false );
- _lDefaultTimeToIdle = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_IDLE, 10000L );
- _lDefaultTimeToLive = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_LIVE, 10000L );
- _bDefaultOverflowToDisk = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_OVERFLOW_TO_DISK, true );
- _bDefaultDiskPersistent = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_DISK_PERSISTENT, true );
- _lDefaultDiskExpiry = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_DISK_EXPIRY, 120L );
- _nDefaultMaxElementsOnDisk = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS_DISK, 10000 );
- _bDefaultStatistics = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_STATISTICS, false );
- }
- /**
- * Load caches status
- */
- private void loadCachesConfig( )
- {
- String strCachesStatusFile = AppPathService.getPath( PROPERTY_PATH_CONF, FILE_CACHES_STATUS );
- File file = new File( strCachesStatusFile );
- try ( FileInputStream fis = new FileInputStream( file ) )
- {
- Properties properties = new Properties( );
- properties.load( fis );
- // If the keys aren't found in the datastore then create a key in it
- for ( String strKey : properties.stringPropertyNames( ) )
- {
- String strDSKey = KEY_PREFIX + strKey;
- if ( !DatastoreService.existsInstanceKey( strDSKey ) )
- {
- String strValue = properties.getProperty( strKey );
- DatastoreService.setInstanceDataValue( strDSKey, strValue );
- }
- }
- }
- catch( FileNotFoundException e )
- {
- AppLogService.error( "No cache.dat file. Should be created at shutdown." );
- }
- catch( Exception e )
- {
- AppLogService.error( "Error loading caches status defined in file : {}", file.getAbsolutePath( ), e );
- }
- }
- /**
- * Update cache status
- *
- * @param cs
- * Cacheable Service
- */
- public static void updateCacheStatus( CacheableService cs )
- {
- String strKey = getDSKey( cs.getName( ), PROPERTY_IS_ENABLED );
- DatastoreService.setInstanceDataValue( strKey, ( cs.isCacheEnable( ) ? ENABLED : DISABLED ) );
- }
- /**
- * Returns the cache status
- *
- * @param cs
- * The cacheable service
- * @return The status
- */
- private static boolean getStatus( CacheableService cs )
- {
- String strEnabled = DatastoreService.getInstanceDataValue( getDSKey( cs.getName( ), PROPERTY_IS_ENABLED ), DISABLED );
- return strEnabled.equals( ENABLED );
- }
- /**
- * Return the key of a datastore property
- *
- * @param strCacheName
- * The cacheable service
- * @param strProperty
- * The property
- * @return The DS key
- */
- private static String getDSKey( String strCacheName, String strProperty )
- {
- return KEY_PREFIX + normalizeName( strCacheName ) + strProperty;
- }
- /**
- * Normalize name (remove spaces)
- *
- * @param strName
- * The name to normalize
- * @return The normalized name
- */
- private static String normalizeName( String strName )
- {
- return strName.replace( " ", "" );
- }
- /**
- * Read cache config from the file caches.dat
- *
- * @param strCacheName
- * The cache name
- * @return The config
- */
- private CacheConfiguration getCacheConfiguration( String strCacheName )
- {
- CacheConfiguration config = new CacheConfiguration( );
- config.setName( strCacheName );
- config.setMaxElementsInMemory( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS, _nDefaultMaxElementsInMemory ) );
- config.setEternal( getBooleanProperty( strCacheName, PROPERTY_ETERNAL, _bDefaultEternal ) );
- config.setTimeToIdleSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_IDLE, _lDefaultTimeToIdle ) );
- config.setTimeToLiveSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_LIVE, _lDefaultTimeToLive ) );
- config.setOverflowToDisk( getBooleanProperty( strCacheName, PROPERTY_OVERFLOW_TO_DISK, _bDefaultOverflowToDisk ) );
- config.setDiskPersistent( getBooleanProperty( strCacheName, PROPERTY_DISK_PERSISTENT, _bDefaultDiskPersistent ) );
- config.setDiskExpiryThreadIntervalSeconds( getLongProperty( strCacheName, PROPERTY_DISK_EXPIRY, _lDefaultDiskExpiry ) );
- config.setMaxElementsOnDisk( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS_DISK, _nDefaultMaxElementsOnDisk ) );
- config.setStatistics( getBooleanProperty( strCacheName, PROPERTY_STATISTICS, _bDefaultStatistics ) );
- return config;
- }
- /**
- * Read an Integer property
- *
- * @param strCacheName
- * Property's prefix
- * @param strProperty
- * the key
- * @param nDefault
- * the default value
- * @return The property's value
- */
- private int getIntProperty( String strCacheName, String strProperty, int nDefault )
- {
- String strKey = getDSKey( strCacheName, strProperty );
- if ( DatastoreService.existsInstanceKey( strKey ) )
- {
- String strValue = NOT_FOUND;
- try
- {
- strValue = DatastoreService.getInstanceDataValue( strKey, strValue );
- return Integer.parseInt( strValue );
- }
- catch( NumberFormatException e )
- {
- AppLogService.error( ERROR_NUMERIC_PROP, strCacheName, strProperty, strValue, e );
- }
- }
- return nDefault;
- }
- /**
- * Read a Long property
- *
- * @param strCacheName
- * Property's prefix
- * @param strProperty
- * the key
- * @param lDefault
- * the default value
- * @return The property's value
- */
- private long getLongProperty( String strCacheName, String strProperty, long lDefault )
- {
- String strKey = getDSKey( strCacheName, strProperty );
- if ( DatastoreService.existsInstanceKey( strKey ) )
- {
- String strValue = NOT_FOUND;
- try
- {
- strValue = DatastoreService.getInstanceDataValue( strKey, strValue );
- return Integer.parseInt( strValue );
- }
- catch( NumberFormatException e )
- {
- AppLogService.error( ERROR_NUMERIC_PROP, strCacheName, strProperty, strValue, e );
- }
- }
- return lDefault;
- }
- /**
- * Read a Boolean property
- *
- * @param strCacheName
- * Property's prefix
- * @param strProperty
- * the key
- * @param bDefault
- * the default value
- * @return The property's value
- */
- private boolean getBooleanProperty( String strCacheName, String strProperty, boolean bDefault )
- {
- String strKey = getDSKey( strCacheName, strProperty );
- if ( DatastoreService.existsInstanceKey( strKey ) )
- {
- String strValue = DatastoreService.getInstanceDataValue( strKey, NOT_FOUND );
- return ( strValue.equalsIgnoreCase( TRUE ) );
- }
- return bDefault;
- }
- }