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