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 org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import java.util.Map;
26  
27  
28  /**
29   * An implementation of a LruMemoryStore.
30   * <p/>
31   * This uses {@link java.util.LinkedHashMap} as its backing map. It uses the {@link java.util.LinkedHashMap} LRU
32   * feature. LRU for this implementation means least recently accessed.
33   *
34   * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
35   * @version $Id: LruMemoryStore.java 52 2006-04-24 14:50:03Z gregluck $
36   */
37  public final class LruMemoryStore extends MemoryStore {
38      private static final Log LOG = LogFactory.getLog(LruMemoryStore.class.getName());
39  
40      /**
41       * Constructor for the LruMemoryStore object
42       * The backing {@link java.util.LinkedHashMap} is created with LRU by access order.
43       */
44      public LruMemoryStore(Cache cache, DiskStore diskStore) {
45          super(cache, diskStore);
46  
47          try {
48              map = loadMapInstance();
49          } catch (CacheException e) {
50              LOG.error(cache.getName() + "Cache: Cannot start LruMemoryStore. Initial cause was " + e.getMessage(), e);
51          }
52      }
53  
54      /**
55       * Tries to load a {@link java.util.LinkedHashMap} (JDK1.4) and then
56       * tries to load an {@link org.apache.commons.collections.LRUMap}.
57       * <p/>
58       * This way applications running JDK1.4 do not have a dependency
59       * on Apache commons-collections.
60       *
61       * @return a Map, being either {@link java.util.LinkedHashMap} or
62       */
63      private Map loadMapInstance() throws CacheException {
64          //First try to load java.util.LinkedHashMap, which is preferred, but only if not overriden
65          if (System.getProperty("net.sf.ehcache.useLRUMap") == null) {
66  
67              try {
68                  Class.forName("java.util.LinkedHashMap");
69                  Map candidateMap = new SpoolingLinkedHashMap();
70                  if (LOG.isDebugEnabled()) {
71                      LOG.debug(cache.getName() + " Cache: Using SpoolingLinkedHashMap implementation");
72                  }
73                  return candidateMap;
74              } catch (Exception e) {
75                  if (LOG.isDebugEnabled()) {
76                      LOG.debug(cache.getName() + " Cache: Cannot find java.util.LinkedHashMap");
77                  }
78              }
79          }
80  
81          //Secondly, try and load org.apache.commons.collections.LRUMap
82          try {
83              Class.forName("org.apache.commons.collections.LRUMap");
84              Map candidateMap = new SpoolingLRUMap();
85              if (LOG.isDebugEnabled()) {
86                  LOG.debug(cache.getName() + " Cache: Using SpoolingLRUMap implementation");
87              }
88              return candidateMap;
89          } catch (Exception e) {
90              //Give up
91              throw new CacheException(cache.getName()
92                      + "Cache: Cannot find org.apache.commons.collections.LRUMap.");
93          }
94      }
95  
96  
97      /**
98       * An LRU Map implementation based on Apache Commons LRUMap.
99       * <p/>
100      * This is used if {@link java.util.LinkedHashMap} is not found in the classpath.
101      * LinkedHashMap is part of JDK
102      */
103     public final class SpoolingLRUMap extends org.apache.commons.collections.LRUMap {
104 
105         /**
106          * Constructor.
107          * The maximum size is set to {@link Cache#getMaxElementsInMemory}. If the
108          * LRUMap gets bigger than this, {@link #processRemovedLRU} is called.
109          */
110         public SpoolingLRUMap() {
111             setMaximumSize(cache.getMaxElementsInMemory());
112         }
113 
114         /**
115          * Called after the element has been removed.
116          * <p/>
117          * Our choices are to do nothing or spool the element to disk.
118          * <p/>
119          * Note that value will be null when the memory size is set to 0. Thus a null guard is used.
120          *
121          * @param key
122          * @param value
123          */
124         protected final void processRemovedLRU(Object key, Object value) {
125             //Already removed from the map at this point
126             Element element = (Element) value;
127 
128             //When max size is 0
129             if (element == null) {
130                 return;
131             }
132 
133             //check for expiry before going to the trouble of spooling
134             if (cache.isExpired(element)) {
135                 notifyExpiry(element);
136             } else {
137                 evict(element);
138             }
139         }
140     }
141 
142     /**
143      * An extension of LinkedHashMap which overrides {@link #removeEldestEntry}
144      * to persist cache entries to the auxiliary cache before they are removed.
145      * <p/>
146      * This implementation also provides LRU by access order.
147      */
148     public final class SpoolingLinkedHashMap extends java.util.LinkedHashMap {
149         private static final int INITIAL_CAPACITY = 100;
150         private static final float GROWTH_FACTOR = .75F;
151 
152         /**
153          * Default constructor.
154          * Will create an initial capacity of 100, a loading of .75 and
155          * LRU by access order.
156          */
157         public SpoolingLinkedHashMap() {
158             super(INITIAL_CAPACITY, GROWTH_FACTOR, true);
159         }
160 
161         /**
162          * Returns <tt>true</tt> if this map should remove its eldest entry.
163          * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
164          * inserting a new entry into the map.  It provides the implementer
165          * with the opportunity to remove the eldest entry each time a new one
166          * is added.  This is useful if the map represents a cache: it allows
167          * the map to reduce memory consumption by deleting stale entries.
168          * <p/>
169          * Will return true if:
170          * <ol>
171          * <li> the element has expired
172          * <li> the cache size is greater than the in-memory actual.
173          * In this case we spool to disk before returning.
174          * </ol>
175          *
176          * @param eldest The least recently inserted entry in the map, or if
177          *               this is an access-ordered map, the least recently accessed
178          *               entry.  This is the entry that will be removed it this
179          *               method returns <tt>true</tt>.  If the map was empty prior
180          *               to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
181          *               in this invocation, this will be the entry that was just
182          *               inserted; in other words, if the map contains a single
183          *               entry, the eldest entry is also the newest.
184          * @return true if the eldest entry should be removed
185          *         from the map; <tt>false</t> if it should be retained.
186          */
187         protected final boolean removeEldestEntry(Map.Entry eldest) {
188             Element element = (Element) eldest.getValue();
189             return removeLeastRecentlyUsedElement(element);
190         }
191 
192         /**
193          * Relies on being called from a synchronized method
194          *
195          * @param element
196          * @return true if the LRU element should be removed
197          */
198         private boolean removeLeastRecentlyUsedElement(Element element) throws CacheException {
199             //check for expiry and remove before going to the trouble of spooling it
200             if (cache.isExpired(element)) {
201                 notifyExpiry(element);
202                 return true;
203             }
204 
205             if (isFull()) {
206                 evict(element);
207                 return true;
208             } else {
209                 return false;
210             }
211 
212         }
213     }
214 }