/* ===================================================================
 * IndexSearchResultsCacheInterceptor.java
 * 
 * Created Feb 22, 2005 5:31:22 PM
 * 
 * Copyright (c) 2005 Matt Magoffin (spamsqr@msqr.us)
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ===================================================================
 * $Id: IndexSearchResultsCacheInterceptor.java 4 2009-05-01 03:27:53Z msqr $
 * ===================================================================
 */

package magoffin.matt.ieat.aop;

import java.io.Serializable;

import magoffin.matt.ieat.IllegalActionException;
import magoffin.matt.ieat.biz.RecipeSearchBiz;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.log4j.Logger;

/**
 * Interceptor to cache the {@link magoffin.matt.ieat.domain.UiSearchResults} 
 * object from an {@link magoffin.matt.ieat.biz.RecipeSearchBiz.IndexCriteria} 
 * query.
 * 
 * <p>This interceptor assumes the intercepted method returns a UiSearchResults
 * object and takes a IndexCriteria as a method parameter. After an invocation 
 * of an intercepted method the resulting UiSearchResults object will be cached.
 * If, for a given method call using a given IndexCriteria the results have been 
 * cached the cached copy will be returned instead of calling the method.</p>
 * 
 * <p>Note that cache keys are composed by concatenating the IndexCriteria's
 * <code>displaySection</code>, <code>pageStart</code>, and <code>pageEnd</code>
 * values. A single instance of this interceptor should be used only to cache
 * a single domain object search, i.e. an index of Recipe objects should use 
 * a different instance of this class than an index of User objects.</p>
 * 
 * <p>The cache implementation could use a 
 * {@link net.sf.ehcache.constructs.blocking.BlockingCache}
 * so that the result object is only regenerated once per expiration of the 
 * cached equivalent. This means while the search result is being regenerated other 
 * threads attempting to access the cache will be blocked until the regeneration 
 * has been completed.</p>
 * 
 * <p>The configurable properties of this class are:</p>
 * 
 * <dl>
 *   <dt>cache</dt>
 *   <dd>The cache to use.</dd>
 * </dl>
 * 
 * @author Matt Magoffin (spamsqr@msqr.us)
 * @version $Revision: 4 $ $Date: 2009-05-01 15:27:53 +1200 (Fri, 01 May 2009) $
 */
public class IndexSearchResultsCacheInterceptor implements MethodInterceptor {
	
	private final Logger log = Logger.getLogger(IndexSearchResultsCacheInterceptor.class);
	
	private Ehcache cache;
	
	/* (non-Javadoc)
	 * @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
	 */
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object[] args = invocation.getArguments();

		RecipeSearchBiz.IndexCriteria criteria = null;
		for ( int i = 0; i < args.length; i++ ) {
			if ( args[i] instanceof RecipeSearchBiz.IndexCriteria ) {
				criteria = ((RecipeSearchBiz.IndexCriteria)args[i]);
				break;
			}
		}
		
		if ( criteria == null ) {
			throw new IllegalActionException(RecipeSearchBiz.IndexCriteria.class.getName()
					+" not found as method argument");
		}
		
		String cacheKey = criteria.getDisplaySection()
			+criteria.getPageSize()
			+criteria.getPageStart();
		
		Element result = this.cache.get(cacheKey);
		if ( result != null ) {
			if ( log.isDebugEnabled() ) {
				log.debug("Got cached result on key " +cacheKey 
						+" for IndexCriteria " +criteria);
			}
			return result.getObjectValue();
		}
		
		try {
			// not available in cache, so build and cache now
			Object resultObj = invocation.proceed();
			Element cacheElement = new Element(cacheKey,(Serializable)resultObj);
			cache.put(cacheElement);
			if ( log.isDebugEnabled() ) {
				log.debug("Cached result on key " +cacheKey +" for IndexCriteria " +criteria);
			}
			return resultObj;
		} catch ( final Throwable throwable ) {
			cache.remove(cacheKey);
			throw throwable;
		}
	}
	
	/**
	 * @return the cache
	 */
	public Ehcache getCache() {
		return cache;
	}
	
	/**
	 * @param cache the cache to set
	 */
	public void setCache(Ehcache cache) {
		this.cache = cache;
	}

}
