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