001: package org.apache.turbine.services.cache;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.ByteArrayOutputStream;
023: import java.io.IOException;
024: import java.io.ObjectOutputStream;
025:
026: import java.util.Enumeration;
027: import java.util.Hashtable;
028: import java.util.Vector;
029:
030: import org.apache.commons.configuration.Configuration;
031:
032: import org.apache.turbine.services.InitializationException;
033: import org.apache.turbine.services.TurbineBaseService;
034:
035: /**
036: * This Service functions as a Global Cache. A global cache is a good
037: * place to store items that you may need to access often but don't
038: * necessarily need (or want) to fetch from the database everytime. A
039: * good example would be a look up table of States that you store in a
040: * database and use throughout your application. Since information
041: * about States doesn't change very often, you could store this
042: * information in the Global Cache and decrease the overhead of
043: * hitting the database everytime you need State information.
044: *
045: * The following properties are needed to configure this service:<br>
046: *
047: * <code><pre>
048: * services.GlobalCacheService.classname=org.apache.turbine.services.cache.TurbineGlobalCacheService
049: * services.GlobalCacheService.cache.initial.size=20
050: * services.GlobalCacheService.cache.check.frequency=5000
051: * </pre></code>
052: *
053: * <dl>
054: * <dt>classname</dt><dd>the classname of this service</dd>
055: * <dt>cache.initial.size</dt><dd>Initial size of hash table use to store cached
056: objects. If this property is not present, the default value is 20</dd>
057: * <dt>cache.check.frequency</dt><dd>Cache check frequency in Millis (1000
058: Millis = 1 second). If this property is not present, the default value is 5000</dd>
059: * </dl>
060: *
061: * @author <a href="mailto:mbryson@mont.mindspring.com">Dave Bryson</a>
062: * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
063: * @author <a href="mailto:john@zenplex.com">John Thorhauer</a>
064: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
065: * @version $Id: TurbineGlobalCacheService.java 534527 2007-05-02 16:10:59Z tv $
066: */
067: public class TurbineGlobalCacheService extends TurbineBaseService
068: implements GlobalCacheService, Runnable {
069: /**
070: * Initial size of hash table
071: * Value must be > 0.
072: * Default = 20
073: */
074: public static final int DEFAULT_INITIAL_CACHE_SIZE = 20;
075:
076: /**
077: * The property for the InitalCacheSize
078: */
079: public static final String INITIAL_CACHE_SIZE = "cache.initial.size";
080:
081: /**
082: * The property for the Cache check frequency
083: */
084: public static final String CACHE_CHECK_FREQUENCY = "cache.check.frequency";
085:
086: /**
087: * Cache check frequency in Millis (1000 Millis = 1 second).
088: * Value must be > 0.
089: * Default = 5 seconds
090: */
091: public static final long DEFAULT_CACHE_CHECK_FREQUENCY = 5000; // 5 seconds
092:
093: /** The cache. **/
094: private Hashtable cache = null;
095:
096: /** cacheCheckFrequency (default - 5 seconds) */
097: private long cacheCheckFrequency = DEFAULT_CACHE_CHECK_FREQUENCY;
098:
099: /**
100: * Constructor.
101: */
102: public TurbineGlobalCacheService() {
103: }
104:
105: /**
106: * Called the first time the Service is used.
107: */
108: public void init() throws InitializationException {
109: int cacheInitialSize = DEFAULT_INITIAL_CACHE_SIZE;
110: Configuration conf = getConfiguration();
111: if (conf != null) {
112: try {
113: cacheInitialSize = conf.getInt(INITIAL_CACHE_SIZE,
114: DEFAULT_INITIAL_CACHE_SIZE);
115: if (cacheInitialSize <= 0) {
116: throw new IllegalArgumentException(
117: INITIAL_CACHE_SIZE + " must be >0");
118: }
119: cacheCheckFrequency = conf.getLong(
120: CACHE_CHECK_FREQUENCY,
121: DEFAULT_CACHE_CHECK_FREQUENCY);
122: if (cacheCheckFrequency <= 0) {
123: throw new IllegalArgumentException(
124: CACHE_CHECK_FREQUENCY + " must be >0");
125: }
126: } catch (Exception x) {
127: throw new InitializationException(
128: "Failed to initialize TurbineGlobalCacheService",
129: x);
130: }
131: }
132:
133: try {
134: cache = new Hashtable(cacheInitialSize);
135:
136: // Start housekeeping thread.
137: Thread housekeeping = new Thread(this );
138: // Indicate that this is a system thread. JVM will quit only when there
139: // are no more active user threads. Settings threads spawned internally
140: // by Turbine as daemons allows commandline applications using Turbine
141: // to terminate in an orderly manner.
142: housekeeping.setDaemon(true);
143: housekeeping.start();
144:
145: setInit(true);
146: } catch (Exception e) {
147: throw new InitializationException(
148: "TurbineGlobalCacheService failed to initialize", e);
149: }
150: }
151:
152: /**
153: * Returns an item from the cache. RefreshableCachedObject will be
154: * refreshed if it is expired and not untouched.
155: *
156: * @param id The key of the stored object.
157: * @return The object from the cache.
158: * @exception ObjectExpiredException when either the object is
159: * not in the cache or it has expired.
160: */
161: public CachedObject getObject(String id)
162: throws ObjectExpiredException {
163: CachedObject obj = null;
164:
165: obj = (CachedObject) cache.get(id);
166:
167: if (obj == null) {
168: // Not in the cache.
169: throw new ObjectExpiredException();
170: }
171:
172: if (obj.isStale()) {
173: if (obj instanceof RefreshableCachedObject) {
174: RefreshableCachedObject rco = (RefreshableCachedObject) obj;
175: if (rco.isUntouched())
176: // Do not refresh an object that has exceeded TimeToLive
177: throw new ObjectExpiredException();
178: // Refresh Object
179: rco.refresh();
180: if (rco.isStale())
181: // Object is Expired.
182: throw new ObjectExpiredException();
183: } else {
184: // Expired.
185: throw new ObjectExpiredException();
186: }
187: }
188:
189: if (obj instanceof RefreshableCachedObject) {
190: // notify it that it's being accessed.
191: RefreshableCachedObject rco = (RefreshableCachedObject) obj;
192: rco.touch();
193: }
194:
195: return obj;
196: }
197:
198: /**
199: * Adds an object to the cache.
200: *
201: * @param id The key to store the object by.
202: * @param o The object to cache.
203: */
204: public void addObject(String id, CachedObject o) {
205: // If the cache already contains the key, remove it and add
206: // the fresh one.
207: if (cache.containsKey(id)) {
208: cache.remove(id);
209: }
210: cache.put(id, o);
211: }
212:
213: /**
214: * Removes an object from the cache.
215: *
216: * @param id The String id for the object.
217: */
218: public void removeObject(String id) {
219: cache.remove(id);
220: }
221:
222: /**
223: * Circle through the cache and remove stale objects. Frequency
224: * is determined by the cacheCheckFrequency property.
225: */
226: public void run() {
227: while (true) {
228: // Sleep for amount of time set in cacheCheckFrequency -
229: // default = 5 seconds.
230: try {
231: Thread.sleep(cacheCheckFrequency);
232: } catch (InterruptedException exc) {
233: }
234:
235: clearCache();
236: }
237: }
238:
239: /**
240: * Iterate through the cache and remove or refresh stale objects.
241: */
242: public void clearCache() {
243: Vector refreshThese = new Vector(20);
244: // Sync on this object so that other threads do not
245: // change the Hashtable while enumerating over it.
246: synchronized (this ) {
247: for (Enumeration e = cache.keys(); e.hasMoreElements();) {
248: String key = (String) e.nextElement();
249: CachedObject co = (CachedObject) cache.get(key);
250: if (co instanceof RefreshableCachedObject) {
251: RefreshableCachedObject rco = (RefreshableCachedObject) co;
252: if (rco.isUntouched())
253: cache.remove(key);
254: else if (rco.isStale())
255: // Do refreshing outside of sync block so as not
256: // to prolong holding the lock on this object
257: refreshThese.addElement(key);
258: } else if (co.isStale()) {
259: cache.remove(key);
260: }
261: }
262: }
263:
264: for (Enumeration e = refreshThese.elements(); e
265: .hasMoreElements();) {
266: String key = (String) e.nextElement();
267: CachedObject co = (CachedObject) cache.get(key);
268: RefreshableCachedObject rco = (RefreshableCachedObject) co;
269: rco.refresh();
270: }
271: }
272:
273: /**
274: * Returns the number of objects currently stored in the cache
275: *
276: * @return int number of object in the cache
277: */
278: public int getNumberOfObjects() {
279: return cache.size();
280: }
281:
282: /**
283: * Returns the current size of the cache.
284: *
285: * @return int representing current cache size in number of bytes
286: */
287: public int getCacheSize() throws IOException {
288: ByteArrayOutputStream baos = new ByteArrayOutputStream();
289: ObjectOutputStream out = new ObjectOutputStream(baos);
290: out.writeObject(cache);
291: out.flush();
292: //
293: // Subtract 4 bytes from the length, because the serialization
294: // magic number (2 bytes) and version number (2 bytes) are
295: // both written to the stream before the object
296: //
297: int objectsize = baos.toByteArray().length - 4;
298: return objectsize;
299: }
300:
301: /**
302: * Flush the cache of all objects.
303: */
304: public void flushCache() {
305:
306: synchronized (this ) {
307: for (Enumeration e = cache.keys(); e.hasMoreElements();) {
308: String key = (String) e.nextElement();
309: cache.remove(key);
310: }
311: }
312: }
313: }
|