View Javadoc

1   /**
2    *  Copyright 2003-2006 Greg Luck
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  
17  package net.sf.ehcache.store;
18  
19  import net.sf.ehcache.Cache;
20  import net.sf.ehcache.CacheException;
21  import net.sf.ehcache.Element;
22  import net.sf.ehcache.Status;
23  import net.sf.ehcache.event.RegisteredEventListeners;
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  
27  import java.util.Iterator;
28  import java.util.Map;
29  
30  /**
31   * An abstract class for the Memory Stores. All Memory store implementations for different
32   * policies (e.g: FIFO, LFU, LRU, etc.) should extend this class.
33   *
34   * @author <a href="mailto:ssuravarapu@users.sourceforge.net">Surya Suravarapu</a>
35   * @version $Id: MemoryStore.java 52 2006-04-24 14:50:03Z gregluck $
36   */
37  public abstract class MemoryStore implements Store {
38  
39      private static final Log LOG = LogFactory.getLog(MemoryStore.class.getName());
40  
41      /**
42       * The cache this store is associated with.
43       */
44      protected Cache cache;
45  
46      /**
47       * Map where items are stored by key.
48       */
49      protected Map map;
50  
51      /**
52       * The DiskStore associated with this MemoryStore.
53       */
54      protected final DiskStore diskStore;
55  
56      /**
57       * status.
58       */
59      protected Status status;
60  
61      /**
62       * Constructs things that all MemoryStores have in common.
63       *
64       * @param cache
65       * @param diskStore
66       */
67      protected MemoryStore(Cache cache, DiskStore diskStore) {
68          status = Status.STATUS_UNINITIALISED;
69          this.cache = cache;
70          this.diskStore = diskStore;
71          status = Status.STATUS_ALIVE;
72  
73          LOG.debug("Initialized " + this.getClass().getName() + " for " + cache.getName());
74      }
75  
76  
77      /**
78       * A factory method to create a MemoryStore.
79       *
80       * @param cache
81       * @param diskStore
82       * @return an instance of a MemoryStore, configured with the appropriate eviction policy
83       */
84      public static MemoryStore create(Cache cache, DiskStore diskStore) {
85          MemoryStore memoryStore = null;
86          MemoryStoreEvictionPolicy policy = cache.getMemoryStoreEvictionPolicy();
87  
88          if (policy.equals(MemoryStoreEvictionPolicy.LRU)) {
89              memoryStore = new LruMemoryStore(cache, diskStore);
90          } else if (policy.equals(MemoryStoreEvictionPolicy.FIFO)) {
91              memoryStore = new FifoMemoryStore(cache, diskStore);
92          } else if (policy.equals(MemoryStoreEvictionPolicy.LFU)) {
93              memoryStore = new LfuMemoryStore(cache, diskStore);
94          }
95          return memoryStore;
96      }
97  
98      /**
99       * Puts an item in the cache. Note that this automatically results in
100      * {@link net.sf.ehcache.store.LruMemoryStore.SpoolingLinkedHashMap#removeEldestEntry} being called.
101      *
102      * @param element the element to add
103      */
104     public final synchronized void put(Element element) throws CacheException {
105         if (element != null) {
106             map.put(element.getObjectKey(), element);
107             doPut(element);
108         }
109     }
110 
111     /**
112      * Allow specialised actions over adding the element to the map.
113      *
114      * @param element
115      */
116     protected void doPut(Element element) throws CacheException {
117         //empty
118     }
119 
120     /**
121      * Gets an item from the cache.
122      * <p/>
123      * The last access time in {@link net.sf.ehcache.Element} is updated.
124      *
125      * @param key the cache key
126      * @return the element, or null if there was no match for the key
127      */
128     public final synchronized Element get(Object key) {
129         Element element = (Element) map.get(key);
130 
131         if (element != null) {
132             element.updateAccessStatistics();
133             if (LOG.isTraceEnabled()) {
134                 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore hit for " + key);
135             }
136         } else if (LOG.isTraceEnabled()) {
137             LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore miss for " + key);
138         }
139         return element;
140     }
141 
142     /**
143      * Gets an item from the cache, without updating Element statistics.
144      *
145      * @param key the cache key
146      * @return the element, or null if there was no match for the key
147      */
148     public final synchronized Element getQuiet(Object key) {
149         Element cacheElement = (Element) map.get(key);
150 
151         if (cacheElement != null) {
152             //cacheElement.updateAccessStatistics(); Don't update statistics
153             if (LOG.isTraceEnabled()) {
154                 LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore hit for " + key);
155             }
156         } else if (LOG.isTraceEnabled()) {
157             LOG.trace(cache.getName() + "Cache: " + cache.getName() + "MemoryStore miss for " + key);
158         }
159         return cacheElement;
160     }
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() + "Cache: Cannot remove entry as key " + key + " was not found");
178             }
179             return null;
180         }
181     }
182 
183     /**
184      * Remove all of the elements from the store.
185      * <p/>
186      * If there are registered <code>CacheEventListener</code>s they are notified of the expiry or removal
187      * of the <code>Element</code> as each is removed.
188      */
189     public final synchronized void removeAll() throws CacheException {
190         notifyingRemoveAll();
191         clear();
192     }
193 
194     /**
195      * Clears any data structures and places it back to its state when it was first created.
196      */
197     protected final void clear() {
198         map.clear();
199     }
200 
201     /**
202      * If there are registered <code>CacheEventListener</code>s they are notified of the expiry or removal
203      * of the <code>Element</code> as each is removed.
204      */
205     protected final void notifyingRemoveAll() throws CacheException {
206         RegisteredEventListeners listeners = cache.getCacheEventNotificationService();
207         if (!listeners.getCacheEventListeners().isEmpty()) {
208             Object[] keys = getKeyArray();
209             for (int i = 0; i < keys.length; i++) {
210                 Object key = keys[i];
211                 Element element = remove(key);
212                 if (cache.isExpired(element)) {
213                     listeners.notifyElementExpiry(element, false);
214                 } else {
215                     listeners.notifyElementRemoved(element, false);
216                 }
217             }
218         }
219     }
220 
221     /**
222      * Prepares for shutdown.
223      */
224     public final synchronized void dispose() {
225         if (status.equals(Status.STATUS_SHUTDOWN)) {
226             return;
227         }
228         status = Status.STATUS_SHUTDOWN;
229         flush();
230 
231         //release reference to cache
232         cache = null;
233     }
234 
235     /**
236      * Flush to disk.
237      */
238     public final synchronized void flush() {
239         if (cache.isOverflowToDisk()) {
240             if (LOG.isDebugEnabled()) {
241                 LOG.debug(cache.getName() + " is persistent. Spooling " + map.size() + " elements to the disk store.");
242             }
243             spoolAllToDisk();
244             //should be empty in any case
245             clear();
246         }
247     }
248 
249     /**
250      * Spools all elements to disk, in preparation for shutdown.
251      * <p/>
252      * Relies on being called from a synchronized method
253      * <p/>
254      * This revised implementation is a little slower but avoids using increased memory during the method.
255      */
256     protected final void spoolAllToDisk() {
257         Object[] keys = getKeyArray();
258         for (int i = 0; i < keys.length; i++) {
259             Element element = (Element) map.get(keys[i]);
260 
261             if (!element.isSerializable()) {
262                 if (LOG.isDebugEnabled()) {
263                     LOG.debug("Object with key " + element.getObjectKey()
264                             + " is not Serializable and is not being overflowed to disk.");
265                 }
266             } else {
267                 spoolToDisk(element);
268                 //Don't notify listeners. They are not being removed from the cache, only a store
269                 remove(keys[i]);
270             }
271         }
272 
273     }
274 
275     /**
276      * Puts the element in the DiskStore.
277      * Should only be called if {@link Cache#isOverflowToDisk} is true
278      * <p/>
279      * Relies on being called from a synchronized method
280      *
281      * @param element The Element
282      */
283     protected final void spoolToDisk(Element element) {
284         diskStore.put(element);
285         if (LOG.isDebugEnabled()) {
286             LOG.debug(cache.getName() + "Cache: spool to disk done for: " + element.getObjectKey());
287         }
288     }
289 
290     /**
291      * Gets the status of the MemoryStore.
292      */
293     public final Status getStatus() {
294         return status;
295     }
296 
297     /**
298      * Gets an Array of the keys for all elements in the memory cache.
299      * <p/>
300      * Does not check for expired entries
301      *
302      * @return An Object[]
303      */
304     public final synchronized Object[] getKeyArray() {
305         return map.keySet().toArray();
306     }
307 
308     /**
309      * Returns the current cache size.
310      *
311      * @return The size value
312      */
313     public final int getSize() {
314         return map.size();
315     }
316 
317 
318     /**
319      * An unsynchronized check to see if a key is in the Store. No check is made to see if the Element is expired.
320      *
321      * @param key The Element key
322      * @return true if found. If this method return false, it means that an Element with the given key is definitely not in the MemoryStore.
323      *         If it returns true, there is an Element there. An attempt to get it may return null if the Element has expired.
324      */
325     public final boolean containsKey(Object key) {
326         return map.containsKey(key);
327     }
328 
329 
330     /**
331      * Measures the size of the memory store by measuring the serialized size of all elements.
332      * <p/>
333      * Warning: This method can be very expensive to run. Allow approximately 1 second
334      * per 1MB of entries. Running this method could create liveness problems
335      * because the object lock is held for a long period
336      *
337      * @return the size, in bytes
338      */
339     public final synchronized long getSizeInBytes() throws CacheException {
340         long sizeInBytes = 0;
341         for (Iterator iterator = map.values().iterator(); iterator.hasNext();) {
342             Element element = (Element) iterator.next();
343             if (element != null) {
344                 sizeInBytes += element.getSerializedSize();
345             }
346         }
347         return sizeInBytes;
348     }
349 
350 
351     /**
352      * Evict the <code>Element</code>.
353      * <p/>
354      * Evict means that the <code>Element</code> is:
355      * <ul>
356      * <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore
357      * <li>if not, the <code>Element</code> is removed.
358      * </ul>
359      *
360      * @param element the <code>Element</code> to be evicted.
361      */
362     protected final void evict(Element element) throws CacheException {
363         boolean spooled = false;
364         if (cache.isOverflowToDisk()) {
365             if (!element.isSerializable()) {
366                 if (LOG.isDebugEnabled()) {
367                     LOG.debug(new StringBuffer("Object with key ").append(element.getObjectKey())
368                             .append(" is not Serializable and cannot be overflowed to disk"));
369                 }
370             } else {
371                 spoolToDisk(element);
372                 spooled = true;
373             }
374         }
375 
376         if (!spooled) {
377             cache.getCacheEventNotificationService().notifyElementRemoved(element, false);
378         }
379     }
380 
381     /**
382      * Before eviction elements are checked.
383      *
384      * @param element
385      */
386     protected final void notifyExpiry(Element element) {
387         cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
388     }
389 
390     /**
391      * An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
392      */
393     protected final boolean isFull() {
394         return map.size() > cache.getMaxElementsInMemory();
395     }
396 
397 }