View Javadoc

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