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("example region");
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( "exampleregion" );
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 }