View Javadoc

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                  // Value not cached - fetch it
73                  Object value = factory.createEntry(key);
74                  element = makeAndCheckElement(key, value);
75              } catch (final Throwable throwable) {
76                  // Could not fetch - Ditch the entry from the cache and rethrow
77                  // release the lock you acquired
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         // Refetch the entries
134         final Collection keys = getKeys();
135 
136         LOG.debug(getName() + ": found " + keys.size() + " keys to refresh");
137 
138         // perform the refresh
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                 // Collect the exception and keep going.
156                 // Throw the exception once all the entries have been refreshed
157                 // If the refresh fails, keep the old element. It will simply become staler.
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                 //need to create
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             //update the value of the cloned Element in place
258             replacementElement = element;
259             ((UpdatingCacheEntryFactory) factory).updateEntryValue(key, replacementElement.getObjectValue());
260 
261             //put the updated element back into the backingCache, without updating stats
262             //It is not usually necessary to do this. We do this in case the element expired
263             //or idles out of the backingCache. In that case we hold a reference to it but the
264             // backingCache no longer does.
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         //simply build a new element using the supplied key
291         if (!(value instanceof Element)) {
292             return new Element(key, value);
293         }
294 
295         //It is already an element - perform sanity checks
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 }