View Javadoc

1   package org.apache.turbine.services.cache;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.ObjectOutputStream;
22  
23  import java.util.Enumeration;
24  import java.util.Hashtable;
25  import java.util.Vector;
26  
27  import org.apache.commons.configuration.Configuration;
28  
29  import org.apache.turbine.services.InitializationException;
30  import org.apache.turbine.services.TurbineBaseService;
31  
32  /***
33   * This Service functions as a Global Cache.  A global cache is a good
34   * place to store items that you may need to access often but don't
35   * necessarily need (or want) to fetch from the database everytime.  A
36   * good example would be a look up table of States that you store in a
37   * database and use throughout your application.  Since information
38   * about States doesn't change very often, you could store this
39   * information in the Global Cache and decrease the overhead of
40   * hitting the database everytime you need State information.
41   *
42   * The following properties are needed to configure this service:<br>
43   *
44   * <code><pre>
45   * services.GlobalCacheService.classname=org.apache.turbine.services.cache.TurbineGlobalCacheService
46   * services.GlobalCacheService.cache.initial.size=20
47   * services.GlobalCacheService.cache.check.frequency=5000
48   * </pre></code>
49   *
50   * <dl>
51   * <dt>classname</dt><dd>the classname of this service</dd>
52   * <dt>cache.initial.size</dt><dd>Initial size of hash table use to store cached
53   objects.  If this property is not present, the default value is 20</dd>
54   * <dt>cache.check.frequency</dt><dd>Cache check frequency in Millis (1000
55   Millis = 1 second).  If this property is not present, the default value is 5000</dd>
56   * </dl>
57   *
58   * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
59   * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
60   * @author <a href="mailto:john@zenplex.com">John Thorhauer</a>
61   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
62   * @version $Id: TurbineGlobalCacheService.java 264148 2005-08-29 14:21:04Z henning $
63   */
64  public class TurbineGlobalCacheService
65          extends TurbineBaseService
66          implements GlobalCacheService,
67          Runnable
68  {
69      /***
70       * Initial size of hash table
71       * Value must be > 0.
72       * Default = 20
73       */
74      public static final int DEFAULT_INITIAL_CACHE_SIZE = 20;
75  
76      /***
77       * The property for the InitalCacheSize
78       */
79      public static final String INITIAL_CACHE_SIZE = "cache.initial.size";
80  
81      /***
82       * The property for the Cache check frequency
83       */
84      public static final String CACHE_CHECK_FREQUENCY = "cache.check.frequency";
85  
86      /***
87       * Cache check frequency in Millis (1000 Millis = 1 second).
88       * Value must be > 0.
89       * Default = 5 seconds
90       */
91      public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds
92  
93      /*** The cache. **/
94      private Hashtable cache = null;
95  
96      /*** cacheCheckFrequency (default - 5 seconds) */
97      private long cacheCheckFrequency = DEFAULT_CACHE_CHECK_FREQUENCY;
98  
99      /***
100      * Constructor.
101      */
102     public TurbineGlobalCacheService()
103     {
104     }
105 
106     /***
107      * Called the first time the Service is used.
108      */
109     public void init()
110             throws InitializationException
111     {
112         int cacheInitialSize = DEFAULT_INITIAL_CACHE_SIZE;
113         Configuration conf = getConfiguration();
114         if (conf != null)
115         {
116             try
117             {
118                 cacheInitialSize = conf.getInt(INITIAL_CACHE_SIZE, DEFAULT_INITIAL_CACHE_SIZE);
119                 if (cacheInitialSize <= 0)
120                 {
121                     throw new IllegalArgumentException(INITIAL_CACHE_SIZE + " must be >0");
122                 }
123                 cacheCheckFrequency = conf.getLong(CACHE_CHECK_FREQUENCY, DEFAULT_CACHE_CHECK_FREQUENCY);
124                 if (cacheCheckFrequency <= 0)
125                 {
126                     throw new IllegalArgumentException(CACHE_CHECK_FREQUENCY + " must be >0");
127                 }
128             }
129             catch (Exception x)
130             {
131                 throw new InitializationException(
132                         "Failed to initialize TurbineGlobalCacheService", x);
133             }
134         }
135 
136         try
137         {
138             cache = new Hashtable(cacheInitialSize);
139 
140             // Start housekeeping thread.
141             Thread housekeeping = new Thread(this);
142             // Indicate that this is a system thread. JVM will quit only when there
143             // are no more active user threads. Settings threads spawned internally
144             // by Turbine as daemons allows commandline applications using Turbine
145             // to terminate in an orderly manner.
146             housekeeping.setDaemon(true);
147             housekeeping.start();
148 
149             setInit(true);
150         }
151         catch (Exception e)
152         {
153             throw new InitializationException(
154                     "TurbineGlobalCacheService failed to initialize", e);
155         }
156     }
157 
158     /***
159      * Returns an item from the cache.  RefreshableCachedObject will be
160      * refreshed if it is expired and not untouched.
161      *
162      * @param id The key of the stored object.
163      * @return The object from the cache.
164      * @exception ObjectExpiredException when either the object is
165      * not in the cache or it has expired.
166      */
167     public CachedObject getObject(String id)
168             throws ObjectExpiredException
169     {
170         CachedObject obj = null;
171 
172         obj = (CachedObject) cache.get(id);
173 
174         if (obj == null)
175         {
176             // Not in the cache.
177             throw new ObjectExpiredException();
178         }
179 
180         if (obj.isStale())
181         {
182             if (obj instanceof RefreshableCachedObject)
183             {
184                 RefreshableCachedObject rco = (RefreshableCachedObject) obj;
185                 if (rco.isUntouched())
186                 // Do not refresh an object that has exceeded TimeToLive
187                     throw new ObjectExpiredException();
188                 // Refresh Object
189                 rco.refresh();
190                 if (rco.isStale())
191                 // Object is Expired.
192                     throw new ObjectExpiredException();
193             }
194             else
195             {
196                 // Expired.
197                 throw new ObjectExpiredException();
198             }
199         }
200 
201         if (obj instanceof RefreshableCachedObject)
202         {
203             // notify it that it's being accessed.
204             RefreshableCachedObject rco = (RefreshableCachedObject) obj;
205             rco.touch();
206         }
207 
208         return obj;
209     }
210 
211     /***
212      * Adds an object to the cache.
213      *
214      * @param id The key to store the object by.
215      * @param o The object to cache.
216      */
217     public void addObject(String id,
218                           CachedObject o)
219     {
220         // If the cache already contains the key, remove it and add
221         // the fresh one.
222         if (cache.containsKey(id))
223         {
224             cache.remove(id);
225         }
226         cache.put(id, o);
227     }
228 
229     /***
230      * Removes an object from the cache.
231      *
232      * @param id The String id for the object.
233      */
234     public void removeObject(String id)
235     {
236         cache.remove(id);
237     }
238 
239     /***
240      * Circle through the cache and remove stale objects.  Frequency
241      * is determined by the cacheCheckFrequency property.
242      */
243     public void run()
244     {
245         while (true)
246         {
247             // Sleep for amount of time set in cacheCheckFrequency -
248             // default = 5 seconds.
249             try
250             {
251                 Thread.sleep(cacheCheckFrequency);
252             }
253             catch (InterruptedException exc)
254             {
255             }
256 
257             clearCache();
258         }
259     }
260 
261     /***
262      * Iterate through the cache and remove or refresh stale objects.
263      */
264     public void clearCache()
265     {
266         Vector refreshThese = new Vector(20);
267         // Sync on this object so that other threads do not
268         // change the Hashtable while enumerating over it.
269         synchronized (this)
270         {
271             for (Enumeration e = cache.keys(); e.hasMoreElements();)
272             {
273                 String key = (String) e.nextElement();
274                 CachedObject co = (CachedObject) cache.get(key);
275                 if (co instanceof RefreshableCachedObject)
276                 {
277                     RefreshableCachedObject rco = (RefreshableCachedObject) co;
278                     if (rco.isUntouched())
279                         cache.remove(key);
280                     else if (rco.isStale())
281                     // Do refreshing outside of sync block so as not
282                     // to prolong holding the lock on this object
283                         refreshThese.addElement(key);
284                 }
285                 else if (co.isStale())
286                 {
287                     cache.remove(key);
288                 }
289             }
290         }
291 
292         for (Enumeration e = refreshThese.elements(); e.hasMoreElements();)
293         {
294             String key = (String) e.nextElement();
295             CachedObject co = (CachedObject) cache.get(key);
296             RefreshableCachedObject rco = (RefreshableCachedObject) co;
297             rco.refresh();
298         }
299     }
300 
301     /***
302      * Returns the number of objects currently stored in the cache
303      *
304      * @return int number of object in the cache
305      */
306     public int getNumberOfObjects()
307     {
308         return cache.size();
309     }
310 
311     /***
312      * Returns the current size of the cache.
313      *
314      * @return int representing current cache size in number of bytes
315      */
316     public int getCacheSize()
317             throws IOException
318     {
319         ByteArrayOutputStream baos = new ByteArrayOutputStream();
320         ObjectOutputStream out = new ObjectOutputStream(baos);
321         out.writeObject(cache);
322         out.flush();
323         //
324         // Subtract 4 bytes from the length, because the serialization
325         // magic number (2 bytes) and version number (2 bytes) are
326         // both written to the stream before the object
327         //
328         int objectsize = baos.toByteArray().length - 4;
329         return objectsize;
330     }
331 
332     /***
333      * Flush the cache of all objects.
334      */
335     public void flushCache()
336     {
337 
338         synchronized (this)
339         {
340             for (Enumeration e = cache.keys(); e.hasMoreElements();)
341             {
342                 String key = (String) e.nextElement();
343                 cache.remove(key);
344             }
345         }
346     }
347 }