001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.core.cache;
066:
067: import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock;
068: import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock;
069: import org.apache.commons.collections.LRUMap;
070: import org.apache.log4j.Logger;
071:
072: import java.util.HashMap;
073: import java.util.Iterator;
074: import java.util.Map;
075: import java.util.Vector;
076:
077: /**
078: * Unordered cache is a hash map backed class. As such, access to individual items
079: * is relatively quick. However getting all the items in cache is a very slow process,
080: * and the order of the items is not guaranteed.
081: * <p/>
082: * If the size of the unordered cache is specified, then the cache automatically uses
083: * the least recently used (LRU) algorithm to determine which items should be removed
084: * from the cache.
085: * <p/>
086: * Unless you need to specifically store arrays of objects then this will be the cache
087: * to use for most of your needs.
088: * Creation date: (9/7/00 11:47:14 AM)
089: */
090: public class UnOrderedCache implements Cache {
091:
092: /**
093: * Read Write lock on the cache contents so we can concurrently read from the
094: * cache.
095: */
096: private final ReadWriteLock cacheLock = new WriterPreferenceReadWriteLock();
097:
098: /**
099: * Access count for this cache
100: */
101: private long accessCount = 0;
102:
103: //The actual cache contents. Access must be synchronized.
104: //Default is a hashmap, but if maxsize > 0 , then LRU map is
105: //used instead
106: private Map cacheContents = java.util.Collections
107: .synchronizedMap(new HashMap());
108:
109: //
110: private String cacheName = null;
111:
112: //This class
113: private static final String this Class = UnOrderedCache.class
114: .getName()
115: + ".";
116:
117: //Maxium size
118: private int maxSize = 0;
119:
120: //The log
121: private static final transient Logger log = Logger
122: .getLogger(UnOrderedCache.class);
123:
124: /**
125: * UnOrderedCache constructor comment.
126: */
127: public UnOrderedCache() {
128: super ();
129: } /* UnOrderedCache() */
130:
131: private final synchronized void incrementAccessCount() {
132: accessCount++;
133: }
134:
135: /**
136: * Adds an item to this cache
137: *
138: * @param newItem The new item to cache
139: */
140: public void addItem(CacheEntry newItem) {
141: incrementAccessCount();
142:
143: if (log.isDebugEnabled()) {
144: log.debug("Cache " + getName() + " Added item "
145: + newItem.getKey());
146: }
147:
148: try {
149: cacheLock.writeLock().acquire();
150: } catch (InterruptedException ex) {
151: log
152: .error(
153: "Interrupted waiting for write lock. Aborting method",
154: ex);
155: return;
156: }
157:
158: try {
159: //Simply put it in. LRUMap if maxsize is needed, takes care
160: //of removing the least recently used items.
161: cacheContents.put(newItem.getKey(), newItem);
162:
163: newItem.clearUsedCount();
164: } finally {
165: cacheLock.writeLock().release();
166: }
167: } /* addItem(CacheEntry) */
168:
169: /**
170: * Clear the cache by creating a new map
171: */
172: public void clear() {
173: try {
174: cacheLock.writeLock().acquire();
175: } catch (InterruptedException ex) {
176: log
177: .error(
178: "Interrupted waiting for write lock. Aborting method",
179: ex);
180: return;
181: }
182: try {
183: if (maxSize > 0) {
184: cacheContents = java.util.Collections
185: .synchronizedMap(new LRUMap(maxSize));
186: } else {
187: cacheContents = java.util.Collections
188: .synchronizedMap(new HashMap());
189: }
190: } finally {
191: cacheLock.writeLock().release();
192: }
193: } /* clear() */
194:
195: /**
196: * Retrieve the cache entry specified by the item key
197: *
198: * @param itemKey the key for the cache entry
199: * @return the resulting cache entry or null if it no longer exists.
200: */
201: public CacheEntry getCacheEntry(String itemKey) {
202: incrementAccessCount();
203: CacheEntry returnValue = null;
204: boolean removeCacheItem = false;
205: CacheEntry oneItem = null;
206: try {
207: cacheLock.readLock().acquire();
208: } catch (InterruptedException ex) {
209: log
210: .error(
211: "Interrupted waiting for write lock. Aborting method",
212: ex);
213: return null;
214: }
215:
216: try {
217: oneItem = (CacheEntry) cacheContents.get(itemKey);
218:
219: if (oneItem != null) {
220: if (oneItem.isExpired()) {
221: if (log.isDebugEnabled()) {
222: log
223: .debug("Item '"
224: + itemKey
225: + "' expired & was removed. Expiry was "
226: + oneItem.getExpires());
227: }
228: removeCacheItem = true;
229:
230: //Don't set the return value
231: } else {
232: //We're ok here..
233: oneItem.incrementUsedCount();
234: returnValue = oneItem;
235: }
236: }
237:
238: } finally {
239: cacheLock.readLock().release();
240: }
241:
242: //We have to remove here after the readlock is released, otherwise
243: //we cause deadlock
244: if (removeCacheItem) {
245: removeItem(oneItem.getContents());
246: return null;
247: }
248:
249: return returnValue;
250: }
251:
252: /**
253: * Get an item as defined by the key.
254: *
255: * @param itemKey The item's key
256: * @return <code>Cacheable</code> object or null if it doesn't exist in the
257: * cache
258: */
259: public Cacheable getItem(String itemKey) {
260: //Locking isn't necessary since we get the cache entry through an embedded
261: //lock
262: CacheEntry oneItem = this .getCacheEntry(itemKey);
263: if (oneItem == null) {
264: return null;
265: }
266:
267: if (oneItem.getContents() == null) {
268: if (log.isDebugEnabled()) {
269: log.debug("Item '" + itemKey + "' had null contents");
270: }
271: }
272:
273: return oneItem.getContents();
274: } /* getItem(String) */
275:
276: /**
277: * Get the number of items in the cache
278: *
279: * @return the number of items in this cache
280: */
281: public int getItemCount() {
282: int returnvalue = 0;
283: try {
284: cacheLock.readLock().acquire();
285: } catch (InterruptedException ex) {
286: log
287: .error(
288: "Interrupted waiting for write lock. Aborting method",
289: ex);
290: return 0;
291: }
292: try {
293: returnvalue = cacheContents.size();
294: } finally {
295: cacheLock.readLock().release();
296: }
297: return returnvalue;
298: } /* getItemCount() */
299:
300: /**
301: * Return all the items in a Vector This is a REALLY messy function
302: * for unordered caches. Use very INFREQUENTLY
303: *
304: * @return <code>java.util.Vector</code> of all the items.
305: */
306: public Vector getItems() {
307: incrementAccessCount();
308: Vector v = null;
309: try {
310: cacheLock.writeLock().acquire();
311: } catch (InterruptedException ex) {
312: log
313: .error(
314: "Interrupted waiting for write lock. Aborting method",
315: ex);
316: return null;
317: }
318:
319: try {
320: if (cacheContents.size() <= 0) {
321: return null;
322: }
323:
324: //Kind of pre-allocate to reduce time in the syncrhonized block.
325: //It's quite possible that final v will be different size then lenght,
326: //but since it's dynamic it should be able to handle that no problem.
327: int length = cacheContents.size();
328: v = new Vector(length);
329:
330: for (Iterator i = cacheContents.values().iterator(); i
331: .hasNext();) {
332: CacheEntry oneItem = (CacheEntry) i.next();
333: if (oneItem.isExpired()) {
334: i.remove();
335: } else {
336: oneItem.incrementUsedCount();
337: v.addElement(oneItem.getContents());
338: }
339: }
340: } finally {
341: cacheLock.writeLock().release();
342: }
343:
344: return v;
345: } /* getItems() */
346:
347: /**
348: * Returns the name of this cache
349: *
350: * @return <code>java.lang.String</code> the name of the cache
351: */
352: public synchronized String getName() {
353: return cacheName;
354: } /* getName() */
355:
356: /**
357: * Return the number of times this cache has been accessed
358: *
359: * @return the number of times accessed as integer
360: */
361: public synchronized long getUsedCount() {
362: return accessCount;
363: } /* getUsedCount() */
364:
365: /**
366: * Retrieve whether the cache instance is an ordered cache [list based]
367: * or unordered cache. [map based]
368: *
369: * @return true if the cache is an ordered cache.
370: */
371: public boolean isOrdered() {
372: return false;
373: }
374:
375: /**
376: * Removes an item as keyed by the paramter
377: *
378: * @param oldItem The item to remove.
379: */
380: public synchronized void removeItem(Cacheable oldItem) {
381: try {
382: cacheLock.writeLock().acquire();
383: } catch (InterruptedException ex) {
384: log
385: .error(
386: "Interrupted waiting for write lock. Aborting method",
387: ex);
388: return;
389: }
390: try {
391: cacheContents.remove(oldItem.getKey());
392: } finally {
393: cacheLock.writeLock().release();
394: }
395: } /* removeItem(Cacheable) */
396:
397: /**
398: * Eventually the new way to set items
399: *
400: * @param newItems the new items to add
401: * @throws CacheException upon error
402: */
403: public void setItems(java.util.List newItems) throws CacheException {
404: if (newItems instanceof java.util.Vector) {
405: setItems(newItems);
406: } else {
407: setItems(new Vector(newItems));
408: }
409: }
410:
411: /**
412: * Goes through all the new items and adds them to the list.
413: *
414: * @param newItems A <code>java.util.Vector</code> of new items to add to
415: * the cache list
416: * @throws CacheException if there's an error adding the cache entry items
417: */
418: public void setItems(Vector newItems) throws CacheException {
419: incrementAccessCount();
420: clear();
421: try {
422: cacheLock.writeLock().acquire();
423: } catch (InterruptedException ex) {
424: log
425: .error(
426: "Interrupted waiting for write lock. Aborting method",
427: ex);
428: return;
429: }
430:
431: try {
432: Object oneObject = null;
433: int size = newItems.size();
434: for (int i = 0; i < size; i++) {
435: oneObject = newItems.elementAt(i);
436:
437: if (oneObject instanceof Cacheable) {
438: CacheEntry ce = new CacheEntry(
439: (Cacheable) oneObject, -1);
440: addItem(ce);
441: } else {
442: String myName = this Class + ":setItems()";
443: throw new CacheException(
444: myName
445: + ":Item in vector is of class "
446: + oneObject.getClass().getName()
447: + ", which is not Cacheable - cannot set items");
448: }
449: }
450: } finally {
451: cacheLock.writeLock().release();
452: }
453: } /* setItems(Vector) */
454:
455: /**
456: * Sets the maximum size for the cache. If it is greater than zero then
457: * we use an LRU Map instead of a normal hashmap to provide automatic
458: * removal of old items.
459: *
460: * @param newMaxSize The new maximum size to use for the cache
461: */
462: public synchronized void setMaxSize(int newMaxSize) {
463: try {
464: cacheLock.writeLock().acquire();
465: } catch (InterruptedException ex) {
466: log
467: .error(
468: "Interrupted waiting for write lock. Aborting method",
469: ex);
470: return;
471: }
472:
473: try {
474: if (newMaxSize > 0) {
475: LRUMap newMap = new LRUMap(newMaxSize);
476: if (cacheContents.size() > 0) {
477: newMap.putAll(cacheContents);
478: }
479: cacheContents = java.util.Collections
480: .synchronizedMap(newMap);
481: } else {
482: cacheContents = java.util.Collections
483: .synchronizedMap(new HashMap(cacheContents));
484: }
485:
486: maxSize = newMaxSize;
487: } finally {
488: cacheLock.writeLock().release();
489: }
490: } /* setMaxSize(int) */
491:
492: /**
493: * Sets the name of the cache
494: *
495: * @param newName the new name to set for the cache.
496: */
497: public synchronized void setName(String newName) {
498: cacheName = newName;
499: } /* setName(String) */
500:
501: } /* UnOrderedCache */
|