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