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 org.apache.commons.logging.Log;
021: import org.apache.commons.logging.LogFactory;
022:
023: import java.util.Map;
024:
025: /**
026: * An implementation of a LruMemoryStore.
027: * <p/>
028: * This uses {@link java.util.LinkedHashMap} as its backing map. It uses the {@link java.util.LinkedHashMap} LRU
029: * feature. LRU for this implementation means least recently accessed.
030: *
031: * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
032: * @version $Id: LruMemoryStore.java 519 2007-07-27 07:11:45Z gregluck $
033: */
034: public class LruMemoryStore extends MemoryStore {
035: private static final Log LOG = LogFactory
036: .getLog(LruMemoryStore.class.getName());
037:
038: /**
039: * Constructor for the LruMemoryStore object
040: * The backing {@link java.util.LinkedHashMap} is created with LRU by access order.
041: */
042: public LruMemoryStore(Ehcache cache, Store diskStore) {
043: super (cache, diskStore);
044:
045: try {
046: map = loadMapInstance();
047: } catch (CacheException e) {
048: LOG
049: .error(
050: cache.getName()
051: + "Cache: Cannot start LruMemoryStore. Initial cause was "
052: + e.getMessage(), e);
053: }
054: }
055:
056: /**
057: * Tries to load a {@link java.util.LinkedHashMap} (JDK1.4) and then
058: * tries to load an {@link org.apache.commons.collections.LRUMap}.
059: * <p/>
060: * This way applications running JDK1.4 do not have a dependency
061: * on Apache commons-collections.
062: *
063: * @return a Map, being either {@link java.util.LinkedHashMap} or
064: */
065: private Map loadMapInstance() throws CacheException {
066: //First try to load java.util.LinkedHashMap, which is preferred, but only if not overriden
067: if (System.getProperty("net.sf.ehcache.useLRUMap") == null) {
068:
069: try {
070: Class.forName("java.util.LinkedHashMap");
071: Map candidateMap = new SpoolingLinkedHashMap();
072: if (LOG.isDebugEnabled()) {
073: LOG
074: .debug(cache.getName()
075: + " Cache: Using SpoolingLinkedHashMap implementation");
076: }
077: return candidateMap;
078: } catch (Exception e) {
079: if (LOG.isDebugEnabled()) {
080: LOG
081: .debug(cache.getName()
082: + " Cache: Cannot find java.util.LinkedHashMap");
083: }
084: }
085: }
086:
087: //Secondly, try and load org.apache.commons.collections.LRUMap
088: try {
089: Class.forName("org.apache.commons.collections.LRUMap");
090: Map candidateMap = new SpoolingLRUMap();
091: if (LOG.isDebugEnabled()) {
092: LOG
093: .debug(cache.getName()
094: + " Cache: Using SpoolingLRUMap implementation");
095: }
096: return candidateMap;
097: } catch (Exception e) {
098: //Give up
099: throw new CacheException(
100: cache.getName()
101: + "Cache: Cannot find org.apache.commons.collections.LRUMap.");
102: }
103: }
104:
105: /**
106: * An LRU Map implementation based on Apache Commons LRUMap.
107: * <p/>
108: * This is used if {@link java.util.LinkedHashMap} is not found in the classpath.
109: * LinkedHashMap is part of JDK
110: */
111: public final class SpoolingLRUMap extends
112: org.apache.commons.collections.LRUMap {
113:
114: /**
115: * Constructor.
116: * The maximum size is set to {@link Ehcache#getMaxElementsInMemory}. If the
117: * LRUMap gets bigger than this, {@link #processRemovedLRU} is called.
118: */
119: public SpoolingLRUMap() {
120: setMaximumSize(cache.getMaxElementsInMemory());
121: }
122:
123: /**
124: * Called after the element has been removed.
125: * <p/>
126: * Our choices are to do nothing or spool the element to disk.
127: * <p/>
128: * Note that value will be null when the memory size is set to 0. Thus a null guard is used.
129: *
130: * @param key
131: * @param value
132: */
133: protected final void processRemovedLRU(Object key, Object value) {
134: //Already removed from the map at this point
135: Element element = (Element) value;
136:
137: //When max size is 0
138: if (element == null) {
139: return;
140: }
141:
142: //check for expiry before going to the trouble of spooling
143: if (element.isExpired()) {
144: notifyExpiry(element);
145: } else {
146: evict(element);
147: }
148: }
149: }
150:
151: /**
152: * An extension of LinkedHashMap which overrides {@link #removeEldestEntry}
153: * to persist cache entries to the auxiliary cache before they are removed.
154: * <p/>
155: * This implementation also provides LRU by access order.
156: */
157: public final class SpoolingLinkedHashMap extends
158: java.util.LinkedHashMap {
159: private static final int INITIAL_CAPACITY = 100;
160: private static final float GROWTH_FACTOR = .75F;
161:
162: /**
163: * Default constructor.
164: * Will create an initial capacity of 100, a loading of .75 and
165: * LRU by access order.
166: */
167: public SpoolingLinkedHashMap() {
168: super (INITIAL_CAPACITY, GROWTH_FACTOR, true);
169: }
170:
171: /**
172: * Returns <tt>true</tt> if this map should remove its eldest entry.
173: * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
174: * inserting a new entry into the map. It provides the implementer
175: * with the opportunity to remove the eldest entry each time a new one
176: * is added. This is useful if the map represents a cache: it allows
177: * the map to reduce memory consumption by deleting stale entries.
178: * <p/>
179: * Will return true if:
180: * <ol>
181: * <li> the element has expired
182: * <li> the cache size is greater than the in-memory actual.
183: * In this case we spool to disk before returning.
184: * </ol>
185: *
186: * @param eldest The least recently inserted entry in the map, or if
187: * this is an access-ordered map, the least recently accessed
188: * entry. This is the entry that will be removed it this
189: * method returns <tt>true</tt>. If the map was empty prior
190: * to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
191: * in this invocation, this will be the entry that was just
192: * inserted; in other words, if the map contains a single
193: * entry, the eldest entry is also the newest.
194: * @return true if the eldest entry should be removed
195: * from the map; <tt>false</t> if it should be retained.
196: */
197: protected final boolean removeEldestEntry(Map.Entry eldest) {
198: Element element = (Element) eldest.getValue();
199: return removeLeastRecentlyUsedElement(element);
200: }
201:
202: /**
203: * Relies on being called from a synchronized method
204: *
205: * @param element
206: * @return true if the LRU element should be removed
207: */
208: private boolean removeLeastRecentlyUsedElement(Element element)
209: throws CacheException {
210: //check for expiry and remove before going to the trouble of spooling it
211: if (element.isExpired()) {
212: notifyExpiry(element);
213: return true;
214: }
215:
216: if (isFull()) {
217: evict(element);
218: return true;
219: } else {
220: return false;
221: }
222:
223: }
224: }
225: }
|