CacheService.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of Paris
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *  1. Redistributions of source code must retain the above copyright notice
  10.  *     and the following disclaimer.
  11.  *
  12.  *  2. Redistributions in binary form must reproduce the above copyright notice
  13.  *     and the following disclaimer in the documentation and/or other materials
  14.  *     provided with the distribution.
  15.  *
  16.  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
  17.  *     contributors may be used to endorse or promote products derived from
  18.  *     this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * License 1.0
  33.  */
  34. package fr.paris.lutece.portal.service.cache;

  35. import java.io.File;
  36. import java.io.FileInputStream;
  37. import java.io.FileNotFoundException;
  38. import java.lang.management.ManagementFactory;
  39. import java.util.ArrayList;
  40. import java.util.List;
  41. import java.util.Properties;

  42. import javax.management.MBeanServer;

  43. import fr.paris.lutece.portal.service.datastore.DatastoreService;
  44. import fr.paris.lutece.portal.service.util.AppLogService;
  45. import fr.paris.lutece.portal.service.util.AppPathService;
  46. import fr.paris.lutece.portal.service.util.AppPropertiesService;
  47. import net.sf.ehcache.Cache;
  48. import net.sf.ehcache.CacheManager;
  49. import net.sf.ehcache.config.CacheConfiguration;
  50. import net.sf.ehcache.config.Configuration;
  51. import net.sf.ehcache.config.ConfigurationFactory;
  52. import net.sf.ehcache.management.ManagementService;

  53. /**
  54.  * Provides cache object for cacheable services
  55.  */
  56. public final class CacheService
  57. {
  58.     private static final String ERROR_NUMERIC_PROP = "Invalid numeric property : {} {} = {}";
  59.     private static final String PROPERTY_PATH_CONF = "path.conf";
  60.     private static final String PROPERTY_IS_ENABLED = ".enabled";
  61.     private static final String FILE_CACHES_STATUS = "caches.dat";

  62.     // Cache configuration properties
  63.     private static final String PROPERTY_MAX_ELEMENTS = ".maxElementsInMemory";
  64.     private static final String PROPERTY_ETERNAL = ".eternal";
  65.     private static final String PROPERTY_TIME_TO_IDLE = ".timeToIdleSeconds";
  66.     private static final String PROPERTY_TIME_TO_LIVE = ".timeToLiveSeconds";
  67.     private static final String PROPERTY_OVERFLOW_TO_DISK = ".overflowToDisk";
  68.     private static final String PROPERTY_DISK_PERSISTENT = ".diskPersistent";
  69.     private static final String PROPERTY_DISK_EXPIRY = ".diskExpiryThreadIntervalSeconds";
  70.     private static final String PROPERTY_MAX_ELEMENTS_DISK = ".maxElementsOnDisk";
  71.     private static final String PROPERTY_STATISTICS = ".statistics";

  72.     // Datastore
  73.     private static final String KEY_PREFIX = "core.cache.status.";

  74.     // JMX monitoring properties
  75.     private static final String PROPERTY_JMX_MONITORING = "lutece.cache.jmx.monitoring.enabled";
  76.     private static final String PROPERTY_MONITOR_CACHE_MANAGER = "lutece.cache.jmx.monitorCacheManager";
  77.     private static final String PROPERTY_MONITOR_CACHES = "lutece.cache.jmx.monitorCaches";
  78.     private static final String PROPERTY_MONITOR_CACHE_CONFIGURATIONS = "lutece.cache.jmx.monitorCacheConfiguration";
  79.     private static final String PROPERTY_MONITOR_CACHE_STATISTICS = "lutece.cache.jmx.monitorCacheStatistics";
  80.     private static final String FALSE = "false";
  81.     private static final String TRUE = "true";
  82.     private static final String ENABLED = "1";
  83.     private static final String DISABLED = "0";
  84.     private static final String NOT_FOUND = "NOT FOUND";
  85.     private static final String PREFIX_DEFAULT = "lutece.cache.default";
  86.     private static final String LUTECE_CACHEMANAGER_NAME = "LuteceCacheManager";
  87.     private static CacheService _singleton;
  88.     private static CacheManager _manager;

  89.     private static List<CacheableService> _listCacheableServicesRegistry = new ArrayList<>( );
  90.     private int _nDefaultMaxElementsInMemory;
  91.     private boolean _bDefaultEternal;
  92.     private long _lDefaultTimeToIdle;
  93.     private long _lDefaultTimeToLive;
  94.     private boolean _bDefaultOverflowToDisk;
  95.     private boolean _bDefaultDiskPersistent;
  96.     private long _lDefaultDiskExpiry;
  97.     private int _nDefaultMaxElementsOnDisk;
  98.     private boolean _bDefaultStatistics;

  99.     /**
  100.      * Creates a new instance of CacheService
  101.      */
  102.     private CacheService( )
  103.     {
  104.     }

  105.     /**
  106.      * Gets the unique instance of the CacheService
  107.      *
  108.      * @return The unique instance of the CacheService
  109.      */
  110.     public static synchronized CacheService getInstance( )
  111.     {
  112.         if ( _singleton == null )
  113.         {
  114.             _singleton = new CacheService( );
  115.             _singleton.init( );
  116.             Configuration configuration = ConfigurationFactory.parseConfiguration( );
  117.             configuration.setName( LUTECE_CACHEMANAGER_NAME );
  118.             _manager = CacheManager.create( configuration );
  119.         }

  120.         return _singleton;
  121.     }

  122.     /**
  123.      * Itializes the service by creating a manager object with a given configuration file.
  124.      */
  125.     private void init( )
  126.     {
  127.         loadDefaults( );
  128.         loadCachesConfig( );

  129.         boolean bJmxMonitoring = AppPropertiesService.getProperty( PROPERTY_JMX_MONITORING, FALSE ).equals( TRUE );

  130.         if ( bJmxMonitoring )
  131.         {
  132.             initJmxMonitoring( );
  133.         }
  134.     }

  135.     /**
  136.      * Init JMX monitoring configuration
  137.      */
  138.     private void initJmxMonitoring( )
  139.     {
  140.         MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer( );

  141.         boolean bRegisterCacheManager = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_MANAGER, FALSE ).equals( TRUE );
  142.         boolean bRegisterCaches = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHES, FALSE ).equals( TRUE );
  143.         boolean bRegisterCacheConfigurations = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_CONFIGURATIONS, FALSE ).equals( TRUE );
  144.         boolean bRegisterCacheStatistics = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_STATISTICS, FALSE ).equals( TRUE );
  145.         ManagementService.registerMBeans( _manager, mBeanServer, bRegisterCacheManager, bRegisterCaches, bRegisterCacheConfigurations,
  146.                 bRegisterCacheStatistics );
  147.     }

  148.     /**
  149.      * Create a cache for a given Service
  150.      *
  151.      * @param strCacheName
  152.      *            The Cache/Service name
  153.      * @return A cache object
  154.      */
  155.     public Cache createCache( String strCacheName )
  156.     {
  157.         Cache cache = new Cache( getCacheConfiguration( strCacheName ) );
  158.         _manager.addCache( cache );

  159.         return _manager.getCache( strCacheName );
  160.     }

  161.     /**
  162.      * Reset all caches
  163.      */
  164.     public static void resetCaches( )
  165.     {
  166.         // Reset cache
  167.         for ( CacheableService cs : _listCacheableServicesRegistry )
  168.         {
  169.             cs.resetCache( );
  170.         }
  171.     }

  172.     /**
  173.      * Shutdown the cache service and the cache manager. Should be called when the webapp is stopped.
  174.      */
  175.     public void shutdown( )
  176.     {
  177.         CacheService.storeCachesStatus( );
  178.         _manager.shutdown( );
  179.     }

  180.     /**
  181.      * Registers a new CacheableService
  182.      *
  183.      * @param cs
  184.      *            The CacheableService
  185.      */
  186.     public static void registerCacheableService( CacheableService cs )
  187.     {
  188.         _listCacheableServicesRegistry.add( cs );

  189.         // read cache status from file "caches.dat"
  190.         cs.enableCache( getStatus( cs ) );
  191.     }

  192.     /**
  193.      * Returns all registered Cacheable services
  194.      *
  195.      * @return A collection containing all registered Cacheable services
  196.      */
  197.     public static List<CacheableService> getCacheableServicesList( )
  198.     {
  199.         return _listCacheableServicesRegistry;
  200.     }

  201.     /**
  202.      * Stores cache status
  203.      */
  204.     public static void storeCachesStatus( )
  205.     {
  206.         for ( CacheableService cs : _listCacheableServicesRegistry )
  207.         {
  208.             String strKey = getDSKey( cs.getName( ), PROPERTY_IS_ENABLED );
  209.             DatastoreService.setInstanceDataValue( strKey, cs.isCacheEnable( ) ? ENABLED : DISABLED );
  210.         }
  211.     }

  212.     /**
  213.      * Returns cache config
  214.      *
  215.      * @param cache
  216.      *            The cache
  217.      * @return Cache infos
  218.      */
  219.     static String getInfos( Cache cache )
  220.     {
  221.         StringBuilder sbInfos = new StringBuilder( );
  222.         sbInfos.append( PROPERTY_MAX_ELEMENTS ).append( "=" ).append( cache.getCacheConfiguration( ).getMaxElementsInMemory( ) ).append( "\n" );
  223.         sbInfos.append( PROPERTY_ETERNAL ).append( "=" ).append( cache.getCacheConfiguration( ).isEternal( ) ).append( "\n" );
  224.         sbInfos.append( PROPERTY_TIME_TO_IDLE ).append( "=" ).append( cache.getCacheConfiguration( ).getTimeToIdleSeconds( ) ).append( "\n" );
  225.         sbInfos.append( PROPERTY_TIME_TO_LIVE ).append( "=" ).append( cache.getCacheConfiguration( ).getTimeToLiveSeconds( ) ).append( "\n" );
  226.         sbInfos.append( PROPERTY_OVERFLOW_TO_DISK ).append( "=" ).append( cache.getCacheConfiguration( ).isOverflowToDisk( ) ).append( "\n" );
  227.         sbInfos.append( PROPERTY_DISK_PERSISTENT ).append( "=" ).append( cache.getCacheConfiguration( ).isDiskPersistent( ) ).append( "\n" );
  228.         sbInfos.append( PROPERTY_DISK_EXPIRY ).append( "=" ).append( cache.getCacheConfiguration( ).getDiskExpiryThreadIntervalSeconds( ) ).append( "\n" );
  229.         sbInfos.append( PROPERTY_MAX_ELEMENTS_DISK ).append( "=" ).append( cache.getCacheConfiguration( ).getMaxElementsOnDisk( ) ).append( "\n" );
  230.         sbInfos.append( PROPERTY_STATISTICS ).append( '=' ).append( cache.getCacheConfiguration( ).getStatistics( ) ).append( "\n" );

  231.         return sbInfos.toString( );
  232.     }

  233.     /**
  234.      * Load defaults configuration parameters
  235.      */
  236.     private void loadDefaults( )
  237.     {
  238.         _nDefaultMaxElementsInMemory = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS, 10000 );
  239.         _bDefaultEternal = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_ETERNAL, false );
  240.         _lDefaultTimeToIdle = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_IDLE, 10000L );
  241.         _lDefaultTimeToLive = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_LIVE, 10000L );
  242.         _bDefaultOverflowToDisk = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_OVERFLOW_TO_DISK, true );
  243.         _bDefaultDiskPersistent = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_DISK_PERSISTENT, true );
  244.         _lDefaultDiskExpiry = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_DISK_EXPIRY, 120L );
  245.         _nDefaultMaxElementsOnDisk = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS_DISK, 10000 );
  246.         _bDefaultStatistics = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_STATISTICS, false );
  247.     }

  248.     /**
  249.      * Load caches status
  250.      */
  251.     private void loadCachesConfig( )
  252.     {
  253.         String strCachesStatusFile = AppPathService.getPath( PROPERTY_PATH_CONF, FILE_CACHES_STATUS );
  254.         File file = new File( strCachesStatusFile );

  255.         try ( FileInputStream fis = new FileInputStream( file ) )
  256.         {
  257.             Properties properties = new Properties( );
  258.             properties.load( fis );

  259.             // If the keys aren't found in the datastore then create a key in it
  260.             for ( String strKey : properties.stringPropertyNames( ) )
  261.             {
  262.                 String strDSKey = KEY_PREFIX + strKey;

  263.                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
  264.                 {
  265.                     String strValue = properties.getProperty( strKey );
  266.                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
  267.                 }
  268.             }
  269.         }
  270.         catch( FileNotFoundException e )
  271.         {
  272.             AppLogService.error( "No cache.dat file. Should be created at shutdown." );
  273.         }
  274.         catch( Exception e )
  275.         {
  276.             AppLogService.error( "Error loading caches status defined in file : {}", file.getAbsolutePath( ), e );
  277.         }
  278.     }

  279.     /**
  280.      * Update cache status
  281.      *
  282.      * @param cs
  283.      *            Cacheable Service
  284.      */
  285.     public static void updateCacheStatus( CacheableService cs )
  286.     {
  287.         String strKey = getDSKey( cs.getName( ), PROPERTY_IS_ENABLED );
  288.         DatastoreService.setInstanceDataValue( strKey, ( cs.isCacheEnable( ) ? ENABLED : DISABLED ) );
  289.     }

  290.     /**
  291.      * Returns the cache status
  292.      *
  293.      * @param cs
  294.      *            The cacheable service
  295.      * @return The status
  296.      */
  297.     private static boolean getStatus( CacheableService cs )
  298.     {
  299.         String strEnabled = DatastoreService.getInstanceDataValue( getDSKey( cs.getName( ), PROPERTY_IS_ENABLED ), DISABLED );

  300.         return strEnabled.equals( ENABLED );
  301.     }

  302.     /**
  303.      * Return the key of a datastore property
  304.      *
  305.      * @param strCacheName
  306.      *            The cacheable service
  307.      * @param strProperty
  308.      *            The property
  309.      * @return The DS key
  310.      */
  311.     private static String getDSKey( String strCacheName, String strProperty )
  312.     {
  313.         return KEY_PREFIX + normalizeName( strCacheName ) + strProperty;
  314.     }

  315.     /**
  316.      * Normalize name (remove spaces)
  317.      *
  318.      * @param strName
  319.      *            The name to normalize
  320.      * @return The normalized name
  321.      */
  322.     private static String normalizeName( String strName )
  323.     {
  324.         return strName.replace( " ", "" );
  325.     }

  326.     /**
  327.      * Read cache config from the file caches.dat
  328.      *
  329.      * @param strCacheName
  330.      *            The cache name
  331.      * @return The config
  332.      */
  333.     private CacheConfiguration getCacheConfiguration( String strCacheName )
  334.     {
  335.         CacheConfiguration config = new CacheConfiguration( );
  336.         config.setName( strCacheName );
  337.         config.setMaxElementsInMemory( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS, _nDefaultMaxElementsInMemory ) );
  338.         config.setEternal( getBooleanProperty( strCacheName, PROPERTY_ETERNAL, _bDefaultEternal ) );
  339.         config.setTimeToIdleSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_IDLE, _lDefaultTimeToIdle ) );
  340.         config.setTimeToLiveSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_LIVE, _lDefaultTimeToLive ) );
  341.         config.setOverflowToDisk( getBooleanProperty( strCacheName, PROPERTY_OVERFLOW_TO_DISK, _bDefaultOverflowToDisk ) );
  342.         config.setDiskPersistent( getBooleanProperty( strCacheName, PROPERTY_DISK_PERSISTENT, _bDefaultDiskPersistent ) );
  343.         config.setDiskExpiryThreadIntervalSeconds( getLongProperty( strCacheName, PROPERTY_DISK_EXPIRY, _lDefaultDiskExpiry ) );
  344.         config.setMaxElementsOnDisk( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS_DISK, _nDefaultMaxElementsOnDisk ) );
  345.         config.setStatistics( getBooleanProperty( strCacheName, PROPERTY_STATISTICS, _bDefaultStatistics ) );

  346.         return config;
  347.     }

  348.     /**
  349.      * Read an Integer property
  350.      *
  351.      * @param strCacheName
  352.      *            Property's prefix
  353.      * @param strProperty
  354.      *            the key
  355.      * @param nDefault
  356.      *            the default value
  357.      * @return The property's value
  358.      */
  359.     private int getIntProperty( String strCacheName, String strProperty, int nDefault )
  360.     {
  361.         String strKey = getDSKey( strCacheName, strProperty );

  362.         if ( DatastoreService.existsInstanceKey( strKey ) )
  363.         {
  364.             String strValue = NOT_FOUND;

  365.             try
  366.             {
  367.                 strValue = DatastoreService.getInstanceDataValue( strKey, strValue );

  368.                 return Integer.parseInt( strValue );
  369.             }
  370.             catch( NumberFormatException e )
  371.             {
  372.                 AppLogService.error( ERROR_NUMERIC_PROP, strCacheName, strProperty, strValue, e );
  373.             }
  374.         }

  375.         return nDefault;
  376.     }

  377.     /**
  378.      * Read a Long property
  379.      *
  380.      * @param strCacheName
  381.      *            Property's prefix
  382.      * @param strProperty
  383.      *            the key
  384.      * @param lDefault
  385.      *            the default value
  386.      * @return The property's value
  387.      */
  388.     private long getLongProperty( String strCacheName, String strProperty, long lDefault )
  389.     {
  390.         String strKey = getDSKey( strCacheName, strProperty );

  391.         if ( DatastoreService.existsInstanceKey( strKey ) )
  392.         {
  393.             String strValue = NOT_FOUND;

  394.             try
  395.             {
  396.                 strValue = DatastoreService.getInstanceDataValue( strKey, strValue );

  397.                 return Integer.parseInt( strValue );
  398.             }
  399.             catch( NumberFormatException e )
  400.             {
  401.                 AppLogService.error( ERROR_NUMERIC_PROP, strCacheName, strProperty, strValue, e );
  402.             }
  403.         }

  404.         return lDefault;
  405.     }

  406.     /**
  407.      * Read a Boolean property
  408.      *
  409.      * @param strCacheName
  410.      *            Property's prefix
  411.      * @param strProperty
  412.      *            the key
  413.      * @param bDefault
  414.      *            the default value
  415.      * @return The property's value
  416.      */
  417.     private boolean getBooleanProperty( String strCacheName, String strProperty, boolean bDefault )
  418.     {
  419.         String strKey = getDSKey( strCacheName, strProperty );

  420.         if ( DatastoreService.existsInstanceKey( strKey ) )
  421.         {
  422.             String strValue = DatastoreService.getInstanceDataValue( strKey, NOT_FOUND );

  423.             return ( strValue.equalsIgnoreCase( TRUE ) );
  424.         }

  425.         return bDefault;
  426.     }
  427. }