View Javadoc
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  
36  import fr.paris.lutece.portal.service.page.PageEvent;
37  import fr.paris.lutece.portal.service.page.PageEventListener;
38  import fr.paris.lutece.portal.service.page.PageService;
39  
40  import net.sf.ehcache.Cache;
41  import net.sf.ehcache.CacheException;
42  import net.sf.ehcache.CacheManager;
43  import net.sf.ehcache.Ehcache;
44  import net.sf.ehcache.constructs.blocking.BlockingCache;
45  import net.sf.ehcache.constructs.blocking.LockTimeoutException;
46  import net.sf.ehcache.constructs.web.AlreadyCommittedException;
47  import net.sf.ehcache.constructs.web.AlreadyGzippedException;
48  import net.sf.ehcache.constructs.web.filter.FilterNonReentrantException;
49  import net.sf.ehcache.constructs.web.filter.SimpleCachingHeadersPageCachingFilter;
50  
51  import java.util.List;
52  
53  import javax.servlet.FilterChain;
54  import javax.servlet.FilterConfig;
55  import javax.servlet.http.HttpServletRequest;
56  import javax.servlet.http.HttpServletResponse;
57  
58  import org.apache.logging.log4j.LogManager;
59  import org.apache.logging.log4j.Logger;
60  
61  /**
62   * Headers Page Caching Filter based on EHCACHE WEB
63   */
64  public class HeadersPageCachingFilter extends SimpleCachingHeadersPageCachingFilter implements CacheableService, PageEventListener
65  {
66      private static final String BLOCKING_TIMEOUT_MILLIS = "blockingTimeoutMillis";
67      private static final String INIT_PARAM_CACHE_NAME = "cacheName";
68      private Cache _cache;
69      private Logger _logger = LogManager.getLogger( "lutece.cache" );
70      private boolean _bInit;
71      private boolean _bEnable = true;
72      private String _strCacheName;
73  
74      /**
75       * {@inheritDoc }
76       */
77      @Override
78      public void doInit( FilterConfig filterConfig )
79      {
80          // Override to inhibate the startup call made too early
81          // The original treatment is done at the first doFilter call
82          // through the init method below
83      }
84  
85      /**
86       * Initialization of the filter
87       */
88      protected void init( )
89      {
90          // Execute the doInit
91          synchronized( HeadersPageCachingFilter.class )
92          {
93              if ( blockingCache == null )
94              {
95                  _strCacheName = filterConfig.getInitParameter( INIT_PARAM_CACHE_NAME );
96                  CacheService.getInstance( ).createCache( _strCacheName );
97                  _cache = CacheManager.getInstance( ).getCache( _strCacheName );
98                  CacheService.registerCacheableService( this );
99                  _logger.debug( "Initializing cache : {}", _strCacheName );
100 
101                 setCacheNameIfAnyConfigured( filterConfig );
102 
103                 final String localCacheName = getCacheName( );
104                 Ehcache cache = getCacheManager( ).getEhcache( localCacheName );
105 
106                 if ( cache == null )
107                 {
108                     throw new CacheException( "cache '" + localCacheName + "' not found in configuration" );
109                 }
110 
111                 if ( !( cache instanceof BlockingCache ) )
112                 {
113                     // decorate and substitute
114                     BlockingCache newBlockingCache = new BlockingCache( cache );
115                     getCacheManager( ).replaceCacheWithDecoratedCache( cache, newBlockingCache );
116                 }
117 
118                 blockingCache = (BlockingCache) getCacheManager( ).getEhcache( localCacheName );
119 
120                 Integer blockingTimeoutMillis = parseBlockingCacheTimeoutMillis( filterConfig );
121 
122                 if ( ( blockingTimeoutMillis != null ) && ( blockingTimeoutMillis > 0 ) )
123                 {
124                     blockingCache.setTimeoutMillis( blockingTimeoutMillis );
125                 }
126             }
127 
128             PageService.addPageEventListener( this );
129         }
130 
131         _bInit = true;
132     }
133 
134     /**
135      * Returns a boolean describing whether the filter has been initialized
136      *
137      * @return True if the filter is initialized
138      */
139     protected boolean getInit( )
140     {
141         return _bInit;
142     }
143 
144     /**
145      * Reads the filterConfig for the parameter "blockingTimeoutMillis", and if found, set the blocking timeout. If there is a parsing exception, no timeout is
146      * set.
147      *
148      * @param filterConfig
149      *            The filter config
150      * @return The timeout value
151      */
152     private Integer parseBlockingCacheTimeoutMillis( FilterConfig filterConfig )
153     {
154         String timeout = filterConfig.getInitParameter( BLOCKING_TIMEOUT_MILLIS );
155 
156         try
157         {
158             return Integer.parseInt( timeout );
159         }
160         catch( NumberFormatException e )
161         {
162             return null;
163         }
164     }
165 
166     /**
167      * {@inheritDoc } This method is overriden to provide the cache name
168      */
169     @Override
170     protected String getCacheName( )
171     {
172         return _strCacheName;
173     }
174 
175     /**
176      * {@inheritDoc }
177      */
178     @Override
179     protected void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain )
180             throws AlreadyGzippedException, AlreadyCommittedException, FilterNonReentrantException, LockTimeoutException, Exception
181     {
182         if ( !_bInit )
183         {
184             init( );
185         }
186 
187         if ( _bEnable )
188         {
189             super.doFilter( request, response, chain );
190             _logger.debug( "URI served from cache : {}", request.getRequestURI( ) );
191         }
192         else
193         {
194             chain.doFilter( request, response );
195         }
196     }
197 
198     // Cacheable Service implementation
199     /**
200      * {@inheritDoc }
201      */
202     @Override
203     public boolean isCacheEnable( )
204     {
205         return _bEnable;
206     }
207 
208     /**
209      * {@inheritDoc }
210      */
211     @Override
212     public int getCacheSize( )
213     {
214         return _cache.getSize( );
215     }
216 
217     /**
218      * {@inheritDoc }
219      */
220     @Override
221     public void resetCache( )
222     {
223         _cache.removeAll( );
224     }
225 
226     /**
227      * {@inheritDoc }
228      */
229     @Override
230     public String getName( )
231     {
232         return _strCacheName;
233     }
234 
235     /**
236      * {@inheritDoc }
237      */
238     @Override
239     public void enableCache( boolean bEnable )
240     {
241         _bEnable = bEnable;
242 
243         if ( ( !_bEnable ) && ( _cache != null ) )
244         {
245             _cache.removeAll( );
246         }
247 
248         CacheService.updateCacheStatus( this );
249     }
250 
251     /**
252      * {@inheritDoc }
253      */
254     @Override
255     public List<String> getKeys( )
256     {
257         return _cache.getKeys( );
258     }
259 
260     /**
261      * {@inheritDoc }
262      */
263     @Override
264     public int getMaxElements( )
265     {
266         return _cache.getCacheConfiguration( ).getMaxElementsInMemory( );
267     }
268 
269     /**
270      * {@inheritDoc }
271      */
272     @Override
273     public long getTimeToLive( )
274     {
275         return _cache.getCacheConfiguration( ).getTimeToLiveSeconds( );
276     }
277 
278     /**
279      * {@inheritDoc }
280      */
281     @Override
282     public long getMemorySize( )
283     {
284         return _cache.calculateInMemorySize( );
285     }
286 
287     /**
288      * {@inheritDoc }
289      */
290     @Override
291     public String getInfos( )
292     {
293         return CacheService.getInfos( _cache );
294     }
295 
296     /**
297      * {@inheritDoc }
298      */
299     @Override
300     public void processPageEvent( PageEvent event )
301     {
302         String strPattern = "page_id=" + event.getPage( ).getId( );
303 
304         for ( String strKey : (List<String>) blockingCache.getKeys( ) )
305         {
306             if ( strKey.contains( strPattern ) && ( event.getEventType( ) != PageEvent.PAGE_CREATED ) )
307             {
308                 blockingCache.remove( strKey );
309             }
310         }
311     }
312 }