View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de 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  
36  import fr.paris.lutece.portal.service.datastore.DatastoreService;
37  import fr.paris.lutece.portal.service.util.AppLogService;
38  import fr.paris.lutece.portal.service.util.AppPathService;
39  import fr.paris.lutece.portal.service.util.AppPropertiesService;
40  
41  import net.sf.ehcache.Cache;
42  import net.sf.ehcache.CacheManager;
43  import net.sf.ehcache.config.CacheConfiguration;
44  import net.sf.ehcache.management.ManagementService;
45  
46  import java.io.File;
47  import java.io.FileInputStream;
48  import java.io.FileNotFoundException;
49  import java.io.IOException;
50  
51  import java.lang.management.ManagementFactory;
52  
53  import java.util.ArrayList;
54  import java.util.List;
55  import java.util.Properties;
56  
57  import javax.management.MBeanServer;
58  
59  
60  /**
61   * Provides cache object for cacheable services
62   */
63  public final class CacheService
64  {
65      private static final String PROPERTY_PATH_CONF = "path.conf";
66      private static final String PROPERTY_IS_ENABLED = ".enabled";
67      private static final String FILE_CACHES_STATUS = "caches.dat";
68  
69      // Cache configuration properties
70      private static final String PROPERTY_MAX_ELEMENTS = ".maxElementsInMemory";
71      private static final String PROPERTY_ETERNAL = ".eternal";
72      private static final String PROPERTY_TIME_TO_IDLE = ".timeToIdleSeconds";
73      private static final String PROPERTY_TIME_TO_LIVE = ".timeToLiveSeconds";
74      private static final String PROPERTY_OVERFLOW_TO_DISK = ".overflowToDisk";
75      private static final String PROPERTY_DISK_PERSISTENT = ".diskPersistent";
76      private static final String PROPERTY_DISK_EXPIRY = ".diskExpiryThreadIntervalSeconds";
77      private static final String PROPERTY_MAX_ELEMENTS_DISK = ".maxElementsOnDisk";
78      private static final String PROPERTY_STATISTICS = ".statistics";
79  
80      // Datastore
81      private static final String KEY_PREFIX = "core.cache.status.";
82  
83      // JMX monitoring properties
84      private static final String PROPERTY_JMX_MONITORING = "lutece.cache.jmx.monitoring.enabled";
85      private static final String PROPERTY_MONITOR_CACHE_MANAGER = "lutece.cache.jmx.monitorCacheManager";
86      private static final String PROPERTY_MONITOR_CACHES = "lutece.cache.jmx.monitorCaches";
87      private static final String PROPERTY_MONITOR_CACHE_CONFIGURATIONS = "lutece.cache.jmx.monitorCacheConfiguration";
88      private static final String PROPERTY_MONITOR_CACHE_STATISTICS = "lutece.cache.jmx.monitorCacheStatistics";
89      private static final String FALSE = "false";
90      private static final String TRUE = "true";
91      private static final String ENABLED = "1";
92      private static final String DISABLED = "0";
93      private static final String NOT_FOUND = "NOT FOUND";
94      private static final String PREFIX_DEFAULT = "lutece.cache.default";
95      private static CacheService _singleton;
96      private static CacheManager _manager;
97  
98      //    private static Properties _propertiesCacheConfig;
99      // Cacheable Services registry
100     private static List<CacheableService> _listCacheableServicesRegistry = new ArrayList<CacheableService>(  );
101     private int _nDefaultMaxElementsInMemory;
102     private boolean _bDefaultEternal;
103     private long _lDefaultTimeToIdle;
104     private long _lDefaultTimeToLive;
105     private boolean _bDefaultOverflowToDisk;
106     private boolean _bDefaultDiskPersistent;
107     private long _lDefaultDiskExpiry;
108     private int _nDefaultMaxElementsOnDisk;
109     private boolean _bDefaultStatistics;
110 
111     /**
112      * Creates a new instance of CacheService
113      */
114     private CacheService(  )
115     {
116     }
117 
118     /**
119      * Gets the unique instance of the CacheService
120      *
121      * @return The unique instance of the CacheService
122      */
123     public static synchronized CacheService getInstance(  )
124     {
125         if ( _singleton == null )
126         {
127             _singleton = new CacheService(  );
128             _singleton.init(  );
129         }
130 
131         return _singleton;
132     }
133 
134     /**
135      * Itializes the service by creating a manager object with a given
136      * configuration file.
137      */
138     private void init(  )
139     {
140         _manager = CacheManager.create(  );
141         loadDefaults(  );
142         loadCachesConfig(  );
143 
144         boolean bJmxMonitoring = AppPropertiesService.getProperty( PROPERTY_JMX_MONITORING, FALSE ).equals( TRUE );
145 
146         if ( bJmxMonitoring )
147         {
148             initJmxMonitoring(  );
149         }
150     }
151 
152     /**
153      * Init JMX monitoring configuration
154      */
155     private void initJmxMonitoring(  )
156     {
157         MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(  );
158 
159         boolean bRegisterCacheManager = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_MANAGER, FALSE )
160                                                             .equals( TRUE );
161         boolean bRegisterCaches = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHES, FALSE ).equals( TRUE );
162         boolean bRegisterCacheConfigurations = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_CONFIGURATIONS,
163                 FALSE ).equals( TRUE );
164         boolean bRegisterCacheStatistics = AppPropertiesService.getProperty( PROPERTY_MONITOR_CACHE_STATISTICS, FALSE )
165                                                                .equals( TRUE );
166         ManagementService.registerMBeans( _manager, mBeanServer, bRegisterCacheManager, bRegisterCaches,
167             bRegisterCacheConfigurations, bRegisterCacheStatistics );
168     }
169 
170     /**
171      * Create a cache for a given Service
172      *
173      * @param strCacheName The Cache/Service name
174      * @return A cache object
175      */
176     public Cache createCache( String strCacheName )
177     {
178         Cache cache = new Cache( getCacheConfiguration( strCacheName ) );
179         _manager.addCache( cache );
180 
181         return _manager.getCache( strCacheName );
182     }
183 
184     /**
185      * Reset all caches
186      */
187     public static void resetCaches(  )
188     {
189         // Reset cache
190         for ( CacheableService cs : _listCacheableServicesRegistry )
191         {
192             cs.resetCache(  );
193         }
194     }
195 
196     /**
197      * Shutdown the cache service and the cache manager. Should be called when
198      * the webapp is stopped.
199      */
200     public void shutdown(  )
201     {
202         CacheService.storeCachesStatus(  );
203         _manager.shutdown(  );
204     }
205 
206     /**
207      * Registers a new CacheableService
208      *
209      * @deprecated use registerCacheableService( CacheableService cs )
210      * @param strName The name
211      * @param cs The CacheableService
212      */
213     @Deprecated
214     public static void registerCacheableService( String strName, CacheableService cs )
215     {
216         registerCacheableService( cs );
217     }
218 
219     /**
220      * Registers a new CacheableService
221      *
222      * @param cs The CacheableService
223      */
224     public static void registerCacheableService( CacheableService cs )
225     {
226         _listCacheableServicesRegistry.add( cs );
227 
228         // read cache status from file "caches.dat"
229         cs.enableCache( getStatus( cs ) );
230     }
231 
232     /**
233      * Returns all registered Cacheable services
234      *
235      * @return A collection containing all registered Cacheable services
236      */
237     public static List<CacheableService> getCacheableServicesList(  )
238     {
239         return _listCacheableServicesRegistry;
240     }
241 
242     /**
243      * Stores cache status
244      */
245     public static void storeCachesStatus(  )
246     {
247         for ( CacheableService cs : _listCacheableServicesRegistry )
248         {
249             String strKey = getDSKey( cs.getName(  ), PROPERTY_IS_ENABLED );
250             DatastoreService.setInstanceDataValue( strKey, cs.isCacheEnable(  ) ? ENABLED : DISABLED );
251         }
252     }
253 
254     /**
255      * Returns cache config
256      *
257      * @param cache The cache
258      * @return Cache infos
259      */
260     static String getInfos( Cache cache )
261     {
262         StringBuilder sbInfos = new StringBuilder(  );
263         sbInfos.append( PROPERTY_MAX_ELEMENTS ).append( "=" )
264                .append( cache.getCacheConfiguration(  ).getMaxElementsInMemory(  ) ).append( "\n" );
265         sbInfos.append( PROPERTY_ETERNAL ).append( "=" ).append( cache.getCacheConfiguration(  ).isEternal(  ) )
266                .append( "\n" );
267         sbInfos.append( PROPERTY_TIME_TO_IDLE ).append( "=" )
268                .append( cache.getCacheConfiguration(  ).getTimeToIdleSeconds(  ) ).append( "\n" );
269         sbInfos.append( PROPERTY_TIME_TO_LIVE ).append( "=" )
270                .append( cache.getCacheConfiguration(  ).getTimeToLiveSeconds(  ) ).append( "\n" );
271         sbInfos.append( PROPERTY_OVERFLOW_TO_DISK ).append( "=" )
272                .append( cache.getCacheConfiguration(  ).isOverflowToDisk(  ) ).append( "\n" );
273         sbInfos.append( PROPERTY_DISK_PERSISTENT ).append( "=" )
274                .append( cache.getCacheConfiguration(  ).isDiskPersistent(  ) ).append( "\n" );
275         sbInfos.append( PROPERTY_DISK_EXPIRY ).append( "=" )
276                .append( cache.getCacheConfiguration(  ).getDiskExpiryThreadIntervalSeconds(  ) ).append( "\n" );
277         sbInfos.append( PROPERTY_MAX_ELEMENTS_DISK ).append( "=" )
278                .append( cache.getCacheConfiguration(  ).getMaxElementsOnDisk(  ) ).append( "\n" );
279         sbInfos.append( PROPERTY_STATISTICS ).append( '=' ).append( cache.getCacheConfiguration(  ).getStatistics(  ) )
280                .append( "\n" );
281 
282         return sbInfos.toString(  );
283     }
284 
285     /**
286      * Load defaults configuration parameters
287      */
288     private void loadDefaults(  )
289     {
290         _nDefaultMaxElementsInMemory = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS,
291                 10000 );
292         _bDefaultEternal = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_ETERNAL, false );
293         _lDefaultTimeToIdle = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_IDLE, 10000L );
294         _lDefaultTimeToLive = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_TIME_TO_LIVE, 10000L );
295         _bDefaultOverflowToDisk = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_OVERFLOW_TO_DISK,
296                 true );
297         _bDefaultDiskPersistent = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_DISK_PERSISTENT,
298                 true );
299         _lDefaultDiskExpiry = AppPropertiesService.getPropertyLong( PREFIX_DEFAULT + PROPERTY_DISK_EXPIRY, 120L );
300         _nDefaultMaxElementsOnDisk = AppPropertiesService.getPropertyInt( PREFIX_DEFAULT + PROPERTY_MAX_ELEMENTS_DISK,
301                 10000 );
302         _bDefaultStatistics = AppPropertiesService.getPropertyBoolean( PREFIX_DEFAULT + PROPERTY_STATISTICS, false );
303     }
304 
305     /**
306      * Load caches status
307      */
308     private void loadCachesConfig(  )
309     {
310         String strCachesStatusFile = AppPathService.getPath( PROPERTY_PATH_CONF, FILE_CACHES_STATUS );
311         File file = new File( strCachesStatusFile );
312 
313         FileInputStream fis = null;
314 
315         try
316         {
317             Properties properties = new Properties(  );
318             fis = new FileInputStream( file );
319             properties.load( fis );
320 
321             // If the keys aren't found in the datastore then create a key in it
322             for ( String strKey : properties.stringPropertyNames(  ) )
323             {
324                 String strDSKey = KEY_PREFIX + strKey;
325 
326                 if ( !DatastoreService.existsInstanceKey( strDSKey ) )
327                 {
328                     String strValue = properties.getProperty( strKey );
329                     DatastoreService.setInstanceDataValue( strDSKey, strValue );
330                 }
331             }
332         }
333         catch ( FileNotFoundException e )
334         {
335             AppLogService.error( "No cache.dat file. Should be created at shutdown." );
336         }
337         catch ( Exception e )
338         {
339             AppLogService.error( "Error loading caches status defined in file : " + file.getAbsolutePath(  ), e );
340         }
341         finally
342         {
343             if ( fis != null )
344             {
345                 try
346                 {
347                     fis.close(  );
348                 }
349                 catch ( IOException e )
350                 {
351                     AppLogService.error( e.getMessage(  ), e );
352                 }
353             }
354         }
355     }
356 
357     /**
358      * Update cache status
359      * @param cs Cacheable Service
360      */
361     public static void updateCacheStatus( CacheableService cs )
362     {
363         String strKey = getDSKey( cs.getName(  ), PROPERTY_IS_ENABLED );
364         DatastoreService.setInstanceDataValue( strKey, ( cs.isCacheEnable(  ) ? ENABLED : DISABLED ) );
365     }
366 
367     /**
368      * Returns the cache status
369      *
370      * @param cs The cacheable service
371      * @return The status
372      */
373     private static boolean getStatus( CacheableService cs )
374     {
375         String strEnabled = DatastoreService.getInstanceDataValue( getDSKey( cs.getName(  ), PROPERTY_IS_ENABLED ),
376                 DISABLED );
377 
378         return strEnabled.equals( ENABLED );
379     }
380 
381     /**
382      * Return the key of a datastore property
383      * @param strCacheName The cacheable service
384      * @param strProperty The property
385      * @return The DS key
386      */
387     private static String getDSKey( String strCacheName, String strProperty )
388     {
389         return KEY_PREFIX + normalizeName( strCacheName ) + strProperty;
390     }
391 
392     /**
393      * Normalize name (remove spaces)
394      *
395      * @param strName The name to normalize
396      * @return The normalized name
397      */
398     private static String normalizeName( String strName )
399     {
400         return strName.replace( " ", "" );
401     }
402 
403     /**
404      * Read cache config from the file caches.dat
405      *
406      * @param strCacheName The cache name
407      * @return The config
408      */
409     private CacheConfiguration getCacheConfiguration( String strCacheName )
410     {
411         CacheConfiguration config = new CacheConfiguration(  );
412         config.setName( strCacheName );
413         config.setMaxElementsInMemory( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS, _nDefaultMaxElementsInMemory ) );
414         config.setEternal( getBooleanProperty( strCacheName, PROPERTY_ETERNAL, _bDefaultEternal ) );
415         config.setTimeToIdleSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_IDLE, _lDefaultTimeToIdle ) );
416         config.setTimeToLiveSeconds( getLongProperty( strCacheName, PROPERTY_TIME_TO_LIVE, _lDefaultTimeToLive ) );
417         config.setOverflowToDisk( getBooleanProperty( strCacheName, PROPERTY_OVERFLOW_TO_DISK, _bDefaultOverflowToDisk ) );
418         config.setDiskPersistent( getBooleanProperty( strCacheName, PROPERTY_DISK_PERSISTENT, _bDefaultDiskPersistent ) );
419         config.setDiskExpiryThreadIntervalSeconds( getLongProperty( strCacheName, PROPERTY_DISK_EXPIRY,
420                 _lDefaultDiskExpiry ) );
421         config.setMaxElementsOnDisk( getIntProperty( strCacheName, PROPERTY_MAX_ELEMENTS_DISK,
422                 _nDefaultMaxElementsOnDisk ) );
423         config.setStatistics( getBooleanProperty( strCacheName, PROPERTY_STATISTICS, _bDefaultStatistics ) );
424 
425         return config;
426     }
427 
428     /**
429      * Read an Integer property
430      *
431      * @param strCacheName Property's prefix
432      * @param strProperty the key
433      * @param nDefault the default value
434      * @return The property's value
435      */
436     private int getIntProperty( String strCacheName, String strProperty, int nDefault )
437     {
438         String strKey = getDSKey( strCacheName, strProperty );
439 
440         if ( DatastoreService.existsInstanceKey( strKey ) )
441         {
442             String strValue = NOT_FOUND;
443 
444             try
445             {
446                 strValue = DatastoreService.getInstanceDataValue( strKey, strValue );
447 
448                 int nValue = Integer.parseInt( strValue );
449 
450                 return nValue;
451             }
452             catch ( NumberFormatException e )
453             {
454                 AppLogService.error( "Invalid numeric property : " + strCacheName + strProperty + "=" + strValue, e );
455             }
456         }
457 
458         return nDefault;
459     }
460 
461     /**
462      * Read a Long property
463      *
464      * @param strCacheName Property's prefix
465      * @param strProperty the key
466      * @param lDefault the default value
467      * @return The property's value
468      */
469     private long getLongProperty( String strCacheName, String strProperty, long lDefault )
470     {
471         String strKey = getDSKey( strCacheName, strProperty );
472 
473         if ( DatastoreService.existsInstanceKey( strKey ) )
474         {
475             String strValue = NOT_FOUND;
476 
477             try
478             {
479                 strValue = DatastoreService.getInstanceDataValue( strKey, strValue );
480 
481                 long lValue = Integer.parseInt( strValue );
482 
483                 return lValue;
484             }
485             catch ( NumberFormatException e )
486             {
487                 AppLogService.error( "Invalid numeric property : " + strCacheName + strProperty + "=" + strValue, e );
488             }
489         }
490 
491         return lDefault;
492     }
493 
494     /**
495      * Read a Boolean property
496      *
497      * @param strCacheName Property's prefix
498      * @param strProperty the key
499      * @param bDefault the default value
500      * @return The property's value
501      */
502     private boolean getBooleanProperty( String strCacheName, String strProperty, boolean bDefault )
503     {
504         String strKey = getDSKey( strCacheName, strProperty );
505 
506         if ( DatastoreService.existsInstanceKey( strKey ) )
507         {
508             String strValue = DatastoreService.getInstanceDataValue( strKey, NOT_FOUND );
509 
510             return ( strValue.equalsIgnoreCase( TRUE ) );
511         }
512 
513         return bDefault;
514     }
515 }