1 package org.apache.turbine.services.cache;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
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
144 Thread housekeeping = new Thread(this);
145
146
147
148
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
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
190 throw new ObjectExpiredException();
191
192 rco.refresh();
193 if (rco.isStale())
194
195 throw new ObjectExpiredException();
196 }
197 else
198 {
199
200 throw new ObjectExpiredException();
201 }
202 }
203
204 if (obj instanceof RefreshableCachedObject)
205 {
206
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
224
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
251
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
271
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
285
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
328
329
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 }