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 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;
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
141 Thread housekeeping = new Thread(this);
142
143
144
145
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
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
187 throw new ObjectExpiredException();
188
189 rco.refresh();
190 if (rco.isStale())
191
192 throw new ObjectExpiredException();
193 }
194 else
195 {
196
197 throw new ObjectExpiredException();
198 }
199 }
200
201 if (obj instanceof RefreshableCachedObject)
202 {
203
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
221
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
248
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
268
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
282
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
325
326
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 }