%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
org.apache.turbine.services.cache.TurbineGlobalCacheService |
|
|
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 | 6 | private Hashtable cache = null; |
95 | ||
96 | /** cacheCheckFrequency (default - 5 seconds) */ |
|
97 | 6 | private long cacheCheckFrequency = DEFAULT_CACHE_CHECK_FREQUENCY; |
98 | ||
99 | /** |
|
100 | * Constructor. |
|
101 | */ |
|
102 | public TurbineGlobalCacheService() |
|
103 | 6 | { |
104 | 6 | } |
105 | ||
106 | /** |
|
107 | * Called the first time the Service is used. |
|
108 | */ |
|
109 | public void init() |
|
110 | throws InitializationException |
|
111 | { |
|
112 | 0 | int cacheInitialSize = DEFAULT_INITIAL_CACHE_SIZE; |
113 | 0 | Configuration conf = getConfiguration(); |
114 | 0 | if (conf != null) |
115 | { |
|
116 | try |
|
117 | { |
|
118 | 0 | cacheInitialSize = conf.getInt(INITIAL_CACHE_SIZE, DEFAULT_INITIAL_CACHE_SIZE); |
119 | 0 | if (cacheInitialSize <= 0) |
120 | { |
|
121 | 0 | throw new IllegalArgumentException(INITIAL_CACHE_SIZE + " must be >0"); |
122 | } |
|
123 | 0 | cacheCheckFrequency = conf.getLong(CACHE_CHECK_FREQUENCY, DEFAULT_CACHE_CHECK_FREQUENCY); |
124 | 0 | if (cacheCheckFrequency <= 0) |
125 | { |
|
126 | 0 | throw new IllegalArgumentException(CACHE_CHECK_FREQUENCY + " must be >0"); |
127 | } |
|
128 | } |
|
129 | 0 | catch (Exception x) |
130 | { |
|
131 | 0 | throw new InitializationException( |
132 | "Failed to initialize TurbineGlobalCacheService", x); |
|
133 | 0 | } |
134 | } |
|
135 | ||
136 | try |
|
137 | { |
|
138 | 0 | cache = new Hashtable(cacheInitialSize); |
139 | ||
140 | // Start housekeeping thread. |
|
141 | 0 | 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 | 0 | housekeeping.setDaemon(true); |
147 | 0 | housekeeping.start(); |
148 | ||
149 | 0 | setInit(true); |
150 | } |
|
151 | 0 | catch (Exception e) |
152 | { |
|
153 | 0 | throw new InitializationException( |
154 | "TurbineGlobalCacheService failed to initialize", e); |
|
155 | 0 | } |
156 | 0 | } |
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 | 0 | CachedObject obj = null; |
171 | ||
172 | 0 | obj = (CachedObject) cache.get(id); |
173 | ||
174 | 0 | if (obj == null) |
175 | { |
|
176 | // Not in the cache. |
|
177 | 0 | throw new ObjectExpiredException(); |
178 | } |
|
179 | ||
180 | 0 | if (obj.isStale()) |
181 | { |
|
182 | 0 | if (obj instanceof RefreshableCachedObject) |
183 | { |
|
184 | 0 | RefreshableCachedObject rco = (RefreshableCachedObject) obj; |
185 | 0 | if (rco.isUntouched()) |
186 | // Do not refresh an object that has exceeded TimeToLive |
|
187 | 0 | throw new ObjectExpiredException(); |
188 | // Refresh Object |
|
189 | 0 | rco.refresh(); |
190 | 0 | if (rco.isStale()) |
191 | // Object is Expired. |
|
192 | 0 | throw new ObjectExpiredException(); |
193 | } |
|
194 | else |
|
195 | { |
|
196 | // Expired. |
|
197 | 0 | throw new ObjectExpiredException(); |
198 | } |
|
199 | } |
|
200 | ||
201 | 0 | if (obj instanceof RefreshableCachedObject) |
202 | { |
|
203 | // notify it that it's being accessed. |
|
204 | 0 | RefreshableCachedObject rco = (RefreshableCachedObject) obj; |
205 | 0 | rco.touch(); |
206 | } |
|
207 | ||
208 | 0 | 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 | 0 | if (cache.containsKey(id)) |
223 | { |
|
224 | 0 | cache.remove(id); |
225 | } |
|
226 | 0 | cache.put(id, o); |
227 | 0 | } |
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 | 0 | cache.remove(id); |
237 | 0 | } |
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 | 0 | Thread.sleep(cacheCheckFrequency); |
252 | } |
|
253 | 0 | catch (InterruptedException exc) |
254 | { |
|
255 | 0 | } |
256 | ||
257 | 0 | clearCache(); |
258 | } |
|
259 | } |
|
260 | ||
261 | /** |
|
262 | * Iterate through the cache and remove or refresh stale objects. |
|
263 | */ |
|
264 | public void clearCache() |
|
265 | { |
|
266 | 0 | 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 | 0 | synchronized (this) |
270 | { |
|
271 | 0 | for (Enumeration e = cache.keys(); e.hasMoreElements();) |
272 | { |
|
273 | 0 | String key = (String) e.nextElement(); |
274 | 0 | CachedObject co = (CachedObject) cache.get(key); |
275 | 0 | if (co instanceof RefreshableCachedObject) |
276 | { |
|
277 | 0 | RefreshableCachedObject rco = (RefreshableCachedObject) co; |
278 | 0 | if (rco.isUntouched()) |
279 | 0 | cache.remove(key); |
280 | 0 | else if (rco.isStale()) |
281 | // Do refreshing outside of sync block so as not |
|
282 | // to prolong holding the lock on this object |
|
283 | 0 | refreshThese.addElement(key); |
284 | } |
|
285 | 0 | else if (co.isStale()) |
286 | { |
|
287 | 0 | cache.remove(key); |
288 | } |
|
289 | } |
|
290 | 0 | } |
291 | ||
292 | 0 | for (Enumeration e = refreshThese.elements(); e.hasMoreElements();) |
293 | { |
|
294 | 0 | String key = (String) e.nextElement(); |
295 | 0 | CachedObject co = (CachedObject) cache.get(key); |
296 | 0 | RefreshableCachedObject rco = (RefreshableCachedObject) co; |
297 | 0 | rco.refresh(); |
298 | } |
|
299 | 0 | } |
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 | 0 | 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 | 0 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
320 | 0 | ObjectOutputStream out = new ObjectOutputStream(baos); |
321 | 0 | out.writeObject(cache); |
322 | 0 | 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 | 0 | int objectsize = baos.toByteArray().length - 4; |
329 | 0 | return objectsize; |
330 | } |
|
331 | ||
332 | /** |
|
333 | * Flush the cache of all objects. |
|
334 | */ |
|
335 | public void flushCache() |
|
336 | { |
|
337 | ||
338 | 0 | synchronized (this) |
339 | { |
|
340 | 0 | for (Enumeration e = cache.keys(); e.hasMoreElements();) |
341 | { |
|
342 | 0 | String key = (String) e.nextElement(); |
343 | 0 | cache.remove(key); |
344 | } |
|
345 | 0 | } |
346 | 0 | } |
347 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |