001: /*
002: * All content copyright (c) 2003-2007 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package net.sf.ehcache.store;
006:
007: import net.sf.ehcache.CacheException;
008: import net.sf.ehcache.Ehcache;
009: import net.sf.ehcache.Element;
010: import net.sf.ehcache.Status;
011: import org.apache.commons.logging.Log;
012: import org.apache.commons.logging.LogFactory;
013:
014: import java.util.Iterator;
015: import java.util.Map;
016:
017: /**
018: * This is a modification of the Ehcache net.sf.ehcache.store.MemoryStore.java class, which is under the Apache open source license.
019: *
020: */
021: /**
022: * An abstract class for the Memory Stores. All Memory store implementations for different policies (e.g: FIFO, LFU,
023: * LRU, etc.) should extend this class.
024: *
025: * @author <a href="mailto:ssuravarapu@users.sourceforge.net">Surya Suravarapu</a>
026: * @version $Id: MemoryStoreTC.java 5280 2007-08-27 18:23:11Z asi $
027: */
028: public abstract class MemoryStoreTC implements Store {
029:
030: private static final Log LOG = LogFactory.getLog(MemoryStore.class
031: .getName());
032:
033: /**
034: * The cache this store is associated with.
035: */
036: protected Ehcache cache;
037:
038: /**
039: * Map where items are stored by key.
040: */
041: protected Map map;
042:
043: /**
044: * The DiskStore associated with this MemoryStore.
045: */
046: protected final DiskStore diskStore;
047:
048: /**
049: * status.
050: */
051: protected Status status;
052:
053: /**
054: * Constructs things that all MemoryStores have in common.
055: *
056: * @param cache
057: * @param diskStore
058: */
059: protected MemoryStoreTC(Ehcache cache, Store diskStore) {
060: status = Status.STATUS_UNINITIALISED;
061: this .cache = cache;
062: this .diskStore = (DiskStore) diskStore;
063: status = Status.STATUS_ALIVE;
064:
065: if (LOG.isDebugEnabled()) {
066: LOG.debug("Initialized " + this .getClass().getName()
067: + " for " + cache.getName());
068: }
069: }
070:
071: /**
072: * A factory method to create a MemoryStore.
073: *
074: * @param cache
075: * @param diskStore
076: * @return an instance of a MemoryStore, configured with the appropriate eviction policy
077: */
078: public static MemoryStore create(Ehcache cache, Store diskStore) {
079: MemoryStore memoryStore = null;
080: MemoryStoreEvictionPolicy policy = cache
081: .getMemoryStoreEvictionPolicy();
082:
083: if (policy.equals(MemoryStoreEvictionPolicy.LRU)) {
084: memoryStore = new LruMemoryStore(cache,
085: (DiskStore) diskStore);
086: } else if (policy.equals(MemoryStoreEvictionPolicy.FIFO)) {
087: memoryStore = new FifoMemoryStore(cache,
088: (DiskStore) diskStore);
089: } else if (policy.equals(MemoryStoreEvictionPolicy.LFU)) {
090: memoryStore = new LfuMemoryStore(cache,
091: (DiskStore) diskStore);
092: }
093: return memoryStore;
094: }
095:
096: /**
097: * Puts an item in the cache. Note that this automatically results in
098: * {@link net.sf.ehcache.store.LruMemoryStore.SpoolingLinkedHashMap#removeEldestEntry} being called.
099: *
100: * @param element the element to add
101: */
102: public final synchronized void put(Element element)
103: throws CacheException {
104: if (element != null) {
105: map.put(element.getObjectKey(), element);
106: doPut(element);
107: }
108: }
109:
110: /**
111: * Allow specialised actions over adding the element to the map.
112: *
113: * @param element
114: */
115: protected void doPut(Element element) throws CacheException {
116: // empty
117: }
118:
119: /**
120: * Gets an item from the cache. <p/> The last access time in {@link net.sf.ehcache.Element} is updated.
121: *
122: * @param key the cache key
123: * @return the element, or null if there was no match for the key
124: */
125: public final synchronized Element get(Object key) {
126: Element element = (Element) map.get(key);
127:
128: if (element != null) {
129: element.updateAccessStatistics();
130: if (LOG.isTraceEnabled()) {
131: LOG.trace(cache.getName() + "Cache: " + cache.getName()
132: + "MemoryStore hit for " + key);
133: }
134: } else if (LOG.isTraceEnabled()) {
135: LOG.trace(cache.getName() + "Cache: " + cache.getName()
136: + "MemoryStore miss for " + key);
137: }
138: return element;
139: }
140:
141: /**
142: * Gets an item from the cache, without updating Element statistics.
143: *
144: * @param key the cache key
145: * @return the element, or null if there was no match for the key
146: */
147: public final synchronized Element getQuiet(Object key) {
148: Element cacheElement = (Element) map.get(key);
149:
150: if (cacheElement != null) {
151: // cacheElement.updateAccessStatistics(); Don't update statistics
152: if (LOG.isTraceEnabled()) {
153: LOG.trace(cache.getName() + "Cache: " + cache.getName()
154: + "MemoryStore hit for " + key);
155: }
156: } else if (LOG.isTraceEnabled()) {
157: LOG.trace(cache.getName() + "Cache: " + cache.getName()
158: + "MemoryStore miss for " + key);
159: }
160: return cacheElement;
161: }
162:
163: /**
164: * Removes an Element from the store.
165: *
166: * @param key the key of the Element, usually a String
167: * @return the Element if one was found, else null
168: */
169: public final synchronized Element remove(Object key) {
170:
171: // remove single item.
172: Element element = (Element) map.remove(key);
173: if (element != null) {
174: return element;
175: } else {
176: if (LOG.isDebugEnabled()) {
177: LOG.debug(cache.getName()
178: + "Cache: Cannot remove entry as key " + key
179: + " was not found");
180: }
181: return null;
182: }
183: }
184:
185: /**
186: * Remove all of the elements from the store.
187: */
188: public final synchronized void removeAll() throws CacheException {
189: clear();
190: }
191:
192: /**
193: * Clears any data structures and places it back to its state when it was first created.
194: */
195: protected final void clear() {
196: map.clear();
197: }
198:
199: /**
200: * Prepares for shutdown.
201: */
202: public final synchronized void dispose() {
203: if (status.equals(Status.STATUS_SHUTDOWN)) {
204: return;
205: }
206: status = Status.STATUS_SHUTDOWN;
207: flush();
208:
209: // release reference to cache
210: cache = null;
211: }
212:
213: /**
214: * Flush to disk.
215: */
216: public final synchronized void flush() {
217: if (cache.isOverflowToDisk()) {
218: if (LOG.isDebugEnabled()) {
219: LOG.debug(cache.getName() + " is persistent. Spooling "
220: + map.size() + " elements to the disk store.");
221: }
222: spoolAllToDisk();
223: // should be empty in any case
224: clear();
225: }
226: }
227:
228: /**
229: * Spools all elements to disk, in preparation for shutdown. <p/> Relies on being called from a synchronized method
230: * <p/> This revised implementation is a little slower but avoids using increased memory during the method.
231: */
232: protected final void spoolAllToDisk() {
233: Object[] keys = getKeyArray();
234: for (int i = 0; i < keys.length; i++) {
235: Element element = (Element) map.get(keys[i]);
236: if (element != null) {
237: if (!element.isSerializable()) {
238: if (LOG.isDebugEnabled()) {
239: LOG
240: .debug("Object with key "
241: + element.getObjectKey()
242: + " is not Serializable and is not being overflowed to disk.");
243: }
244: } else {
245: spoolToDisk(element);
246: // Don't notify listeners. They are not being removed from the cache, only a store
247: remove(keys[i]);
248: }
249: }
250: }
251: }
252:
253: /**
254: * Puts the element in the DiskStore. Should only be called if {@link Ehcache#isOverflowToDisk} is true <p/> Relies on
255: * being called from a synchronized method
256: *
257: * @param element The Element
258: */
259: protected final void spoolToDisk(Element element) {
260: diskStore.put(element);
261: if (LOG.isDebugEnabled()) {
262: LOG.debug(cache.getName()
263: + "Cache: spool to disk done for: "
264: + element.getObjectKey());
265: }
266: }
267:
268: /**
269: * Gets the status of the MemoryStore.
270: */
271: public final Status getStatus() {
272: return status;
273: }
274:
275: /**
276: * Gets an Array of the keys for all elements in the memory cache. <p/> Does not check for expired entries
277: *
278: * @return An Object[]
279: */
280: public final synchronized Object[] getKeyArray() {
281: return map.keySet().toArray();
282: }
283:
284: /**
285: * Returns the current cache size.
286: *
287: * @return The size value
288: */
289: public final int getSize() {
290: return map.size();
291: }
292:
293: /**
294: * An unsynchronized check to see if a key is in the Store. No check is made to see if the Element is expired.
295: *
296: * @param key The Element key
297: * @return true if found. If this method return false, it means that an Element with the given key is definitely not
298: * in the MemoryStore. If it returns true, there is an Element there. An attempt to get it may return null if
299: * the Element has expired.
300: */
301: public final boolean containsKey(Object key) {
302: return map.containsKey(key);
303: }
304:
305: /**
306: * Measures the size of the memory store by measuring the serialized size of all elements. If the objects are not
307: * Serializable they count as 0. <p/> Warning: This method can be very expensive to run. Allow approximately 1 second
308: * per 1MB of entries. Running this method could create liveness problems because the object lock is held for a long
309: * period
310: *
311: * @return the size, in bytes
312: */
313: public final synchronized long getSizeInBytes()
314: throws CacheException {
315: long sizeInBytes = 0;
316: for (Iterator iterator = map.values().iterator(); iterator
317: .hasNext();) {
318: Element element = (Element) iterator.next();
319: if (element != null) {
320: sizeInBytes += element.getSerializedSize();
321: }
322: }
323: return sizeInBytes;
324: }
325:
326: /**
327: * Evict the <code>Element</code>. <p/> Evict means that the <code>Element</code> is:
328: * <ul>
329: * <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore
330: * <li>if not, the <code>Element</code> is removed.
331: * </ul>
332: *
333: * @param element the <code>Element</code> to be evicted.
334: */
335: protected final void evict(Element element) throws CacheException {
336: boolean spooled = false;
337: if (cache.isOverflowToDisk()) {
338: if (!element.isSerializable()) {
339: if (LOG.isDebugEnabled()) {
340: LOG
341: .debug(new StringBuffer("Object with key ")
342: .append(element.getObjectKey())
343: .append(
344: " is not Serializable and cannot be overflowed to disk"));
345: }
346: } else {
347: spoolToDisk(element);
348: spooled = true;
349: }
350: }
351:
352: if (!spooled) {
353: cache.getCacheEventNotificationService()
354: .notifyElementEvicted(element, false);
355: }
356: }
357:
358: /**
359: * Before eviction elements are checked.
360: *
361: * @param element
362: */
363: protected final void notifyExpiry(Element element) {
364: cache.getCacheEventNotificationService().notifyElementExpiry(
365: element, false);
366: }
367:
368: /**
369: * An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
370: */
371: protected final boolean isFull() {
372: return map.size() > cache.getMaxElementsInMemory();
373: }
374:
375: }
|