View Javadoc

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