View Javadoc
1   package org.apache.commons.jcs.utils.access;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.concurrent.ConcurrentHashMap;
23  import java.util.concurrent.ConcurrentMap;
24  
25  import org.apache.commons.jcs.JCS;
26  import org.apache.commons.jcs.access.CacheAccess;
27  import org.apache.commons.jcs.access.GroupCacheAccess;
28  import org.apache.commons.jcs.access.exception.CacheException;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  
32  /**
33   * Utility class to encapsulate doing a piece of work, and caching the results
34   * in JCS. Simply construct this class with the region name for the Cache and
35   * keep a static reference to it instead of the JCS itself. Then make a new
36   * org.apache.commons.jcs.utils.access.AbstractJCSWorkerHelper and implement Object
37   * doWork() and do the work in there, returning the object to be cached. Then
38   * call .getResult() with the key and the AbstractJCSWorkerHelper to get the
39   * result of the work. If the object isn't already in the Cache,
40   * AbstractJCSWorkerHelper.doWork() will get called, and the result will be put
41   * into the cache. If the object is already in cache, the cached result will be
42   * returned instead.
43   * <p>
44   * As an added bonus, multiple JCSWorkers with the same region, and key won't do
45   * the work multiple times: The first JCSWorker to get started will do the work,
46   * and all subsequent workers with the same region, group, and key will wait on
47   * the first one and use his resulting work instead of doing the work
48   * themselves.
49   * <p>
50   * This is ideal when the work being done is a query to the database where the
51   * results may take time to be retrieved.
52   * <p>
53   * For example:
54   *
55   * <pre>
56   *      public static JCSWorker cachingWorker = new JCSWorker(&quot;example region&quot;);
57   *   		public Object getSomething(Serializable aKey){
58   *        JCSWorkerHelper helper = new AbstractJCSWorkerHelper(){
59   *          public Object doWork(){
60   *            // Do some (DB?) work here which results in a list
61   *            // This only happens if the cache dosn't have a item in this region for aKey
62   *            // Note this is especially useful with Hibernate, which will cache indiviual
63   *            // Objects, but not entire query result sets.
64   *            List results = query.list();
65   *            // Whatever we return here get's cached with aKey, and future calls to
66   *            // getResult() on a CachedWorker with the same region and key will return that instead.
67   *            return results;
68   *        };
69   *        List result = worker.getResult(aKey, helper);
70   *      }
71   * </pre>
72   *
73   * This is essentially the same as doing:
74   *
75   * <pre>
76   * JCS jcs = JCS.getInstance( &quot;exampleregion&quot; );
77   * List results = (List) jcs.get( aKey );
78   * if ( results != null )
79   * {
80   *     //do the work here
81   *     results = query.list();
82   *     jcs.put( aKey, results );
83   * }
84   * </pre>
85   *
86   * <p>
87   * But has the added benefit of the work-load sharing; under normal
88   * circumstances if multiple threads all tried to do the same query at the same
89   * time, the same query would happen multiple times on the database, and the
90   * resulting object would get put into JCS multiple times.
91   * <p>
92   * @author Travis Savo
93   */
94  public class JCSWorker<K, V>
95  {
96      /** The logger */
97      private static final Log logger = LogFactory.getLog( JCSWorker.class );
98  
99      /** The cache we are working with */
100     private CacheAccess<K, V> cache;
101 
102     /** The cache we are working with */
103     private GroupCacheAccess<K, V> groupCache;
104 
105     /**
106      * Map to hold who's doing work presently.
107      */
108     private volatile ConcurrentMap<String, JCSWorkerHelper<V>> map = new ConcurrentHashMap<String, JCSWorkerHelper<V>>();
109 
110     /**
111      * Region for the JCS cache.
112      */
113     private final String region;
114 
115     /**
116      * Constructor which takes a region for the JCS cache.
117      * @param aRegion
118      *            The Region to use for the JCS cache.
119      */
120     public JCSWorker( final String aRegion )
121     {
122         region = aRegion;
123         try
124         {
125             cache = JCS.getInstance( aRegion );
126             groupCache = JCS.getGroupCacheInstance( aRegion );
127         }
128         catch ( CacheException e )
129         {
130             throw new RuntimeException( e.getMessage() );
131         }
132     }
133 
134     /**
135      * Getter for the region of the JCS Cache.
136      * @return The JCS region in which the result will be cached.
137      */
138     public String getRegion()
139     {
140         return region;
141     }
142 
143     /**
144      * Gets the cached result for this region/key OR does the work and caches
145      * the result, returning the result. If the result has not been cached yet,
146      * this calls doWork() on the JCSWorkerHelper to do the work and cache the
147      * result. This is also an opportunity to do any post processing of the
148      * result in your CachedWorker implementation.
149      * @param aKey
150      *            The key to get/put with on the Cache.
151      * @param aWorker
152      *            The JCSWorkerHelper implementing Object doWork(). This gets
153      *            called if the cache get misses, and the result is put into
154      *            cache.
155      * @return The result of doing the work, or the cached result.
156      * @throws Exception
157      *             Throws an exception if anything goes wrong while doing the
158      *             work.
159      */
160     public V getResult( K aKey, JCSWorkerHelper<V> aWorker )
161         throws Exception
162     {
163         return run( aKey, null, aWorker );
164     }
165 
166     /**
167      * Gets the cached result for this region/key OR does the work and caches
168      * the result, returning the result. If the result has not been cached yet,
169      * this calls doWork() on the JCSWorkerHelper to do the work and cache the
170      * result. This is also an opportunity to do any post processing of the
171      * result in your CachedWorker implementation.
172      * @param aKey
173      *            The key to get/put with on the Cache.
174      * @param aGroup
175      *            The cache group to put the result in.
176      * @param aWorker
177      *            The JCSWorkerHelper implementing Object doWork(). This gets
178      *            called if the cache get misses, and the result is put into
179      *            cache.
180      * @return The result of doing the work, or the cached result.
181      * @throws Exception
182      *             Throws an exception if anything goes wrong while doing the
183      *             work.
184      */
185     public V getResult( K aKey, String aGroup, JCSWorkerHelper<V> aWorker )
186         throws Exception
187     {
188         return run( aKey, aGroup, aWorker );
189     }
190 
191     /**
192      * Try and get the object from the cache, and if it's not there, do the work
193      * and cache it. This also ensures that only one CachedWorker is doing the
194      * work and subsequent calls to a CachedWorker with identical
195      * region/key/group will wait on the results of this call. It will call the
196      * JCSWorkerHelper.doWork() if the cache misses, and will put the result.
197      * @param aKey
198      * @param aGroup
199      * @param aHelper
200      * @return Either the result of doing the work, or the cached result.
201      * @throws Exception
202      *             If something goes wrong while doing the work, throw an
203      *             exception.
204      */
205     private V run( K aKey, String aGroup, JCSWorkerHelper<V> aHelper )
206         throws Exception
207     {
208         V result = null;
209         // long start = 0;
210         // long dbTime = 0;
211         JCSWorkerHelper<V> helper = map.putIfAbsent(getRegion() + aKey, aHelper);
212 
213         if ( helper != null )
214         {
215             synchronized ( helper )
216             {
217                 if ( logger.isDebugEnabled() )
218                 {
219                     logger.debug( "Found a worker already doing this work (" + getRegion() + ":" + aKey + ")." );
220                 }
221                 while ( !helper.isFinished() )
222                 {
223                     try
224                     {
225                         helper.wait();
226                     }
227                     catch (InterruptedException e)
228                     {
229                         // expected
230                     }
231                 }
232                 if ( logger.isDebugEnabled() )
233                 {
234                     logger.debug( "Another thread finished our work for us. Using those results instead. ("
235                         + getRegion() + ":" + aKey + ")." );
236                 }
237             }
238         }
239         // Do the work
240         try
241         {
242             if ( logger.isDebugEnabled() )
243             {
244                 logger.debug( getRegion() + " is doing the work." );
245             }
246 
247             // Try to get the item from the cache
248             if ( aGroup != null )
249             {
250                 result = groupCache.getFromGroup( aKey, aGroup );
251             }
252             else
253             {
254                 result = cache.get( aKey );
255             }
256             // If the cache dosn't have it, do the work.
257             if ( result == null )
258             {
259                 result = aHelper.doWork();
260                 if ( logger.isDebugEnabled() )
261                 {
262                     logger.debug( "Work Done, caching: key:" + aKey + ", group:" + aGroup + ", result:" + result + "." );
263                 }
264                 // Stick the result of the work in the cache.
265                 if ( aGroup != null )
266                 {
267                     groupCache.putInGroup( aKey, aGroup, result );
268                 }
269                 else
270                 {
271                     cache.put( aKey, result );
272                 }
273             }
274             // return the result
275             return result;
276         }
277         finally
278         {
279             if ( logger.isDebugEnabled() )
280             {
281                 logger.debug( getRegion() + ":" + aKey + " entered finally." );
282             }
283 
284             // Remove ourselves as the worker.
285             if ( helper == null )
286             {
287                 map.remove( getRegion() + aKey );
288             }
289             synchronized ( aHelper )
290             {
291                 aHelper.setFinished( true );
292                 // Wake everyone waiting on us
293                 aHelper.notifyAll();
294             }
295         }
296     }
297 }