1 package org.apache.turbine.services.cache;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.ByteArrayOutputStream;
58 import java.io.IOException;
59 import java.io.ObjectOutputStream;
60 import java.util.Enumeration;
61 import java.util.Hashtable;
62 import java.util.Vector;
63 import org.apache.turbine.services.InitializationException;
64 import org.apache.turbine.services.TurbineBaseService;
65 import org.apache.turbine.services.resources.TurbineResources;
66
67 import org.apache.commons.configuration.Configuration;
68
69
70 /***
71 * This Service functions as a Global Cache. A global cache is a good
72 * place to store items that you may need to access often but don't
73 * necessarily need (or want) to fetch from the database everytime. A
74 * good example would be a look up table of States that you store in a
75 * database and use throughout your application. Since information
76 * about States doesn't change very often, you could store this
77 * information in the Global Cache and decrease the overhead of
78 * hitting the database everytime you need State information.
79 *
80 * The following properties are needed to configure this service:<br>
81 *
82 * <code><pre>
83 * services.GlobalCacheService.classname=org.apache.turbine.services.cache.TurbineGlobalCacheService
84 * services.GlobalCacheService.cache.initial.size=20
85 * services.GlobalCacheService.cache.check.frequency=5000
86 * </pre></code>
87 *
88 * <dl>
89 * <dt>classname</dt><dd>the classname of this service</dd>
90 * <dt>cache.initial.size/dt><dd>Initial size of hash table use to store cached
91 objects. If this property is not present, the default value is 20</dd>
92 * <dt>cache.check.frequency</dt><dd>Cache check frequency in Millis (1000
93 Millis = 1 second). If this property is not present, the default value is 5000</dd>
94 * </dl>
95 * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
96 * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
97 * @author <a href="mailto:john@zenplex.com">John Thorhauer</a>
98 * @version $Id: TurbineGlobalCacheService.java,v 1.4 2002/04/16 22:07:08 kschrader Exp $
99 */
100 public class TurbineGlobalCacheService
101 extends TurbineBaseService
102 implements GlobalCacheService,
103 Runnable
104 {
105 /***
106 * Initial size of hash table
107 * Value must be > 0.
108 * Default = 20
109 */
110 public static final int DEFAULT_INITIAL_CACHE_SIZE = 20;
111
112 /***
113 * The property for the InitalCacheSize
114 */
115 public static final String INITIAL_CACHE_SIZE = "cache.initial.size";
116
117 /***
118 * The property for the Cache check frequency
119 */
120 public static final String CACHE_CHECK_FREQUENCY = "cache.check.frequency";
121
122 /***
123 * Cache check frequency in Millis (1000 Millis = 1 second).
124 * Value must be > 0.
125 * Default = 5 seconds
126 */
127 public static final long DEFAULT_CACHE_CHECK_FREQUENCY =
128 TurbineResources.getLong("cache.check.frequency", 5000 ); // 5 seconds
129
130 /*** The cache. **/
131 private Hashtable cache = null;
132
133 /*** cacheCheckFrequency (default - 5 seconds) */
134 private long cacheCheckFrequency = DEFAULT_CACHE_CHECK_FREQUENCY;
135
136 /***
137 * Constructor.
138 */
139 public TurbineGlobalCacheService()
140 {
141 }
142
143 /***
144 * Called the first time the Service is used.
145 */
146 public void init()
147 throws InitializationException {
148 int cacheInitialSize = DEFAULT_INITIAL_CACHE_SIZE;
149 Configuration conf = getConfiguration();
150 if (conf != null) {
151 try {
152 cacheInitialSize = conf.getInt(INITIAL_CACHE_SIZE, DEFAULT_INITIAL_CACHE_SIZE);
153 if (cacheInitialSize <= 0) {
154 throw new IllegalArgumentException(INITIAL_CACHE_SIZE + " must be >0");
155 }
156 cacheCheckFrequency = conf.getLong(CACHE_CHECK_FREQUENCY, DEFAULT_CACHE_CHECK_FREQUENCY);
157 if (cacheCheckFrequency <= 0) {
158 throw new IllegalArgumentException(CACHE_CHECK_FREQUENCY + " must be >0");
159 }
160 }
161 catch (Exception x) {
162 throw new InitializationException(
163 "Failed to initialize TurbineGlobalCacheService",x);
164 }
165 }
166
167 try {
168 cache = new Hashtable(cacheInitialSize);
169
170 // Start housekeeping thread.
171 Thread housekeeping = new Thread(this);
172 // Indicate that this is a system thread. JVM will quit only when there
173 // are no more active user threads. Settings threads spawned internally
174 // by Turbine as daemons allows commandline applications using Turbine
175 // to terminate in an orderly manner.
176 housekeeping.setDaemon(true);
177 housekeeping.start();
178 setInit(true);
179 }
180 catch (Exception e) {
181 throw new InitializationException(
182 "TurbineGlobalCacheService failed to initialize", e);
183 }
184 }
185
186 /***
187 * Returns an item from the cache. RefreshableCachedObject will be
188 * refreshed if it is expired not untouched.
189 *
190 * @param id The key of the stored object.
191 * @return The object from the cache.
192 * @exception ObjectExpiredException when either the object is
193 * not in the cache or it has expired.
194 */
195 public CachedObject getObject(String id)
196 throws ObjectExpiredException
197 {
198 CachedObject obj = null;
199 boolean stale = false;
200
201 obj = (CachedObject) cache.get(id);
202
203 if (obj == null)
204 {
205 // Not in the cache.
206 throw new ObjectExpiredException();
207 }
208
209 if (obj.isStale()) {
210 if (obj instanceof RefreshableCachedObject) {
211 RefreshableCachedObject rco = (RefreshableCachedObject) obj;
212 if (rco.isUntouched())
213 // Do not refresh an object that has exceeded TimeToLive
214 throw new ObjectExpiredException();
215 // Refresh Object
216 rco.refresh();
217 if (rco.isStale())
218 // Object is Expired.
219 throw new ObjectExpiredException();
220 } else {
221 // Expired.
222 throw new ObjectExpiredException();
223 }
224 }
225
226 if (obj instanceof RefreshableCachedObject)
227 {
228 // notify it that it's being accessed.
229 RefreshableCachedObject rco = (RefreshableCachedObject) obj;
230 rco.touch();
231 }
232
233 return obj;
234 }
235
236 /***
237 * Adds an object to the cache.
238 *
239 * @param id The key to store the object by.
240 * @param o The object to cache.
241 */
242 public void addObject(String id,
243 CachedObject o)
244 {
245 // If the cache already contains the key, remove it and add
246 // the fresh one.
247 if ( cache.containsKey(id) )
248 {
249 cache.remove(id);
250 }
251 cache.put(id,o);
252 }
253
254 /***
255 * Removes an object from the cache.
256 *
257 * @param id The String id for the object.
258 */
259 public void removeObject(String id)
260 {
261 cache.remove(id);
262 }
263
264 /***
265 * Circle through the cache and remove stale objects. Frequency
266 * is determined by the cacheCheckFrequency property.
267 */
268 public void run()
269 {
270 while(true)
271 {
272 // Sleep for amount of time set in cacheCheckFrequency -
273 // default = 5 seconds.
274 try
275 {
276 Thread.sleep(cacheCheckFrequency);
277 }
278 catch(InterruptedException exc)
279 {
280 }
281
282 clearCache();
283 }
284 }
285
286 /***
287 * Iterate through the cache and remove or refresh stale objects.
288 */
289 public void clearCache()
290 {
291 Vector refreshThese = new Vector(20);
292 // Sync on this object so that other threads do not
293 // change the Hashtable while enumerating over it.
294 synchronized (this)
295 {
296 for ( Enumeration e = cache.keys(); e.hasMoreElements(); )
297 {
298 String key = (String) e.nextElement();
299 CachedObject co = (CachedObject) cache.get(key);
300 if (co instanceof RefreshableCachedObject)
301 {
302 RefreshableCachedObject rco = (RefreshableCachedObject)co;
303 if (rco.isUntouched())
304 cache.remove(key);
305 else if (rco.isStale())
306 // Do refreshing outside of sync block so as not
307 // to prolong holding the lock on this object
308 refreshThese.addElement(key);
309 }
310 else if ( co.isStale() )
311 {
312 cache.remove(key);
313 }
314 }
315 }
316
317 for ( Enumeration e = refreshThese.elements(); e.hasMoreElements(); )
318 {
319 String key = (String)e.nextElement();
320 CachedObject co = (CachedObject)cache.get(key);
321 RefreshableCachedObject rco = (RefreshableCachedObject)co;
322 rco.refresh();
323 }
324 }
325
326 /***
327 * Returns the number of objects currently stored in the cache
328 *
329 * @return int number of object in the cache
330 */
331 public int getNumberOfObjects()
332 {
333 return cache.size();
334 }
335
336 /***
337 * Returns the current size of the cache.
338 *
339 * @return int representing current cache size in number of bytes
340 */
341 public int getCacheSize()
342 throws IOException
343 {
344 ByteArrayOutputStream baos = new ByteArrayOutputStream();
345 ObjectOutputStream out = new ObjectOutputStream(baos);
346 out.writeObject(cache);
347 out.flush();
348 //
349 // Subtract 4 bytes from the length, because the serialization
350 // magic number (2 bytes) and version number (2 bytes) are
351 // both written to the stream before the object
352 //
353 int objectsize = baos.toByteArray().length - 4;
354 return objectsize;
355 }
356
357 /***
358 * Flush the cache of all objects.
359 */
360 public void flushCache() {
361
362 synchronized (this) {
363 for ( Enumeration e = cache.keys(); e.hasMoreElements(); ) {
364 String key = (String) e.nextElement();
365 CachedObject co = (CachedObject) cache.get(key);
366 cache.remove(key);
367 }
368 }
369 }
370 }
This page was automatically generated by Maven