View Javadoc

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