1 package org.apache.jcs.utils.access;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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("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 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
203
204 JCSWorkerHelper helper = null;
205
206 synchronized ( map )
207 {
208
209 helper = (JCSWorkerHelper) map.get( getRegion() + aKey );
210 if ( helper == null )
211 {
212
213
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
237 try
238 {
239 if ( logger.isDebugEnabled() )
240 {
241 logger.debug( getRegion() + " is doing the work." );
242 }
243 result = null;
244
245
246 if ( aGroup != null )
247 {
248 result = cache.getFromGroup( aKey, aGroup );
249 }
250 else
251 {
252 result = cache.get( aKey );
253 }
254
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
263 if ( aGroup != null )
264 {
265 cache.putInGroup( aKey, aGroup, result );
266 }
267 else
268 {
269 cache.put( aKey, result );
270 }
271 }
272
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
284 if ( helper == null )
285 {
286 map.remove( getRegion() + aKey );
287 }
288 synchronized ( this )
289 {
290 aHelper.setFinished( true );
291
292 notifyAll();
293 }
294 }
295 }
296 }
297 }