1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
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.constructs.blocking;
18
19 import net.sf.ehcache.CacheException;
20 import net.sf.ehcache.Ehcache;
21 import net.sf.ehcache.Element;
22
23 import java.util.Collection;
24 import java.util.Iterator;
25
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29
30 /***
31 * A selfpopulating decorator for {@link Ehcache} that creates entries on demand.
32 * <p/>
33 * Clients of the cache simply call it without needing knowledge of whether
34 * the entry exists in the cache.
35 * <p/>
36 * The cache is designed to be refreshed. Refreshes operate on the backing cache, and do not
37 * degrade performance of {@link #get(java.io.Serializable)} calls.
38 * <p/>
39 * Thread safety depends on the factory being used. The UpdatingCacheEntryFactory should be made
40 * thread safe. In addition users of returned values should not modify their contents.
41 *
42 * @author Greg Luck
43 * @version $Id: SelfPopulatingCache.html 13146 2011-08-01 17:12:39Z oletizi $
44 */
45 public class SelfPopulatingCache extends BlockingCache {
46
47 private static final Logger LOG = LoggerFactory.getLogger(SelfPopulatingCache.class.getName());
48
49 /***
50 * A factory for creating entries, given a key
51 */
52 protected final CacheEntryFactory factory;
53
54 /***
55 * Creates a SelfPopulatingCache.
56 */
57 public SelfPopulatingCache(final Ehcache cache, final CacheEntryFactory factory) throws CacheException {
58 super(cache);
59 this.factory = factory;
60 }
61
62 /***
63 * Looks up an entry. creating it if not found.
64 */
65 @Override
66 public Element get(final Object key) throws LockTimeoutException {
67
68 Element element = super.get(key);
69
70 if (element == null) {
71 try {
72
73 Object value = factory.createEntry(key);
74 element = makeAndCheckElement(key, value);
75 } catch (final Throwable throwable) {
76
77
78 element = new Element(key, null);
79 throw new CacheException("Could not fetch object for cache entry with key \"" + key + "\".", throwable);
80 } finally {
81 put(element);
82 }
83 }
84 return element;
85 }
86
87 /***
88 * Refresh the elements of this cache.
89 * <p/>
90 * Refreshes bypass the {@link BlockingCache} and act directly on the backing {@link Ehcache}.
91 * This way, {@link BlockingCache} gets can continue to return stale data while the refresh, which
92 * might be expensive, takes place.
93 * <p/>
94 * Quiet methods are used, so that statistics are not affected.
95 * Note that the refreshed elements will not be replicated to any cache peers.
96 * <p/>
97 * Configure ehcache.xml to stop elements from being refreshed forever:
98 * <ul>
99 * <li>use timeToIdle to discard elements unused for a period of time
100 * <li>use timeToLive to discard elmeents that have existed beyond their allotted lifespan
101 * </ul>
102 *
103 * @throws CacheException
104 */
105 public void refresh() throws CacheException {
106 refresh(true);
107 }
108
109 /***
110 * Refresh the elements of this cache.
111 * <p/>
112 * Refreshes bypass the {@link BlockingCache} and act directly on the backing {@link Ehcache}.
113 * This way, {@link BlockingCache} gets can continue to return stale data while the refresh, which
114 * might be expensive, takes place.
115 * <p/>
116 * Quiet methods are used if argument 0 is true, so that statistics are not affected,
117 * but note that replication will then not occur
118 * <p/>
119 * Configure ehcache.xml to stop elements from being refreshed forever:
120 * <ul>
121 * <li>use timeToIdle to discard elements unused for a period of time
122 * <li>use timeToLive to discard elmeents that have existed beyond their allotted lifespan
123 * </ul>
124 *
125 * @param quiet whether the backing cache is quietly updated or not, if true replication will not occur
126 * @throws CacheException
127 * @since 1.6.1
128 */
129 public void refresh(boolean quiet) throws CacheException {
130 Exception exception = null;
131 Object keyWithException = null;
132
133
134 final Collection keys = getKeys();
135
136 LOG.debug(getName() + ": found " + keys.size() + " keys to refresh");
137
138
139 for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
140 final Object key = iterator.next();
141
142 try {
143 Ehcache backingCache = getCache();
144 final Element element = backingCache.getQuiet(key);
145
146 if (element == null) {
147 if (LOG.isDebugEnabled()) {
148 LOG.debug(getName() + ": entry with key " + key + " has been removed - skipping it");
149 }
150 continue;
151 }
152
153 refreshElement(element, backingCache, quiet);
154 } catch (final Exception e) {
155
156
157
158 LOG.warn(getName() + "Could not refresh element " + key, e);
159 keyWithException = key;
160 exception = e;
161 }
162 }
163
164 if (exception != null) {
165 throw new CacheException(exception.getMessage() + " on refresh with key " + keyWithException, exception);
166 }
167 }
168
169 /***
170 * Refresh a single element.
171 * <p/>
172 * Refreshes bypass the {@link BlockingCache} and act directly on the backing {@link Ehcache}.
173 * This way, {@link BlockingCache} gets can continue to return stale data while the refresh, which
174 * might be expensive, takes place.
175 * <p/>
176 * If the element is absent it is created
177 * <p/>
178 * Quiet methods are used, so that statistics are not affected.
179 * Note that the refreshed element will not be replicated to any cache peers.
180 *
181 * @param key
182 * @return the refreshed Element
183 * @throws CacheException
184 * @since 1.6.1
185 */
186 public Element refresh(Object key) throws CacheException {
187 return refresh(key, true);
188 }
189
190 /***
191 * Refresh a single element.
192 * <p/>
193 * Refreshes bypass the {@link BlockingCache} and act directly on the backing {@link Ehcache}.
194 * This way, {@link BlockingCache} gets can continue to return stale data while the refresh, which
195 * might be expensive, takes place.
196 * <p/>
197 * If the element is absent it is created
198 * <p/>
199 * Quiet methods are used if argument 1 is true, so that statistics are not affected,
200 * but note that replication will then not occur
201 *
202 * @param key
203 * @param quiet whether the backing cache is quietly updated or not,
204 * if true replication will not occur
205 * @return the refreshed Element
206 * @throws CacheException
207 * @since 1.6.1
208 */
209 public Element refresh(Object key, boolean quiet) throws CacheException {
210 try {
211 Ehcache backingCache = getCache();
212 Element element = backingCache.getQuiet(key);
213 if (element != null) {
214 return refreshElement(element, backingCache, quiet);
215 } else {
216
217 return get(key);
218 }
219 } catch (CacheException ce) {
220 throw ce;
221 } catch (Exception e) {
222 throw new CacheException(e.getMessage() + " on refresh with key " + key, e);
223 }
224 }
225
226 /***
227 * Refresh a single element.
228 *
229 * @param element the Element to refresh
230 * @param backingCache the underlying {@link Ehcache}.
231 * @throws Exception
232 */
233 protected void refreshElement(final Element element, Ehcache backingCache) throws Exception {
234 refreshElement(element, backingCache, true);
235 }
236
237 /***
238 * Refresh a single element.
239 *
240 * @param element the Element to refresh
241 * @param backingCache the underlying {@link Ehcache}.
242 * @param quiet whether to use putQuiet or not, if true replication will not occur
243 * @return the refreshed Element
244 * @throws Exception
245 * @since 1.6.1
246 */
247 protected Element refreshElement(final Element element, Ehcache backingCache, boolean quiet) throws Exception {
248 Object key = element.getObjectKey();
249
250 if (LOG.isDebugEnabled()) {
251 LOG.debug(getName() + ": refreshing element with key " + key);
252 }
253
254 final Element replacementElement;
255
256 if (factory instanceof UpdatingCacheEntryFactory) {
257
258 replacementElement = element;
259 ((UpdatingCacheEntryFactory) factory).updateEntryValue(key, replacementElement.getObjectValue());
260
261
262
263
264
265 } else {
266 final Object value = factory.createEntry(key);
267 replacementElement = makeAndCheckElement(key, value);
268 }
269
270 if (quiet) {
271 backingCache.putQuiet(replacementElement);
272 } else {
273 backingCache.put(replacementElement);
274 }
275 return replacementElement;
276 }
277
278 /***
279 * Both CacheEntryFactory can return an Element rather than just a regular value
280 * this method test this, making a fresh Element otherwise. It also enforces
281 * the rule that the CacheEntryFactory must provide the same key (via equals()
282 * not necessarily same object) if it is returning an Element.
283 *
284 * @param key
285 * @param value
286 * @return the Element to be put back in the cache
287 * @throws CacheException for various illegal states which could be harmful
288 */
289 protected static Element makeAndCheckElement(Object key, Object value) throws CacheException {
290
291 if (!(value instanceof Element)) {
292 return new Element(key, value);
293 }
294
295
296 Element element = (Element) value;
297 if ((element.getObjectKey() == null) && (key == null)) {
298 return element;
299 } else if (element.getObjectKey() == null) {
300 throw new CacheException("CacheEntryFactory returned an Element with a null key");
301 } else if (!element.getObjectKey().equals(key)) {
302 throw new CacheException("CacheEntryFactory returned an Element with a different key: " +
303 element.getObjectKey() + " compared to the key that was requested: " + key);
304 } else {
305 return element;
306 }
307 }
308
309 }