| package org.apache.commons.jcs3.utils.access; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.apache.commons.jcs3.JCS; |
| import org.apache.commons.jcs3.access.CacheAccess; |
| import org.apache.commons.jcs3.access.GroupCacheAccess; |
| import org.apache.commons.jcs3.access.exception.CacheException; |
| import org.apache.commons.jcs3.log.Log; |
| import org.apache.commons.jcs3.log.LogManager; |
| |
| /** |
| * Utility class to encapsulate doing a piece of work, and caching the results |
| * in JCS. Simply construct this class with the region name for the Cache and |
| * keep a static reference to it instead of the JCS itself. Then make a new |
| * org.apache.commons.jcs3.utils.access.AbstractJCSWorkerHelper and implement Object |
| * doWork() and do the work in there, returning the object to be cached. Then |
| * call .getResult() with the key and the AbstractJCSWorkerHelper to get the |
| * result of the work. If the object isn't already in the Cache, |
| * AbstractJCSWorkerHelper.doWork() will get called, and the result will be put |
| * into the cache. If the object is already in cache, the cached result will be |
| * returned instead. |
| * <p> |
| * As an added bonus, multiple JCSWorkers with the same region, and key won't do |
| * the work multiple times: The first JCSWorker to get started will do the work, |
| * and all subsequent workers with the same region, group, and key will wait on |
| * the first one and use his resulting work instead of doing the work |
| * themselves. |
| * <p> |
| * This is ideal when the work being done is a query to the database where the |
| * results may take time to be retrieved. |
| * <p> |
| * For example: |
| * |
| * <pre> |
| * public static JCSWorker cachingWorker = new JCSWorker("example region"); |
| * public Object getSomething(Serializable aKey){ |
| * JCSWorkerHelper helper = new AbstractJCSWorkerHelper(){ |
| * public Object doWork(){ |
| * // Do some (DB?) work here which results in a list |
| * // This only happens if the cache dosn't have a item in this region for aKey |
| * // Note this is especially useful with Hibernate, which will cache indiviual |
| * // Objects, but not entire query result sets. |
| * List results = query.list(); |
| * // Whatever we return here get's cached with aKey, and future calls to |
| * // getResult() on a CachedWorker with the same region and key will return that instead. |
| * return results; |
| * }; |
| * List result = worker.getResult(aKey, helper); |
| * } |
| * </pre> |
| * |
| * This is essentially the same as doing: |
| * |
| * <pre> |
| * JCS jcs = JCS.getInstance( "exampleregion" ); |
| * List results = (List) jcs.get( aKey ); |
| * if ( results != null ) |
| * { |
| * //do the work here |
| * results = query.list(); |
| * jcs.put( aKey, results ); |
| * } |
| * </pre> |
| * |
| * <p> |
| * But has the added benefit of the work-load sharing; under normal |
| * circumstances if multiple threads all tried to do the same query at the same |
| * time, the same query would happen multiple times on the database, and the |
| * resulting object would get put into JCS multiple times. |
| * <p> |
| * @author Travis Savo |
| */ |
| public class JCSWorker<K, V> |
| { |
| /** The logger */ |
| private static final Log logger = LogManager.getLog( JCSWorker.class ); |
| |
| /** The cache we are working with */ |
| private CacheAccess<K, V> cache; |
| |
| /** The cache we are working with */ |
| private GroupCacheAccess<K, V> groupCache; |
| |
| /** |
| * Map to hold who's doing work presently. |
| */ |
| private volatile ConcurrentMap<String, JCSWorkerHelper<V>> map = new ConcurrentHashMap<>(); |
| |
| /** |
| * Region for the JCS cache. |
| */ |
| private final String region; |
| |
| /** |
| * Constructor which takes a region for the JCS cache. |
| * @param aRegion |
| * The Region to use for the JCS cache. |
| */ |
| public JCSWorker( final String aRegion ) |
| { |
| region = aRegion; |
| try |
| { |
| cache = JCS.getInstance( aRegion ); |
| groupCache = JCS.getGroupCacheInstance( aRegion ); |
| } |
| catch ( final CacheException e ) |
| { |
| throw new RuntimeException( e.getMessage() ); |
| } |
| } |
| |
| /** |
| * Getter for the region of the JCS Cache. |
| * @return The JCS region in which the result will be cached. |
| */ |
| public String getRegion() |
| { |
| return region; |
| } |
| |
| /** |
| * Gets the cached result for this region/key OR does the work and caches |
| * the result, returning the result. If the result has not been cached yet, |
| * this calls doWork() on the JCSWorkerHelper to do the work and cache the |
| * result. This is also an opportunity to do any post processing of the |
| * result in your CachedWorker implementation. |
| * @param aKey |
| * The key to get/put with on the Cache. |
| * @param aWorker |
| * The JCSWorkerHelper implementing Object doWork(). This gets |
| * called if the cache get misses, and the result is put into |
| * cache. |
| * @return The result of doing the work, or the cached result. |
| * @throws Exception |
| * Throws an exception if anything goes wrong while doing the |
| * work. |
| */ |
| public V getResult( final K aKey, final JCSWorkerHelper<V> aWorker ) |
| throws Exception |
| { |
| return run( aKey, null, aWorker ); |
| } |
| |
| /** |
| * Gets the cached result for this region/key OR does the work and caches |
| * the result, returning the result. If the result has not been cached yet, |
| * this calls doWork() on the JCSWorkerHelper to do the work and cache the |
| * result. This is also an opportunity to do any post processing of the |
| * result in your CachedWorker implementation. |
| * @param aKey |
| * The key to get/put with on the Cache. |
| * @param aGroup |
| * The cache group to put the result in. |
| * @param aWorker |
| * The JCSWorkerHelper implementing Object doWork(). This gets |
| * called if the cache get misses, and the result is put into |
| * cache. |
| * @return The result of doing the work, or the cached result. |
| * @throws Exception |
| * Throws an exception if anything goes wrong while doing the |
| * work. |
| */ |
| public V getResult( final K aKey, final String aGroup, final JCSWorkerHelper<V> aWorker ) |
| throws Exception |
| { |
| return run( aKey, aGroup, aWorker ); |
| } |
| |
| /** |
| * Try and get the object from the cache, and if it's not there, do the work |
| * and cache it. This also ensures that only one CachedWorker is doing the |
| * work and subsequent calls to a CachedWorker with identical |
| * region/key/group will wait on the results of this call. It will call the |
| * JCSWorkerHelper.doWork() if the cache misses, and will put the result. |
| * @param aKey |
| * @param aGroup |
| * @param aHelper |
| * @return Either the result of doing the work, or the cached result. |
| * @throws Exception |
| * If something goes wrong while doing the work, throw an |
| * exception. |
| */ |
| private V run( final K aKey, final String aGroup, final JCSWorkerHelper<V> aHelper ) |
| throws Exception |
| { |
| V result = null; |
| // long start = 0; |
| // long dbTime = 0; |
| final JCSWorkerHelper<V> helper = map.putIfAbsent(getRegion() + aKey, aHelper); |
| |
| if ( helper != null ) |
| { |
| synchronized ( helper ) |
| { |
| logger.debug( "Found a worker already doing this work ({0}:{1}).", |
| () -> getRegion(), () -> aKey ); |
| while ( !helper.isFinished() ) |
| { |
| try |
| { |
| helper.wait(); |
| } |
| catch (final InterruptedException e) |
| { |
| // expected |
| } |
| } |
| logger.debug( "Another thread finished our work for us. Using " |
| + "those results instead. ({0}:{1}).", |
| () -> getRegion(), () -> aKey ); |
| } |
| } |
| // Do the work |
| try |
| { |
| logger.debug( "{0} is doing the work.", () -> getRegion() ); |
| |
| // Try to get the item from the cache |
| if ( aGroup != null ) |
| { |
| result = groupCache.getFromGroup( aKey, aGroup ); |
| } |
| else |
| { |
| result = cache.get( aKey ); |
| } |
| // If the cache dosn't have it, do the work. |
| if ( result == null ) |
| { |
| result = aHelper.doWork(); |
| logger.debug( "Work Done, caching: key:{0}, group:{1}, result:{2}.", |
| aKey, aGroup, result ); |
| // Stick the result of the work in the cache. |
| if ( aGroup != null ) |
| { |
| groupCache.putInGroup( aKey, aGroup, result ); |
| } |
| else |
| { |
| cache.put( aKey, result ); |
| } |
| } |
| // return the result |
| return result; |
| } |
| finally |
| { |
| logger.debug( "{0}:{1} entered finally.", () -> getRegion(), |
| () -> aKey ); |
| |
| // Remove ourselves as the worker. |
| if ( helper == null ) |
| { |
| map.remove( getRegion() + aKey ); |
| } |
| synchronized ( aHelper ) |
| { |
| aHelper.setFinished( true ); |
| // Wake everyone waiting on us |
| aHelper.notifyAll(); |
| } |
| } |
| } |
| } |