View Javadoc

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 }