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; } }