001 package railo.runtime.tag; 002 003 004 import java.io.IOException; 005 import java.io.OutputStream; 006 007 import javax.servlet.http.HttpServletResponse; 008 009 import org.apache.oro.text.regex.MalformedPatternException; 010 011 import railo.commons.io.IOUtil; 012 import railo.commons.io.cache.CacheEntry; 013 import railo.commons.io.res.Resource; 014 import railo.commons.io.res.util.ResourceUtil; 015 import railo.commons.lang.StringUtil; 016 import railo.runtime.PageContextImpl; 017 import railo.runtime.cache.legacy.CacheItem; 018 import railo.runtime.config.ConfigImpl; 019 import railo.runtime.exp.Abort; 020 import railo.runtime.exp.ApplicationException; 021 import railo.runtime.exp.DeprecatedException; 022 import railo.runtime.exp.ExpressionException; 023 import railo.runtime.exp.PageException; 024 import railo.runtime.exp.TemplateException; 025 import railo.runtime.ext.tag.BodyTagImpl; 026 import railo.runtime.functions.cache.CacheGet; 027 import railo.runtime.functions.cache.CachePut; 028 import railo.runtime.functions.cache.CacheRemove; 029 import railo.runtime.functions.cache.Util; 030 import railo.runtime.functions.dateTime.GetHttpTimeString; 031 import railo.runtime.op.Caster; 032 import railo.runtime.tag.util.DeprecatedUtil; 033 import railo.runtime.type.StructImpl; 034 import railo.runtime.type.dt.DateTime; 035 import railo.runtime.type.dt.DateTimeImpl; 036 import railo.runtime.type.dt.TimeSpan; 037 import railo.runtime.type.dt.TimeSpanImpl; 038 039 /** 040 * Speeds up page rendering when dynamic content does not have to be retrieved each time a user accesses 041 * the page. To accomplish this, cfcache creates temporary files that contain the static HTML returned from 042 * a CFML page. You can use cfcache for simple URLs and URLs that contain URL parameters. 043 * 044 * 045 * 046 **/ 047 public final class Cache extends BodyTagImpl { 048 049 050 051 052 private static final TimeSpan TIMESPAN_FAR_AWAY = new TimeSpanImpl(1000000000,1000000000,1000000000,1000000000); 053 private static final TimeSpan TIMESPAN_0 = new TimeSpanImpl(0,0,0,0); 054 055 056 /** */ 057 private Resource directory; 058 059 060 /** Specifies the protocol used to create pages from cache. Either http:// or https://. The default 061 ** is http://. */ 062 private String protocol; 063 064 /** */ 065 private String expireurl; 066 067 /** */ 068 private int action=CACHE; 069 070 /** When required for basic authentication, a valid username. */ 071 //private String username; 072 073 /** When required for basic authentication, a valid password. */ 074 //private String password; 075 076 private TimeSpan timespan=TIMESPAN_FAR_AWAY; 077 private TimeSpan idletime=TIMESPAN_0; 078 079 080 /** */ 081 private int port=-1; 082 083 private DateTimeImpl now; 084 085 086 private String body; 087 private String _id; 088 private Object id; 089 private String name; 090 private String key; 091 092 093 private boolean hasBody; 094 095 096 private boolean doCaching; 097 098 099 private CacheItem cacheItem; 100 101 102 private String cachename; 103 private Object value; 104 private boolean throwOnError; 105 private String metadata; 106 107 private static final int CACHE=0; 108 private static final int CACHE_SERVER=1; 109 private static final int CACHE_CLIENT=2; 110 private static final int FLUSH=3; 111 private static final int CONTENT=4; 112 113 private static final int GET=5; 114 private static final int PUT=6; 115 116 @Override 117 public void release() { 118 super.release(); 119 directory=null; 120 //username=null; 121 //password=null; 122 protocol=null; 123 expireurl=null; 124 action=CACHE; 125 port=-1; 126 timespan=TIMESPAN_FAR_AWAY; 127 idletime=TIMESPAN_0; 128 body=null; 129 hasBody=false; 130 id=null; 131 key=null; 132 body=null; 133 doCaching=false; 134 cacheItem=null; 135 name=null; 136 cachename=null; 137 throwOnError=false; 138 value=null; 139 metadata=null; 140 } 141 142 /** 143 * @deprecated this attribute is deprecated and will ignored in this tag 144 * @param obj 145 * @throws DeprecatedException 146 */ 147 public void setTimeout(Object obj) throws DeprecatedException { 148 DeprecatedUtil.tagAttribute(pageContext,"Cache","timeout"); 149 } 150 151 /** set the value directory 152 * 153 * @param directory value to set 154 **/ 155 public void setDirectory(String directory) throws ExpressionException { 156 this.directory=ResourceUtil.toResourceExistingParent(pageContext, directory); 157 } 158 public void setCachedirectory(String directory) throws ExpressionException { 159 setDirectory(directory); 160 } 161 162 163 /** set the value protocol 164 * Specifies the protocol used to create pages from cache. Either http:// or https://. The default 165 * is http://. 166 * @param protocol value to set 167 **/ 168 public void setProtocol(String protocol) { 169 if(protocol.endsWith("://"))protocol=protocol.substring(0,protocol.indexOf("://")); 170 this.protocol=protocol.toLowerCase(); 171 } 172 173 /*private String getProtocol() { 174 if(StringUtil.isEmpty(protocol)) { 175 return pageContext. getHttpServletRequest().getScheme(); 176 } 177 return protocol; 178 }*/ 179 180 /** set the value expireurl 181 * 182 * @param expireurl value to set 183 **/ 184 public void setExpireurl(String expireurl) { 185 this.expireurl=expireurl; 186 } 187 188 /** set the value action 189 * 190 * @param action value to set 191 * @throws ApplicationException 192 **/ 193 public void setAction(String action) throws ApplicationException { 194 action=action.toLowerCase().trim(); 195 if(action.equals("get")) this.action=GET; 196 else if(action.equals("put")) this.action=PUT; 197 198 199 else if(action.equals("cache")) this.action=CACHE; 200 else if(action.equals("clientcache")) this.action=CACHE_CLIENT; 201 else if(action.equals("servercache")) this.action=CACHE_SERVER; 202 else if(action.equals("flush")) this.action=FLUSH; 203 else if(action.equals("optimal")) this.action=CACHE; 204 205 else if(action.equals("client-cache")) this.action=CACHE_CLIENT; 206 else if(action.equals("client_cache")) this.action=CACHE_CLIENT; 207 208 else if(action.equals("server-cache")) this.action=CACHE_SERVER; 209 else if(action.equals("server_cache")) this.action=CACHE_SERVER; 210 211 else if(action.equals("content")) this.action=CONTENT; 212 else if(action.equals("content_cache")) this.action=CONTENT; 213 else if(action.equals("contentcache")) this.action=CONTENT; 214 else if(action.equals("content-cache")) this.action=CONTENT; 215 else throw new ApplicationException("invalid value for attribute action for tag cache ["+action+"], " + 216 "valid actions are [get,put,cache, clientcache, servercache, flush, optimal, contentcache]"); 217 218 //get: get an object from the cache. 219 //put: Add an object to the cache. 220 221 222 223 224 } 225 226 /** set the value username 227 * When required for basic authentication, a valid username. 228 * @param username value to set 229 **/ 230 public void setUsername(String username) { 231 //this.username=username; 232 } 233 234 /** set the value password 235 * When required for basic authentication, a valid password. 236 * @param password value to set 237 **/ 238 public void setPassword(String password) { 239 //this.password=password; 240 } 241 242 public void setKey(String key) { 243 this.key=key; 244 } 245 246 /** set the value port 247 * 248 * @param port value to set 249 **/ 250 public void setPort(double port) { 251 this.port=(int)port; 252 } 253 254 public int getPort() { 255 if(port<=0) return pageContext. getHttpServletRequest().getServerPort(); 256 return port; 257 } 258 259 /** 260 * @param timespan The timespan to set. 261 * @throws PageException 262 */ 263 public void setTimespan(TimeSpan timespan) { 264 this.timespan = timespan; 265 } 266 267 268 @Override 269 public int doStartTag() throws PageException { 270 now = new DateTimeImpl(pageContext.getConfig()); 271 try { 272 if(action==CACHE) { 273 doClientCache(); 274 doServerCache(); 275 } 276 else if(action==CACHE_CLIENT) doClientCache(); 277 else if(action==CACHE_SERVER) doServerCache(); 278 else if(action==FLUSH) doFlush(); 279 else if(action==CONTENT) return doContentCache(); 280 else if(action==GET) doGet(); 281 else if(action==PUT) doPut(); 282 283 return EVAL_PAGE; 284 } 285 catch(Exception e) { 286 throw Caster.toPageException(e); 287 } 288 } 289 290 @Override 291 public void doInitBody() { 292 293 } 294 295 @Override 296 public int doAfterBody() { 297 //print.out("doAfterBody"); 298 if(bodyContent!=null)body=bodyContent.getString(); 299 return SKIP_BODY; 300 } 301 302 @Override 303 public int doEndTag() throws PageException {//print.out("doEndTag"+doCaching+"-"+body); 304 if(doCaching && body!=null) { 305 try { 306 writeCacheResource(cacheItem, body); 307 pageContext.write(body); 308 } 309 catch (IOException e) { 310 throw Caster.toPageException(e); 311 } 312 } 313 return EVAL_PAGE; 314 } 315 316 private void doClientCache() { 317 pageContext.setHeader("Last-Modified",GetHttpTimeString.call(pageContext,now)); 318 319 if(timespan!=null) { 320 DateTime expires = getExpiresDate(); 321 pageContext.setHeader("Expires",GetHttpTimeString.call(pageContext,expires)); 322 } 323 } 324 325 private void doServerCache() throws IOException, PageException { 326 if(hasBody)hasBody=!StringUtil.isEmpty(body); 327 328 // call via cfcache disable debugger output 329 pageContext.getDebugger().setOutput(false); 330 331 HttpServletResponse rsp = pageContext.getHttpServletResponse(); 332 333 // generate cache resource matching request object 334 CacheItem ci = generateCacheResource(null,false); 335 336 // use cached resource 337 if(ci.isValid(timespan)){ //if(isOK(cacheResource)){ 338 if(pageContext. getHttpServletResponse().isCommitted()) return; 339 340 OutputStream os=null; 341 try { 342 ci.writeTo(os=getOutputStream(),rsp.getCharacterEncoding()); 343 //IOUtil.copy(is=cacheResource.getInputStream(),os=getOutputStream(),false,false); 344 } 345 finally { 346 IOUtil.flushEL(os); 347 IOUtil.closeEL(os); 348 ((PageContextImpl)pageContext).getRootOut().setClosed(true); 349 } 350 throw new Abort(Abort.SCOPE_REQUEST); 351 } 352 353 // call page again and 354 //MetaData.getInstance(getDirectory()).add(ci.getName(), ci.getRaw()); 355 356 PageContextImpl pci = (PageContextImpl)pageContext; 357 pci.getRootOut().doCache(ci); 358 359 } 360 361 /*private boolean isOK(Resource cacheResource) { 362 return cacheResource.exists() && (cacheResource.lastModified()+timespan.getMillis()>=System.currentTimeMillis()); 363 }*/ 364 365 private int doContentCache() throws IOException { 366 367 // file 368 cacheItem = generateCacheResource(key,true); 369 // use cache 370 if(cacheItem.isValid(timespan)){ 371 pageContext.write(cacheItem.getValue()); 372 doCaching=false; 373 return SKIP_BODY; 374 } 375 doCaching=true; 376 return EVAL_BODY_BUFFERED; 377 } 378 379 private void doGet() throws PageException, IOException { 380 required("cache", "id", id); 381 required("cache", "name", name); 382 String id=Caster.toString(this.id); 383 if(metadata==null){ 384 pageContext.setVariable(name,CacheGet.call(pageContext, id,throwOnError,cachename)); 385 } 386 else { 387 railo.commons.io.cache.Cache cache = 388 Util.getCache(pageContext,cachename,ConfigImpl.CACHE_DEFAULT_OBJECT); 389 CacheEntry entry = throwOnError?cache.getCacheEntry(Util.key(id)):cache.getCacheEntry(Util.key(id),null); 390 if(entry!=null){ 391 pageContext.setVariable(name,entry.getValue()); 392 pageContext.setVariable(metadata,entry.getCustomInfo()); 393 } 394 else { 395 pageContext.setVariable(metadata,new StructImpl()); 396 } 397 } 398 399 400 401 402 403 404 405 406 } 407 private void doPut() throws PageException { 408 required("cache", "id", id); 409 required("cache", "value", value); 410 TimeSpan ts = timespan; 411 TimeSpan it = idletime; 412 if(ts==TIMESPAN_FAR_AWAY)ts=TIMESPAN_0; 413 if(it==TIMESPAN_FAR_AWAY)it=TIMESPAN_0; 414 CachePut.call(pageContext, Caster.toString(id),value,ts,it,cachename); 415 } 416 417 418 419 420 private void doFlush() throws IOException, MalformedPatternException, PageException { 421 if(id!=null){ 422 required("cache", "id", id); 423 CacheRemove.call(pageContext, id,throwOnError,cachename); 424 } 425 else if(StringUtil.isEmpty(expireurl)) { 426 CacheItem.flushAll(pageContext,directory,cachename); 427 } 428 else { 429 CacheItem.flush(pageContext,directory,cachename,expireurl); 430 431 //ResourceUtil.removeChildrenEL(getDirectory(),(ResourceNameFilter)new ExpireURLFilter(expireurl)); 432 } 433 } 434 435 436 private CacheItem generateCacheResource(String key, boolean useId) throws IOException { 437 return CacheItem.getInstance(pageContext,_id,key,useId,directory,cachename,timespan); 438 } 439 440 441 442 private void writeCacheResource(CacheItem cacheItem, String result) throws IOException { 443 cacheItem.store(result); 444 //IOUtil.write(cacheItem.getResource(), result,"UTF-8", false); 445 //MetaData.getInstance(cacheItem.getDirectory()).add(cacheItem.getName(), cacheItem.getRaw()); 446 } 447 448 449 private DateTime getExpiresDate() { 450 return new DateTimeImpl(pageContext,getExpiresTime(),false); 451 } 452 453 private long getExpiresTime() { 454 return now.getTime()+(timespan.getMillis()); 455 } 456 457 private OutputStream getOutputStream() throws PageException, IOException { 458 try { 459 return ((PageContextImpl)pageContext).getResponseStream(); 460 } 461 catch(IllegalStateException ise) { 462 throw new TemplateException("content is already send to user, flush"); 463 } 464 } 465 466 467 /** 468 * sets if tag has a body or not 469 * @param hasBody 470 */ 471 public void hasBody(boolean hasBody) { 472 this.hasBody=hasBody; 473 } 474 475 /** 476 * @param id the id to set 477 */ 478 public void set_id(String _id) { 479 this._id = _id; 480 } 481 482 public void setId(Object id) { 483 this.id = id; 484 } 485 486 public void setName(String name) { 487 this.name = name; 488 } 489 public void setCachename(String cachename) { 490 this.cachename = cachename; 491 } 492 493 /** 494 * @param throwOnError the throwOnError to set 495 */ 496 public void setThrowonerror(boolean throwOnError) { 497 this.throwOnError = throwOnError; 498 } 499 500 public void setValue(Object value) { 501 this.value = value; 502 } 503 504 /** 505 * @param idletime the idletime to set 506 */ 507 public void setIdletime(TimeSpan idletime) { 508 this.idletime = idletime; 509 } 510 511 /** 512 * @param metadata the metadata to set 513 */ 514 public void setMetadata(String metadata) { 515 this.metadata = metadata; 516 } 517 } 518