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 allready in the Cache,
39   * AbstractJCSWorkerHelper.doWork() will get called, and the result will be put
40   * into the cache. If the object is allready 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 benifit 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
94  {
95      private static final Log logger = LogFactory.getLog( JCSWorker.class );
96  
97      private JCS cache;
98  
99      /***
100      * Map to hold who's doing work presently.
101      */
102     private static volatile Map map = new HashMap();
103 
104     /***
105      * Region for the JCS cache.
106      */
107     private String region;
108 
109     /***
110      * Constructor which takes a region for the JCS cache.
111      * @param aRegion
112      *            The Region to use for the JCS cache.
113      */
114     public JCSWorker( final String aRegion )
115     {
116         region = aRegion;
117         try
118         {
119             cache = JCS.getInstance( aRegion );
120         }
121         catch ( CacheException e )
122         {
123             throw new RuntimeException( e.getMessage() );
124         }
125     }
126 
127     /***
128      * Getter for the region of the JCS Cache.
129      * @return The JCS region in which the result will be cached.
130      */
131     public String getRegion()
132     {
133         return region;
134     }
135 
136     /***
137      * Gets the cached result for this region/key OR does the work and caches
138      * the result, returning the result. If the result has not been cached yet,
139      * this calls doWork() on the JCSWorkerHelper to do the work and cache the
140      * result. This is also an opertunity to do any post processing of the
141      * result in your CachedWorker implementation.
142      * @param aKey
143      *            The key to get/put with on the Cache.
144      * @param aWorker
145      *            The JCSWorkerHelper implementing Object doWork(). This gets
146      *            called if the cache get misses, and the result is put into
147      *            cache.
148      * @return The result of doing the work, or the cached result.
149      * @throws Exception
150      *             Throws an exception if anything goes wrong while doing the
151      *             work.
152      */
153     public Object getResult( Serializable aKey, JCSWorkerHelper aWorker )
154         throws Exception
155     {
156         return run( aKey, null, aWorker );
157     }
158 
159     /***
160      * Gets the cached result for this region/key OR does the work and caches
161      * the result, returning the result. If the result has not been cached yet,
162      * this calls doWork() on the JCSWorkerHelper to do the work and cache the
163      * result. This is also an opertunity to do any post processing of the
164      * result in your CachedWorker implementation.
165      * @param aKey
166      *            The key to get/put with on the Cache.
167      * @param aGroup
168      *            The cache group to put the result in.
169      * @param aWorker
170      *            The JCSWorkerHelper implementing Object doWork(). This gets
171      *            called if the cache get misses, and the result is put into
172      *            cache.
173      * @return The result of doing the work, or the cached result.
174      * @throws Exception
175      *             Throws an exception if anything goes wrong while doing the
176      *             work.
177      */
178     public Object getResult( Serializable aKey, String aGroup, JCSWorkerHelper aWorker )
179         throws Exception
180     {
181         return run( aKey, aGroup, aWorker );
182     }
183 
184     /***
185      * Try and get the object from the cache, and if it's not there, do the work
186      * and cache it. This also ensures that only one CachedWorker is doing the
187      * work and subsequent calls to a CachedWorker with identical
188      * region/key/group will wait on the results of this call. It will call the
189      * JCSWorkerHelper.doWork() if the cache misses, and will put the result.
190      * @param aKey
191      * @param aGroup
192      * @param aHelper
193      * @return Either the result of doing the work, or the cached result.
194      * @throws Exception
195      *             If something goes wrong while doing the work, throw an
196      *             exception.
197      */
198     private Object run( Serializable aKey, String aGroup, JCSWorkerHelper aHelper )
199         throws Exception
200     {
201         Object result = null;
202         // long start = 0;
203         // long dbTime = 0;
204         JCSWorkerHelper helper = null;
205 
206         synchronized ( map )
207         {
208             // Check to see if we allready have a thread doing this work.
209             helper = (JCSWorkerHelper) map.get( getRegion() + aKey );
210             if ( helper == null )
211             {
212                 // If not, add ourselves as the Worker so
213                 // calls in another thread will use this worker's result
214                 map.put( getRegion() + aKey, aHelper );
215             }
216         }
217         if ( helper != null )
218         {
219             synchronized ( helper )
220             {
221                 if ( logger.isDebugEnabled() )
222                 {
223                     logger.debug( "Found a worker allready doing this work (" + getRegion() + ":" + aKey + ")." );
224                 }
225                 if ( !helper.isFinished() )
226                 {
227                     helper.wait();
228                 }
229                 if ( logger.isDebugEnabled() )
230                 {
231                     logger.debug( "Another thread finished our work for us. Using thoes results instead. ("
232                         + getRegion() + ":" + aKey + ")." );
233                 }
234             }
235         }
236         // Do the work
237         try
238         {
239             if ( logger.isDebugEnabled() )
240             {
241                 logger.debug( getRegion() + " is doing the work." );
242             }
243             result = null;
244 
245             // Try to get the item from the cache
246             if ( aGroup != null )
247             {
248                 result = cache.getFromGroup( aKey, aGroup );
249             }
250             else
251             {
252                 result = cache.get( aKey );
253             }
254             // If the cache dosn't have it, do the work.
255             if ( result == null )
256             {
257                 result = aHelper.doWork();
258                 if ( logger.isDebugEnabled() )
259                 {
260                     logger.debug( "Work Done, caching: key:" + aKey + ", group:" + aGroup + ", result:" + result + "." );
261                 }
262                 // Stick the result of the work in the cache.
263                 if ( aGroup != null )
264                 {
265                     cache.putInGroup( aKey, aGroup, result );
266                 }
267                 else
268                 {
269                     cache.put( aKey, result );
270                 }
271             }
272             // return the result
273             return result;
274         }
275         finally
276         {
277             if ( logger.isDebugEnabled() )
278             {
279                 logger.debug( getRegion() + ":" + aKey + " entered finally." );
280             }
281             synchronized ( map )
282             {
283                 // Remove ourselves as the worker.
284                 if ( helper == null )
285                 {
286                     map.remove( getRegion() + aKey );
287                 }
288                 synchronized ( this )
289                 {
290                     aHelper.setFinished( true );
291                     // Wake everyone waiting on us
292                     notifyAll();
293                 }
294             }
295         }
296     }
297 }