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
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
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
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
126 Element element = (Element) value;
127
128
129 if (element == null) {
130 return;
131 }
132
133
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
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 }