1 52 53 package freemarker.cache; 54 55 import java.io.IOException ; 56 import java.io.Reader ; 57 import java.io.StringWriter ; 58 import java.util.ArrayList ; 59 import java.util.List ; 60 import java.util.Locale ; 61 import java.util.StringTokenizer ; 62 63 import freemarker.core.Environment; 64 import freemarker.log.Logger; 65 import freemarker.template.Configuration; 66 import freemarker.template.Template; 67 68 77 public class TemplateCache 78 { 79 private static final String ASTERISKSTR = "*"; 80 private static final String LOCALE_SEPARATOR = "_"; 81 private static final char ASTERISK = '*'; 82 private static final String CURRENT_DIR_PATH_PREFIX = "./"; 83 private static final String CURRENT_DIR_PATH = "/./"; 84 private static final String PARENT_DIR_PATH_PREFIX = "../"; 85 private static final String PARENT_DIR_PATH = "/../"; 86 private static final char SLASH = '/'; 87 private static final Logger logger = Logger.getLogger("freemarker.cache"); 88 89 private final TemplateLoader mainLoader; 90 94 95 private final CacheStorage storage; 96 97 private long delay = 5000; 98 99 private boolean localizedLookup = true; 100 101 private Configuration config; 102 103 109 public TemplateCache() 110 { 111 this(createDefaultTemplateLoader()); 112 } 113 114 private static TemplateLoader createDefaultTemplateLoader() { 115 try { 116 return new FileTemplateLoader(); 117 } catch(Exception e) { 118 logger.warn("Could not create a file template loader for current directory", e); 119 return null; 120 } 121 } 122 123 128 public TemplateCache(TemplateLoader loader) 129 { 130 this(loader, new MruCacheStorage(0, Integer.MAX_VALUE)); 131 } 132 133 138 public TemplateCache(TemplateLoader loader, CacheStorage storage) 139 { 140 this.mainLoader = loader; 141 this.storage = storage; 142 if(storage == null) 143 { 144 throw new IllegalArgumentException ("storage == null"); 145 } 146 } 147 148 153 public void setConfiguration(Configuration config) 154 { 155 this.config = config; 156 clear(); 157 } 158 159 public TemplateLoader getTemplateLoader() 160 { 161 return mainLoader; 162 } 163 164 public CacheStorage getCacheStorage() 165 { 166 return storage; 167 } 168 169 211 public Template getTemplate(String name, Locale locale, String encoding, boolean parse) 212 throws IOException 213 { 214 if (name == null) { 215 throw new IllegalArgumentException ("Argument \"name\" can't be null"); 216 } 217 if (locale == null) { 218 throw new IllegalArgumentException ("Argument \"locale\" can't be null"); 219 } 220 if (encoding == null) { 221 throw new IllegalArgumentException ("Argument \"encoding\" can't be null"); 222 } 223 name = normalizeName(name); 224 if(name == null) { 225 return null; 226 } 227 Template result = null; 228 if (mainLoader != null) { 229 result = getTemplate(mainLoader, name, locale, encoding, parse); 230 } 231 236 return result; 237 } 238 239 private Template getTemplate(TemplateLoader loader, String name, Locale locale, String encoding, boolean parse) 240 throws IOException 241 { 242 boolean debug = logger.isDebugEnabled(); 243 String debugName = debug ? name + "[" + locale + "," + encoding + (parse ? ",parsed] " : ",unparsed] ") : null; 244 TemplateKey tk = new TemplateKey(name, locale, encoding, parse); 245 synchronized (storage) 246 { 247 CachedTemplate cachedTemplate = (CachedTemplate)storage.get(tk); 248 long now = System.currentTimeMillis(); 249 long lastModified = -1L; 250 Object newlyFoundSource = null; 251 try 252 { 253 if (cachedTemplate != null) 254 { 255 if (now - cachedTemplate.lastChecked < delay) 257 { 258 if(debug) 259 { 260 logger.debug(debugName + "cached copy not yet stale; using cached."); 261 } 262 return cachedTemplate.template; 263 } 264 cachedTemplate.lastChecked = now; 266 267 newlyFoundSource = findTemplateSource(name, locale); 269 270 if (newlyFoundSource == null) 272 { 273 if(debug) 274 { 275 logger.debug(debugName + "no source found (removing from cache if it was cached)."); 276 } 277 storage.remove(tk); 278 return null; 279 } 280 281 lastModified = loader.getLastModified(newlyFoundSource); 284 boolean lastModifiedNotChanged = lastModified == cachedTemplate.lastModified; 285 boolean sourceEquals = newlyFoundSource.equals(cachedTemplate.source); 286 if(lastModifiedNotChanged && sourceEquals) 287 { 288 if(debug) 289 { 290 logger.debug(debugName + "using cached since " + 291 newlyFoundSource + " didn't change."); 292 } 293 cachedTemplate.lastChecked = now; 294 return cachedTemplate.template; 295 } 296 else 297 { 298 if(debug && !sourceEquals) 299 { 300 logger.debug("Updating source, info for cause: " + 301 "sourceEquals=" + sourceEquals + 302 ", newlyFoundSource=" + newlyFoundSource + 303 ", cachedTemplate.source=" + cachedTemplate.source); 304 } 305 if(debug && !lastModifiedNotChanged) 306 { 307 logger.debug("Updating source, info for cause: " + 308 "lastModifiedNotChanged=" + lastModifiedNotChanged + 309 ", cache lastModified=" + cachedTemplate.lastModified + 310 " != file lastModified=" + lastModified); 311 } 312 cachedTemplate.source = newlyFoundSource; 314 } 315 } 316 else 317 { 318 if(debug) 319 { 320 logger.debug("Could not find template in cache, " 321 + "creating new one; id=[" + 322 tk.name + "[" + tk.locale + "," + tk.encoding + 323 (tk.parse ? ",parsed] " : ",unparsed] ") + "]"); 324 } 325 326 newlyFoundSource = findTemplateSource(name, locale); 330 if (newlyFoundSource == null) 331 { 332 return null; 333 } 334 cachedTemplate = new CachedTemplate(); 335 cachedTemplate.source = newlyFoundSource; 336 cachedTemplate.lastChecked = now; 337 cachedTemplate.lastModified = lastModified = Long.MIN_VALUE; 338 storage.put(tk, cachedTemplate); 339 } 340 if(debug) 341 { 342 logger.debug("Compiling FreeMarker template " + 343 debugName + " from " + newlyFoundSource); 344 } 345 Object source = cachedTemplate.source; 347 cachedTemplate.template = 348 loadTemplate(loader, name, locale, encoding, parse, source); 349 cachedTemplate.lastModified = 350 lastModified == Long.MIN_VALUE 351 ? loader.getLastModified(source) 352 : lastModified; 353 return cachedTemplate.template; 354 } 355 finally 356 { 357 if(newlyFoundSource != null) 358 { 359 loader.closeTemplateSource(newlyFoundSource); 360 } 361 } 362 } 363 } 364 365 private Template loadTemplate(TemplateLoader loader, String name, Locale locale, String encoding, 366 boolean parse, Object source) 367 throws IOException 368 { 369 Template template; 370 Reader reader = loader.getReader(source, encoding); 371 try 372 { 373 if(parse) 374 { 375 try { 376 template = new Template(name, reader, config, encoding); 377 } 378 catch (Template.WrongEncodingException wee) { 379 encoding = wee.specifiedEncoding; 380 reader = loader.getReader(source, encoding); 381 template = new Template(name, reader, config, encoding); 382 } 383 template.setLocale(locale); 384 } 385 else 386 { 387 StringWriter sw = new StringWriter (); 390 char[] buf = new char[4096]; 391 for(;;) 392 { 393 int charsRead = reader.read(buf); 394 if (charsRead > 0) 395 { 396 sw.write(buf, 0, charsRead); 397 } 398 else if(charsRead == -1) 399 { 400 break; 401 } 402 } 403 template = Template.getPlainTextTemplate(name, sw.toString(), config); 404 template.setLocale(locale); 405 } 406 template.setEncoding(encoding); 407 } 408 finally 409 { 410 reader.close(); 411 } 412 return template; 413 } 414 415 420 public synchronized long getDelay() 421 { 422 return delay; 423 } 424 425 430 public synchronized void setDelay(long delay) 431 { 432 this.delay = delay; 433 } 434 435 438 public synchronized boolean getLocalizedLookup() 439 { 440 return localizedLookup; 441 } 442 443 446 public synchronized void setLocalizedLookup(boolean localizedLookup) 447 { 448 this.localizedLookup = localizedLookup; 449 } 450 451 456 public void clear() 457 { 458 synchronized (storage) { 459 storage.clear(); 460 } 461 } 462 463 public static String getFullTemplatePath(Environment env, String parentTemplateDir, String templateNameString) 464 { 465 if (!env.isClassicCompatible()) { 466 if (templateNameString.indexOf("://") >0) { 467 ; 468 } 469 else if (templateNameString.length() > 0 && templateNameString.charAt(0) == '/') { 470 int protIndex = parentTemplateDir.indexOf("://"); 471 if (protIndex >0) { 472 templateNameString = parentTemplateDir.substring(0, protIndex + 2) + templateNameString; 473 } else { 474 templateNameString = templateNameString.substring(1); 475 } 476 } 477 else { 478 templateNameString = parentTemplateDir + templateNameString; 479 } 480 } 481 return templateNameString; 482 } 483 484 private Object findTemplateSource(String name, Locale locale) 485 throws 486 IOException 487 { 488 if (localizedLookup) { 489 int lastDot = name.lastIndexOf('.'); 490 String prefix = lastDot == -1 ? name : name.substring(0, lastDot); 491 String suffix = lastDot == -1 ? "" : name.substring(lastDot); 492 String localeName = LOCALE_SEPARATOR + locale.toString(); 493 StringBuffer buf = new StringBuffer (name.length() + localeName.length()); 494 buf.append(prefix); 495 for (;;) 496 { 497 buf.setLength(prefix.length()); 498 String path = buf.append(localeName).append(suffix).toString(); 499 Object templateSource = acquireTemplateSource(path); 500 if (templateSource != null) 501 { 502 return templateSource; 503 } 504 int lastUnderscore = localeName.lastIndexOf('_'); 505 if (lastUnderscore == -1) 506 break; 507 localeName = localeName.substring(0, lastUnderscore); 508 } 509 return null; 510 } 511 else 512 { 513 return acquireTemplateSource(name); 514 } 515 } 516 517 private Object acquireTemplateSource(String path) throws IOException 518 { 519 int asterisk = path.indexOf(ASTERISK); 520 if(asterisk == -1) 522 { 523 return mainLoader.findTemplateSource(path); 524 } 525 StringTokenizer tok = new StringTokenizer (path, "/"); 526 int lastAsterisk = -1; 527 List tokpath = new ArrayList (); 528 while(tok.hasMoreTokens()) 529 { 530 String pathToken = tok.nextToken(); 531 if(pathToken.equals(ASTERISKSTR)) 532 { 533 if(lastAsterisk != -1) 534 { 535 tokpath.remove(lastAsterisk); 536 } 537 lastAsterisk = tokpath.size(); 538 } 539 tokpath.add(pathToken); 540 } 541 String basePath = concatPath(tokpath, 0, lastAsterisk); 542 String resourcePath = concatPath(tokpath, lastAsterisk + 1, tokpath.size()); 543 544 StringBuffer buf = new StringBuffer (path.length()).append(basePath); 545 int l = basePath.length(); 546 boolean debug = logger.isDebugEnabled(); 547 for(;;) 548 { 549 String fullPath = buf.append(resourcePath).toString(); 550 if(debug) 551 { 552 logger.debug("Trying to find template source " + fullPath); 553 } 554 Object templateSource = mainLoader.findTemplateSource(fullPath); 555 if(templateSource != null) 556 { 557 return templateSource; 558 } 559 if(l == 0) 560 { 561 return null; 562 } 563 l = basePath.lastIndexOf(SLASH, l - 2) + 1; 564 buf.setLength(l); 565 } 566 } 567 568 private String concatPath(List path, int from, int to) 569 { 570 StringBuffer buf = new StringBuffer ((to - from) * 16); 571 for(int i = from; i < to; ++i) 572 { 573 buf.append(path.get(i)).append('/'); 574 } 575 return buf.toString(); 576 } 577 578 private static String normalizeName(String name) { 579 if (name.indexOf("://") >0) { 580 return name; 581 } 582 for(;;) { 583 int parentDirPathLoc = name.indexOf(PARENT_DIR_PATH); 584 if(parentDirPathLoc == 0) { 585 return null; 588 } 589 if(parentDirPathLoc == -1) { 590 if(name.startsWith(PARENT_DIR_PATH_PREFIX)) { 591 return null; 593 } 594 break; 595 } 596 int previousSlashLoc = name.lastIndexOf(SLASH, parentDirPathLoc - 1); 597 name = name.substring(0, previousSlashLoc + 1) + 598 name.substring(parentDirPathLoc + PARENT_DIR_PATH.length()); 599 } 600 for(;;) { 601 int currentDirPathLoc = name.indexOf(CURRENT_DIR_PATH); 602 if(currentDirPathLoc == -1) { 603 if(name.startsWith(CURRENT_DIR_PATH_PREFIX)) { 604 name = name.substring(CURRENT_DIR_PATH_PREFIX.length()); 605 } 606 break; 607 } 608 name = name.substring(0, currentDirPathLoc) + 609 name.substring(currentDirPathLoc + CURRENT_DIR_PATH.length() - 1); 610 } 611 if(name.length() > 1 && name.charAt(0) == SLASH) { 613 name = name.substring(1); 614 } 615 return name; 616 } 617 618 622 private static final class TemplateKey 623 { 624 private final String name; 625 private final Locale locale; 626 private final String encoding; 627 private final boolean parse; 628 629 TemplateKey(String name, Locale locale, String encoding, boolean parse) 630 { 631 this.name = name; 632 this.locale = locale; 633 this.encoding = encoding; 634 this.parse = parse; 635 } 636 637 public boolean equals(Object o) 638 { 639 if (o instanceof TemplateKey) 640 { 641 TemplateKey tk = (TemplateKey)o; 642 return 643 parse == tk.parse && 644 name.equals(tk.name) && 645 locale.equals(tk.locale) && 646 encoding.equals(tk.encoding); 647 } 648 return false; 649 } 650 651 public int hashCode() 652 { 653 return 654 name.hashCode() ^ 655 locale.hashCode() ^ 656 encoding.hashCode() ^ 657 (parse ? Boolean.FALSE : Boolean.TRUE).hashCode(); 658 } 659 } 660 661 666 private static final class CachedTemplate 667 { 668 Template template; 669 Object source; 670 long lastChecked; 671 long lastModified; 672 } 673 } 674 | Popular Tags |