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.constructs.blocking;
18
19 import net.sf.ehcache.CacheException;
20 import net.sf.ehcache.Ehcache;
21 import net.sf.ehcache.Element;
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 import java.io.Serializable;
26 import java.util.Collection;
27 import java.util.Iterator;
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.java 512 2007-07-10 09:18:45Z gregluck $
44 */
45 public class SelfPopulatingCache extends BlockingCache {
46 private static final Log LOG = LogFactory.getLog(SelfPopulatingCache.class.getName());
47
48 /**
49 * A factory for creating entries, given a key
50 */
51 protected final CacheEntryFactory factory;
52
53 /**
54 * Creates a SelfPopulatingCache.
55 */
56 public SelfPopulatingCache(final Ehcache cache, final CacheEntryFactory factory) throws CacheException {
57 super(cache);
58 this.factory = factory;
59 }
60
61 /**
62 * Looks up an entry. creating it if not found.
63 */
64 public Element get(final Object key) throws LockTimeoutException {
65
66 String oldThreadName = Thread.currentThread().getName();
67 setThreadName("get", key);
68
69 try {
70 //if null will lock here
71 Element element = super.get(key);
72
73 if (element == null) {
74 // Value not cached - fetch it
75 Object value = factory.createEntry(key);
76 setThreadName("put", key);
77 element = new Element(key, value);
78 put(element);
79 }
80 return element;
81
82 } catch (LockTimeoutException e) {
83 //do not release the lock, because you never acquired it
84 String message = "Timeout after " + timeoutMillis + " waiting on another thread " +
85 "to fetch object for cache entry \"" + key + "\".";
86 try {
87 throw new LockTimeoutException(message, e);
88 } catch (NoSuchMethodError noSuchMethodError) {
89 //Running 1.3 or lower
90 throw new LockTimeoutException(message);
91 }
92
93
94 } catch (final Throwable throwable) {
95 // Could not fetch - Ditch the entry from the cache and rethrow
96
97 setThreadName("put", key);
98 //release the lock you acquired
99 put(new Element(key, null));
100
101 try {
102 throw new CacheException("Could not fetch object for cache entry \"" + key + "\".", throwable);
103 } catch (NoSuchMethodError noSuchMethodError) {
104 //Running 1.3 or lower
105 throw new CacheException("Could not fetch object for cache entry \"" + key + "\".");
106 }
107
108
109 } finally {
110 Thread.currentThread().setName(oldThreadName);
111 }
112 }
113
114 /**
115 * Rename the thread for easier thread dump reading.
116 *
117 * @param method the method about to be called
118 * @param key the key being operated on
119 */
120 protected void setThreadName(String method, final Object key) {
121 StringBuffer threadName = new StringBuffer(getName()).append(": ").append(method).append("(").append(key).append(")");
122 Thread.currentThread().setName(threadName.toString());
123 }
124
125 /**
126 * Refresh the elements of this cache.
127 * <p/>
128 * Refreshes bypass the {@link BlockingCache} and act directly on the backing {@link Ehcache}.
129 * This way, {@link BlockingCache} gets can continue to return stale data while the refresh, which
130 * might be expensive, takes place.
131 * <p/>
132 * Quiet methods are used, so that statistics are not affected.
133 * <p/>
134 * Threads entering this method are temporarily renamed, so that a Thread Dump will show
135 * meaningful information.
136 * <p/>
137 * Configure ehcache.xml to stop elements from being refreshed forever:
138 * <ul>
139 * <li>use timeToIdle to discard elements unused for a period of time
140 * <li>use timeToLive to discard elmeents that have existed beyond their allotted lifespan
141 * </ul>
142 */
143 public void refresh() throws CacheException {
144 final String oldThreadName = Thread.currentThread().getName();
145 Exception exception = null;
146
147 // Refetch the entries
148 final Collection keys = getKeys();
149
150 if (LOG.isTraceEnabled()) {
151 LOG.trace(getName() + ": found " + keys.size() + " keys to refresh");
152 }
153
154 // perform the refresh
155 for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
156 final Serializable key = (Serializable) iterator.next();
157
158 try {
159 Ehcache backingCache = getCache();
160 final Element element = backingCache.getQuiet(key);
161
162 if (element == null) {
163 if (LOG.isTraceEnabled()) {
164 LOG.trace(getName() + ": entry with key " + key + " has been removed - skipping it");
165 }
166
167 continue;
168 }
169
170 refreshElement(element, backingCache);
171 } catch (final Exception e) {
172 // Collect the exception and keep going.
173 // Throw the exception once all the entries have been refreshed
174 // If the refresh fails, keep the old element. It will simply become staler.
175 LOG.warn(getName() + "Could not refresh element " + key, e);
176 exception = e;
177 } finally {
178 Thread.currentThread().setName(oldThreadName);
179 }
180 }
181
182 if (exception != null) {
183 throw new CacheException(exception.getMessage());
184 }
185 }
186
187 /**
188 * Refresh a single element.
189 *
190 * @param element the Element to refresh
191 * @param backingCache the underlying {@link Ehcache}.
192 * @throws Exception
193 */
194 protected void refreshElement(final Element element, Ehcache backingCache)
195 throws Exception {
196 Object key = element.getObjectKey();
197
198 if (LOG.isTraceEnabled()) {
199 setThreadName("refreshElement", key);
200 LOG.trace(getName() + ": refreshing element with key " + key);
201 }
202
203 final Element replacementElement;
204
205 if (factory instanceof UpdatingCacheEntryFactory) {
206 //update the value of the cloned Element in place
207 replacementElement = element;
208 ((UpdatingCacheEntryFactory) factory).updateEntryValue(key, replacementElement.getValue());
209
210 //put the updated element back into the backingCache, without updating stats
211 //It is not usually necessary to do this. We do this in case the element expired
212 //or idles out of the backingCache. In that case we hold a reference to it but the
213 // backingCache no longer does.
214 } else {
215 final Object value = factory.createEntry(key);
216 replacementElement = new Element(key, value);
217 }
218
219 backingCache.putQuiet(replacementElement);
220 }
221 }