package nl.siegmann.ehcachetag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;
import javax.servlet.jsp.tagext.Tag;

import nl.siegmann.ehcachetag.cachekeyfactories.CacheKeyFactory;
import nl.siegmann.ehcachetag.cachekeyfactories.CacheKeyMetaFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A tag for caching jsp page fragments.
 * 
 * When in doubt or anything goes wrong it will not use information from the cache but generate it anew.
 * 
 * @author paul
 *
 */
public class CacheTag extends BodyTagSupport {

	/**
	 * 
	 */
	private static final long serialVersionUID = 333106287254149880L;

	private static final Logger LOG = LoggerFactory.getLogger(CacheTag.class);
	
	private Object key;
	private String cacheName = EHCacheTagConstants.DEFAULT_CACHE_NAME;
	private String keyFactoryName;
	private ContentCache contentCache = new ContentCache();

	// Actual key for caching as created by the CacheKeyFactory used.
	private Object cacheKey;
	
	/**
	 * Writes the content of the body to the pageContext writer.
	 * The content may come from the cache.
	 * <br/>
	 * Three scenarios:<br/>
	 * 1. Key is null : proceed as normal, return EVAL_BODY_INCLUDE<br/>
	 * 2. Key is not null, no cached value found: create cached body value, return EVAL_BODY_BUFFERED<br/>
	 * 3. Key is not null, cached value found: SKIP_BODY.<br/>
	 * 
	 * @throws IOException 
	 * 
	 */
	@Override
	public int doStartTag() throws JspException {
		
		this.cacheKey = createCacheKey();
		
		if (cacheKey == null) {
			// no cacheKey: generate and write body content in the normal way
			return BodyTagSupport.EVAL_BODY_INCLUDE;
		}
		
		String cachedBodyContent = getCachedBodyContent(cacheName, cacheKey);
		
		int result;
		
		if (cachedBodyContent == ContentCache.NO_CACHED_VALUE) {
			// we have a key but no cached content, start buffering the bodyContent
			result = BodyTagSupport.EVAL_BODY_BUFFERED;
		} else {
			// we have cached content: write content and skip body
			try {
				pageContext.getOut().write(cachedBodyContent);
			} catch (IOException e) {
				throw new JspException(e);
			}
			
			result = BodyTagSupport.SKIP_BODY;
		}
		return result;
	}

	/**
	 * Tries to locate a cacheKeyFactory and have it generate a cacheKey.
	 * 
	 * @param tagCacheKey
	 * @return a cache key generated by the cacheKeyFactory, may be null indicating not to cache
	 */
	Object createCacheKey() {
		
		// locate the cacheKeyMetaFactory
		CacheKeyMetaFactory cacheKeyMetaFactory = (CacheKeyMetaFactory) pageContext.findAttribute(EHCacheTagConstants.METAFACTORY_ATTRIBUTE_NAME);
		if (cacheKeyMetaFactory == null) {
			// if no cacheKeyMetaFactory defined we use the tag key
			return key;
		}
		
		// let the cacheKeyMetaFactory determine the cacheKey
		Object result = null;
		CacheKeyFactory cacheKeyFactory = cacheKeyMetaFactory.getCacheKeyFactory(keyFactoryName);
		if (cacheKeyFactory != null) {
			result = cacheKeyFactory.createCacheKey(key, pageContext);
		}
		return result;
	}
	

	/**
	 * Tries to get the body content from the cache using the cacheKey.
	 * 
	 * @param cacheKey
	 * @return The cached content, null if none found.
	 */
	private String getCachedBodyContent(String cacheName, Object cacheKey) {
		Object cachedObject = contentCache.getContent(cacheName, cacheKey);
		if (cachedObject == ContentCache.NO_CACHED_VALUE) {
			return ContentCache.NO_CACHED_VALUE;
		}
		if(! (cachedObject instanceof String)) {
			LOG.error("Cached object with key '" + cacheKey + "' in cache '" + cacheName + "' is of unexpected type " + (cachedObject == null ? "<null>" : cachedObject.getClass().getName()));
			return ContentCache.NO_CACHED_VALUE;
		}
		return (String) cachedObject;
	}
	
	/**
	 * Two scenarios:<br/>
	 * 1. key is null or bodyContent is null: do nothing<br/>
	 * 2. otherwise: store bodyContent in cache using cacheKey
	 */
	@Override
	public int doEndTag() throws JspException {
		int result = Tag.EVAL_PAGE;
		
		if (cacheKey == null || bodyContent == null) {
			return result;
		}
		
		// store new bodyContent using cacheKey.
		String bodyContentAsString = bodyContent.getString();
		contentCache.putContent(cacheName, cacheKey, bodyContentAsString);
		
		// write bodyContent
		try {
			pageContext.getOut().write(bodyContentAsString);
		} catch (IOException e) {
			throw new JspException(e);
		}
		
		// cleanup for the next user
		cacheKey = null;
		
		return result;
	}

	void setContentCache(ContentCache contentCache) {
		this.contentCache = contentCache;
	}
	
	public Object getKey() {
		return key;
	}

	public void setKey(Object key) {
		this.key = key;
	}

	public String getCache() {
		return cacheName;
	}

	public void setCache(String cacheName) {
		this.cacheName = cacheName;
	}

	public String getKeyFactory() {
		return keyFactoryName;
	}

	public void setKeyFactory(String keyFactoryName) {
		this.keyFactoryName = keyFactoryName;
	}

	void setCacheKey(Object cacheKey) {
		this.cacheKey = cacheKey;
	}
}

@@ -763,16 +763,51 @@
 ass);%0A%09%0A
+%09// attributes set in the jsp page%0A
 %09private
@@ -819,16 +819,16 @@
 ct key;%0A
-
 %09private
@@ -919,16 +919,36 @@
 ryName;%0A
+%09%0A%09// utility class%0A
 %09private
@@ -1091,17 +1091,18 @@
 heKey;%0A%09
+%09
 %0A
-
 %09/**%0A%09 *
@@ -2228,16 +2228,146 @@
 %09%09%7D%0A%09%09%09%0A
+%09%09%09// set cacheKey to null so that the endTag knows%0A%09%09%09// it does not have to store anything in the cache%0A%09%09%09cacheKey = null;%0A%09%09%09%0A
 %09%09%09resul
@@ -4211,31 +4211,8 @@
 y ==
- null %7C%7C bodyContent ==
  nul
@@ -4556,24 +4556,24 @@
 (e);%0A%09%09%7D%0A%09%09%0A
+
 %09%09// cleanup
@@ -4589,17 +4589,16 @@
 next use
-r
 %0A%09%09cache

make CacheTag actually work