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