package railo.runtime.tag;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.oro.text.regex.MalformedPatternException;
import railo.commons.io.IOUtil;
import railo.commons.io.cache.CacheEntry;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContextImpl;
import railo.runtime.cache.legacy.CacheItem;
import railo.runtime.config.ConfigImpl;
import railo.runtime.exp.Abort;
import railo.runtime.exp.ApplicationException;
import railo.runtime.exp.DeprecatedException;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.PageException;
import railo.runtime.exp.TemplateException;
import railo.runtime.ext.tag.BodyTagImpl;
import railo.runtime.functions.cache.CacheGet;
import railo.runtime.functions.cache.CachePut;
import railo.runtime.functions.cache.CacheRemove;
import railo.runtime.functions.cache.Util;
import railo.runtime.functions.dateTime.GetHttpTimeString;
import railo.runtime.net.http.ReqRspUtil;
import railo.runtime.op.Caster;
import railo.runtime.tag.util.DeprecatedUtil;
import railo.runtime.type.StructImpl;
import railo.runtime.type.dt.DateTime;
import railo.runtime.type.dt.DateTimeImpl;
import railo.runtime.type.dt.TimeSpan;
import railo.runtime.type.dt.TimeSpanImpl;
/**
* Speeds up page rendering when dynamic content does not have to be retrieved each time a user accesses
* the page. To accomplish this, cfcache creates temporary files that contain the static HTML returned from
* a CFML page. You can use cfcache for simple URLs and URLs that contain URL parameters.
*
*
*
**/
public final class Cache extends BodyTagImpl {
private static final TimeSpan TIMESPAN_FAR_AWAY = new TimeSpanImpl(1000000000,1000000000,1000000000,1000000000);
private static final TimeSpan TIMESPAN_0 = new TimeSpanImpl(0,0,0,0);
/** */
private Resource directory;
/** Specifies the protocol used to create pages from cache. Either http:// or https://. The default
** is http://. */
private String protocol;
/** */
private String expireurl;
/** */
private int action=CACHE;
/** When required for basic authentication, a valid username. */
//private String username;
/** When required for basic authentication, a valid password. */
//private String password;
private TimeSpan timespan=TIMESPAN_FAR_AWAY;
private TimeSpan idletime=TIMESPAN_0;
/** */
private int port=-1;
private DateTimeImpl now;
private String body;
private String _id;
private Object id;
private String name;
private String key;
private boolean hasBody;
private boolean doCaching;
private CacheItem cacheItem;
private String cachename;
private Object value;
private boolean throwOnError;
private String metadata;
private static final int CACHE=0;
private static final int CACHE_SERVER=1;
private static final int CACHE_CLIENT=2;
private static final int FLUSH=3;
private static final int CONTENT=4;
private static final int GET=5;
private static final int PUT=6;
@Override
public void release() {
super.release();
directory=null;
//username=null;
//password=null;
protocol=null;
expireurl=null;
action=CACHE;
port=-1;
timespan=TIMESPAN_FAR_AWAY;
idletime=TIMESPAN_0;
body=null;
hasBody=false;
id=null;
key=null;
body=null;
doCaching=false;
cacheItem=null;
name=null;
cachename=null;
throwOnError=false;
value=null;
metadata=null;
}
/**
* @deprecated this attribute is deprecated and will ignored in this tag
* @param obj
* @throws DeprecatedException
*/
public void setTimeout(Object obj) throws DeprecatedException {
DeprecatedUtil.tagAttribute(pageContext,"Cache","timeout");
}
/** set the value directory
*
* @param directory value to set
**/
public void setDirectory(String directory) throws ExpressionException {
this.directory=ResourceUtil.toResourceExistingParent(pageContext, directory);
}
public void setCachedirectory(String directory) throws ExpressionException {
setDirectory(directory);
}
/** set the value protocol
* Specifies the protocol used to create pages from cache. Either http:// or https://. The default
* is http://.
* @param protocol value to set
**/
public void setProtocol(String protocol) {
if(protocol.endsWith("://"))protocol=protocol.substring(0,protocol.indexOf("://"));
this.protocol=protocol.toLowerCase();
}
/*private String getProtocol() {
if(StringUtil.isEmpty(protocol)) {
return pageContext. getHttpServletRequest().getScheme();
}
return protocol;
}*/
/** set the value expireurl
*
* @param expireurl value to set
**/
public void setExpireurl(String expireurl) {
this.expireurl=expireurl;
}
/** set the value action
*
* @param action value to set
* @throws ApplicationException
**/
public void setAction(String action) throws ApplicationException {
action=action.toLowerCase().trim();
if(action.equals("get")) this.action=GET;
else if(action.equals("put")) this.action=PUT;
else if(action.equals("cache")) this.action=CACHE;
else if(action.equals("clientcache")) this.action=CACHE_CLIENT;
else if(action.equals("servercache")) this.action=CACHE_SERVER;
else if(action.equals("flush")) this.action=FLUSH;
else if(action.equals("optimal")) this.action=CACHE;
else if(action.equals("client-cache")) this.action=CACHE_CLIENT;
else if(action.equals("client_cache")) this.action=CACHE_CLIENT;
else if(action.equals("server-cache")) this.action=CACHE_SERVER;
else if(action.equals("server_cache")) this.action=CACHE_SERVER;
else if(action.equals("content")) this.action=CONTENT;
else if(action.equals("content_cache")) this.action=CONTENT;
else if(action.equals("contentcache")) this.action=CONTENT;
else if(action.equals("content-cache")) this.action=CONTENT;
else throw new ApplicationException("invalid value for attribute action for tag cache ["+action+"], " +
"valid actions are [get,put,cache, clientcache, servercache, flush, optimal, contentcache]");
//get: get an object from the cache.
//put: Add an object to the cache.
}
/** set the value username
* When required for basic authentication, a valid username.
* @param username value to set
**/
public void setUsername(String username) {
//this.username=username;
}
/** set the value password
* When required for basic authentication, a valid password.
* @param password value to set
**/
public void setPassword(String password) {
//this.password=password;
}
public void setKey(String key) {
this.key=key;
}
/** set the value port
*
* @param port value to set
**/
public void setPort(double port) {
this.port=(int)port;
}
public int getPort() {
if(port<=0) return pageContext. getHttpServletRequest().getServerPort();
return port;
}
/**
* @param timespan The timespan to set.
* @throws PageException
*/
public void setTimespan(TimeSpan timespan) {
this.timespan = timespan;
}
@Override
public int doStartTag() throws PageException {
now = new DateTimeImpl(pageContext.getConfig());
try {
if(action==CACHE) {
doClientCache();
doServerCache();
}
else if(action==CACHE_CLIENT) doClientCache();
else if(action==CACHE_SERVER) doServerCache();
else if(action==FLUSH) doFlush();
else if(action==CONTENT) return doContentCache();
else if(action==GET) doGet();
else if(action==PUT) doPut();
return EVAL_PAGE;
}
catch(Exception e) {
throw Caster.toPageException(e);
}
}
@Override
public void doInitBody() {
}
@Override
public int doAfterBody() {
//print.out("doAfterBody");
if(bodyContent!=null)body=bodyContent.getString();
return SKIP_BODY;
}
@Override
public int doEndTag() throws PageException {//print.out("doEndTag"+doCaching+"-"+body);
if(doCaching && body!=null) {
try {
writeCacheResource(cacheItem, body);
pageContext.write(body);
}
catch (IOException e) {
throw Caster.toPageException(e);
}
}
return EVAL_PAGE;
}
private void doClientCache() {
pageContext.setHeader("Last-Modified",GetHttpTimeString.call(pageContext,now));
if(timespan!=null) {
DateTime expires = getExpiresDate();
pageContext.setHeader("Expires",GetHttpTimeString.call(pageContext,expires));
}
}
private void doServerCache() throws IOException, PageException {
if(hasBody)hasBody=!StringUtil.isEmpty(body);
// call via cfcache disable debugger output
if(pageContext.getConfig().debug())pageContext.getDebugger().setOutput(false);
HttpServletResponse rsp = pageContext.getHttpServletResponse();
// generate cache resource matching request object
CacheItem ci = generateCacheResource(null,false);
// use cached resource
if(ci.isValid(timespan)){ //if(isOK(cacheResource)){
if(pageContext. getHttpServletResponse().isCommitted()) return;
OutputStream os=null;
try {
ci.writeTo(os=getOutputStream(),ReqRspUtil.getCharacterEncoding(pageContext,rsp));
//IOUtil.copy(is=cacheResource.getInputStream(),os=getOutputStream(),false,false);
}
finally {
IOUtil.flushEL(os);
IOUtil.closeEL(os);
((PageContextImpl)pageContext).getRootOut().setClosed(true);
}
throw new Abort(Abort.SCOPE_REQUEST);
}
// call page again and
//MetaData.getInstance(getDirectory()).add(ci.getName(), ci.getRaw());
PageContextImpl pci = (PageContextImpl)pageContext;
pci.getRootOut().doCache(ci);
}
/*private boolean isOK(Resource cacheResource) {
return cacheResource.exists() && (cacheResource.lastModified()+timespan.getMillis()>=System.currentTimeMillis());
}*/
private int doContentCache() throws IOException {
// file
cacheItem = generateCacheResource(key,true);
// use cache
if(cacheItem.isValid(timespan)){
pageContext.write(cacheItem.getValue());
doCaching=false;
return SKIP_BODY;
}
doCaching=true;
return EVAL_BODY_BUFFERED;
}
private void doGet() throws PageException, IOException {
required("cache", "id", id);
required("cache", "name", name);
String id=Caster.toString(this.id);
if(metadata==null){
pageContext.setVariable(name,CacheGet.call(pageContext, id,throwOnError,cachename));
}
else {
railo.commons.io.cache.Cache cache =
Util.getCache(pageContext,cachename,ConfigImpl.CACHE_DEFAULT_OBJECT);
CacheEntry entry = throwOnError?cache.getCacheEntry(Util.key(id)):cache.getCacheEntry(Util.key(id),null);
if(entry!=null){
pageContext.setVariable(name,entry.getValue());
pageContext.setVariable(metadata,entry.getCustomInfo());
}
else {
pageContext.setVariable(metadata,new StructImpl());
}
}
}
private void doPut() throws PageException {
required("cache", "id", id);
required("cache", "value", value);
TimeSpan ts = timespan;
TimeSpan it = idletime;
if(ts==TIMESPAN_FAR_AWAY)ts=TIMESPAN_0;
if(it==TIMESPAN_FAR_AWAY)it=TIMESPAN_0;
CachePut.call(pageContext, Caster.toString(id),value,ts,it,cachename);
}
private void doFlush() throws IOException, MalformedPatternException, PageException {
if(id!=null){
required("cache", "id", id);
CacheRemove.call(pageContext, id,throwOnError,cachename);
}
else if(StringUtil.isEmpty(expireurl)) {
CacheItem.flushAll(pageContext,directory,cachename);
}
else {
CacheItem.flush(pageContext,directory,cachename,expireurl);
//ResourceUtil.removeChildrenEL(getDirectory(),(ResourceNameFilter)new ExpireURLFilter(expireurl));
}
}
private CacheItem generateCacheResource(String key, boolean useId) throws IOException {
return CacheItem.getInstance(pageContext,_id,key,useId,directory,cachename,timespan);
}
private void writeCacheResource(CacheItem cacheItem, String result) throws IOException {
cacheItem.store(result);
//IOUtil.write(cacheItem.getResource(), result,"UTF-8", false);
//MetaData.getInstance(cacheItem.getDirectory()).add(cacheItem.getName(), cacheItem.getRaw());
}
private DateTime getExpiresDate() {
return new DateTimeImpl(pageContext,getExpiresTime(),false);
}
private long getExpiresTime() {
return now.getTime()+(timespan.getMillis());
}
private OutputStream getOutputStream() throws PageException, IOException {
try {
return ((PageContextImpl)pageContext).getResponseStream();
}
catch(IllegalStateException ise) {
throw new TemplateException("content is already send to user, flush");
}
}
/**
* sets if tag has a body or not
* @param hasBody
*/
public void hasBody(boolean hasBody) {
this.hasBody=hasBody;
}
/**
* @param id the id to set
*/
public void set_id(String _id) {
this._id = _id;
}
public void setId(Object id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCachename(String cachename) {
this.cachename = cachename;
}
/**
* @param throwOnError the throwOnError to set
*/
public void setThrowonerror(boolean throwOnError) {
this.throwOnError = throwOnError;
}
public void setValue(Object value) {
this.value = value;
}
/**
* @param idletime the idletime to set
*/
public void setIdletime(TimeSpan idletime) {
this.idletime = idletime;
}
/**
* @param metadata the metadata to set
*/
public void setMetadata(String metadata) {
this.metadata = metadata;
}
}