1 /*****************************************************************************/ 2 /* Software Testing Automation Framework (STAF) */ 3 /* (C) Copyright IBM Corp. 2002, 2007 */ 4 /* */ 5 /* This software is licensed under the Eclipse Public License (EPL) V1.0. */ 6 /*****************************************************************************/ 7 8 package com.ibm.staf.service.stax; 9 10 import java.net.InetAddress; 11 import java.net.UnknownHostException; 12 import java.util.Calendar; 13 import java.util.GregorianCalendar; 14 import java.util.Comparator; 15 import java.util.Collections; 16 import java.util.Date; 17 import java.util.HashMap; 18 import java.util.HashSet; 19 import java.util.Iterator; 20 import java.util.LinkedList; 21 import java.util.List; 22 import java.util.Map; 23 import java.util.Set; 24 25 import com.ibm.staf.STAFResult; 26 import com.ibm.staf.STAFUtil; 27 import com.ibm.staf.STAFHandle; 28 29 /** 30 * Caches STAX XML files. 31 */ 32 public class STAXFileCache 33 { 34 private static final int DEFAULT_CACHE_SIZE = 20; 35 36 /** 37 * LRU is the "Least Recently Used" cache replacement algorithm. 38 * If a new entry needs to be added to the cache and the cache is full, 39 * the least recently used cache entry will be removed. 40 */ 41 public static final int LRU = 1; 42 private static final String LRU_STRING = "LRU"; 43 44 /** 45 * LFU is the "Least Frequently Used" cache replacement algorithm 46 * that can also be configured to get rid of the oldest "stale" entry. 47 * If a new entry needs to be added to the cache and the cache is full, 48 * - If maxAge = 0 (the default indicating no entries are considered to 49 * be stale), the least frequently used cache entry will be removed 50 * - if maxAge > 0, if any cache entry is stale, the mleast recently used 51 * stale entry will be removed. Otherwise, if no cache entries are 52 * stale, the least frequently used cache entry will be removed. 53 */ 54 public static final int LFU = 2; 55 private static final String LFU_STRING = "LFU"; 56 57 private static STAXFileCache instance; 58 59 /** 60 * A map contain the cache entries 61 */ 62 private Map<CacheKey, FileCacheEntry> cache; 63 64 /** 65 * Maximum number of entries that will be stroed in the cache 66 */ 67 private int maxCacheSize; 68 69 /** 70 * Caching algorithm (LRU=1 or LFU=2) 71 */ 72 private int algorithm; 73 74 /** 75 * Maximum age before a cached document is considered "stale" 76 * if using the LFU algorithm. 0 indicates no maximum age. 77 */ 78 private MaxAge maxAge; 79 80 /** 81 * The number of times a cache hit has occurred for any item in the cache 82 */ 83 private long cacheHitCount = 0; 84 85 /** 86 * The number of times a cache miss has occurred for any item not in cache 87 * or that was stale (e.g. not up-to-date) 88 */ 89 private long cacheMissCount = 0; 90 91 /** 92 * Date that the cache was initialized or last purged 93 */ 94 private Date lastPurgeDate; 95 96 private Set<String> localMachineNames; 97 98 /** 99 * Initializes the STAXCache instance. 100 */ STAXFileCache()101 private STAXFileCache() 102 { 103 maxCacheSize = DEFAULT_CACHE_SIZE; 104 algorithm = LRU; // Default cache algorithm 105 maxAge = new MaxAge(); // The default is 0 = No maximum age 106 lastPurgeDate = new Date(); 107 cache = Collections.synchronizedMap( 108 new HashMap<CacheKey, FileCacheEntry>()); 109 110 localMachineNames = new HashSet<String>(); 111 addLocalMachineNames(); 112 } 113 114 /** 115 * Adds known local machine name aliases 116 */ addLocalMachineNames()117 private void addLocalMachineNames() 118 { 119 localMachineNames.add("local"); 120 localMachineNames.add("localhost"); 121 localMachineNames.add("127.0.0.1"); 122 123 try 124 { 125 InetAddress addr = InetAddress.getLocalHost(); 126 127 localMachineNames.add(addr.getHostAddress().toLowerCase()); 128 localMachineNames.add(addr.getHostName().toLowerCase()); 129 localMachineNames.add(addr.getCanonicalHostName().toLowerCase()); 130 } 131 catch (UnknownHostException e) 132 { 133 // Do nothing 134 } 135 } 136 137 /** 138 * Adds a known local machine name alias. 139 * 140 * @param name a local machine name alias such as "localhost". 141 */ addLocalMachineName(String name)142 public void addLocalMachineName(String name) 143 { 144 localMachineNames.add(name.toLowerCase()); 145 } 146 147 /** 148 * Gets the cache instance. 149 * 150 * @return the STAX document cache. 151 */ get()152 public static STAXFileCache get() 153 { 154 if (instance == null) 155 { 156 instance = new STAXFileCache(); 157 } 158 159 return instance; 160 } 161 162 /** 163 * Get the file separator for the operating system of the specified 164 * machine. 165 * 166 * @param machine A string containing the endpoint of a machine 167 * @param handle A STAFHandle object to use to submit the VAR RESOLVE 168 * request 169 * @return STAFResult If successful, rc = 0 and result contains the file 170 * separator. If fails, rc = non-zero and result contains an error 171 * message. 172 */ getFileSep(String machine, STAFHandle handle)173 public static STAFResult getFileSep(String machine, STAFHandle handle) 174 { 175 String fileSep = ""; 176 177 if (STAXFileCache.get().isLocalMachine(machine)) 178 { 179 // Assign the file separator for the local STAX machine 180 fileSep = STAX.fileSep; 181 return new STAFResult(STAFResult.Ok, fileSep); 182 } 183 184 // Get the file separator for the remote machine. 185 186 // Get the file separator from the machine cache if the remote 187 // machine is in the cache (so that don't have to submit a VAR 188 // RESOLVE request to the remote machine). 189 190 if (STAXMachineCache.get().checkCache(machine)) 191 { 192 fileSep = STAXMachineCache.get().getFileSep(machine); 193 return new STAFResult(STAFResult.Ok, fileSep); 194 } 195 196 // The remote machine is not in the machine cache, so submit a 197 // VAR RESOLVE request to get the file separator and add the 198 // machine/fileSep to the machine cache. 199 200 STAFResult result = handle.submit2( 201 machine, "VAR", "RESOLVE STRING {STAF/Config/Sep/File}"); 202 203 if (result.rc == STAFResult.Ok) 204 { 205 fileSep = result.result; 206 207 // Add the machine/fileSep to the machine cache 208 STAXMachineCache.get().addMachine(machine, fileSep); 209 } 210 211 return result; 212 } 213 214 215 /** 216 * Checks if the given machine name is a known alias for 217 * the local machine. 218 * 219 * @param machine the machine name to check. 220 * @return whether the machine is known to be an alias to the 221 * local machine. 222 */ isLocalMachine(String machine)223 public boolean isLocalMachine(String machine) 224 { 225 return localMachineNames.contains(formatEndpoint(machine)); 226 } 227 228 /** 229 * Formats a machine endpoint. This takes a STAF endpoint in the 230 * format [<Interface>://]<Logical or Physical Identifier>[@<Port>] 231 * and strips off the interface and port leaving only the logical 232 * or physical identifier. The identifier will also be converterd to 233 * lowercase. 234 * 235 * @param machine the machine (STAF endpoint) name to format. 236 * @return the logical or physical identifier of the endpoint. 237 */ formatEndpoint(String endpoint)238 public static String formatEndpoint(String endpoint) 239 { 240 // Strip the port 241 endpoint = STAFUtil.stripPortFromEndpoint(endpoint); 242 243 // Strip the interface 244 int index = endpoint.indexOf("://"); 245 if (index > -1) 246 { 247 endpoint = endpoint.substring(index + 3); 248 } 249 250 endpoint = endpoint.toLowerCase(); 251 252 return endpoint; 253 } 254 255 /** 256 * Gets a STAX document from the cache. This does not check to see if the 257 * cached copy is up-to-date. The method checkCache should first be called 258 * (if necessary) to determine if the cached copy exists and is up-to-date. 259 * 260 * @param machine the machine containg the XML file. 261 * @param filename the STAX XML file name. 262 * @param caseSensitiveFileName a flag indicating if the filename is 263 * case-sensitive 264 * @return a cached document or null if the document is not in the cache. 265 */ getDocument(String machine, String filename, boolean caseSensitiveFileName)266 public STAXDocument getDocument(String machine, String filename, 267 boolean caseSensitiveFileName) 268 { 269 STAXDocument doc = null; 270 271 machine = formatEndpoint(machine); 272 273 if (isLocalMachine(machine)) 274 { 275 machine = "local"; 276 } 277 278 FileCacheEntry item = cache.get( 279 new CacheKey(machine, filename, caseSensitiveFileName)); 280 281 if (item != null) 282 { 283 doc = item.getDocument().cloneDocument(); 284 item.hit(); 285 cacheHitCount++; 286 } 287 288 return doc; 289 } 290 291 /** 292 * Gets the contents of the cache in the sort order specified. 293 * 294 * @return a sorted list of STAXCache.CachedSTAX items. 295 */ getCacheContents(int sortBy)296 public List<FileCacheEntry> getCacheContents(int sortBy) 297 { 298 List<FileCacheEntry> cacheContents = null; 299 300 synchronized(cache) 301 { 302 cacheContents = new LinkedList<FileCacheEntry>(cache.values()); 303 } 304 305 if (sortBy == STAXFileCache.LRU) 306 { 307 // Sort the contents by the last hit date in descending order 308 309 Collections.sort( 310 cacheContents, 311 new Comparator<STAXFileCache.FileCacheEntry>() 312 { 313 public int compare(STAXFileCache.FileCacheEntry entry1, 314 STAXFileCache.FileCacheEntry entry2) 315 { 316 // Sort by most recent hit date first 317 return entry1.getLastHitDate().compareTo( 318 entry2.getLastHitDate()) * -1; 319 } 320 }); 321 } 322 else if (sortBy == STAXFileCache.LFU) 323 { 324 // Sort the contents as follows: 325 // - Sort first by whether stale or not. Put the non-stale 326 // documents before all stale documents. Sort the non-stale 327 // documents by the greatest number of hits, and if the same 328 // number of hits, then by recent hit date first. 329 // - Sort the stale documents by the most recent hit date first. 330 331 Collections.sort( 332 cacheContents, 333 new Comparator<STAXFileCache.FileCacheEntry>() 334 { 335 public int compare(STAXFileCache.FileCacheEntry e1, 336 STAXFileCache.FileCacheEntry e2) 337 { 338 boolean e1_isStale = isStale(e1.getLastHitDate()); 339 boolean e2_isStale = isStale(e2.getLastHitDate()); 340 341 if (e1_isStale == false && e2_isStale == false) 342 { 343 // Both entries are not stale. 344 // Sort by greatest number of hits first 345 346 int compareResult = (new Long(e1.getHits())). 347 compareTo(new Long(e2.getHits())); 348 349 if (compareResult == 0) 350 { 351 // Same number of hits 352 353 return e1.getLastHitDate().compareTo( 354 e2.getLastHitDate()) * -1; 355 } 356 else 357 { 358 return compareResult * -1; 359 } 360 } 361 else if (e1_isStale == true && e2_isStale == true) 362 { 363 // Both entries are stale. 364 // Sort by recent hit date first. 365 366 return e1.getLastHitDate().compareTo( 367 e2.getLastHitDate()) * -1; 368 } 369 else if (e1_isStale == false) 370 { 371 // e1_isStale == false && e2_isStale == true 372 return -1; 373 } 374 else 375 { 376 // e1_isStale == true && e2_isStale == false 377 return 1; 378 } 379 } 380 }); 381 } 382 383 return cacheContents; 384 } 385 386 /** 387 * Clears the contents of the cache and clears the cache statistics. 388 * 389 * @return the number of files cleared from the cache. 390 */ purge()391 public int purge() 392 { 393 synchronized(cache) 394 { 395 int cleared = cache.size(); 396 cache.clear(); 397 lastPurgeDate = new Date(); 398 399 // Clear the cache statistics 400 cacheHitCount = 0; 401 cacheMissCount = 0; 402 403 return cleared; 404 } 405 } 406 407 /** 408 * Converts a STAX date string to a java Date object. 409 * 410 * @param staxDate the STAX date string. 411 * @return a java Date object. 412 */ convertSTAXDate(String staxDate)413 public static Date convertSTAXDate(String staxDate) 414 { 415 String sYear = staxDate.substring(0,4); 416 String sMonth = staxDate.substring(4,4+2); 417 String sDay = staxDate.substring(6,6+2); 418 String sHour = staxDate.substring(9,9+2); 419 String sMinute = staxDate.substring(12,12+2); 420 String sSecond = staxDate.substring(15,15+2); 421 422 int year = Integer.parseInt(sYear); 423 424 // Calendar expects month to be zero-based (0 = January) 425 int month = Integer.parseInt(sMonth) - 1; 426 int day = Integer.parseInt(sDay); 427 int hour = Integer.parseInt(sHour); 428 int minute = Integer.parseInt(sMinute); 429 int second = Integer.parseInt(sSecond); 430 431 Calendar cal = Calendar.getInstance(); 432 cal.set(year, month, day, hour, minute, second); 433 434 // Chop off the milliseconds portion 435 // (Should be zero but Calendar sets it to the current system 436 // milliseconds) 437 long time = ((long)(cal.getTimeInMillis() / 1000))*1000; 438 439 return new Date(time); 440 } 441 442 /** 443 * Checks the cache to see if it contains an up-to-date copy of a 444 * STAX document. 445 * 446 * @param machine the machine that contains the STAX XML. 447 * @param filename the filename of the STAX XML. 448 * @param date the current last modification date of the file. 449 * @param caseSensitiveFileName a flag indicating if the filename is 450 * case-sensitive 451 * @return true if the item is cached, false if the item is not cached or 452 * the cached copy is old. 453 */ checkCache(String machine, String filename, Date date, boolean caseSensitiveFileName)454 public boolean checkCache(String machine, String filename, Date date, 455 boolean caseSensitiveFileName) 456 { 457 machine = formatEndpoint(machine); 458 459 if (isLocalMachine(machine)) 460 { 461 machine = "local"; 462 } 463 464 FileCacheEntry item = cache.get( 465 new CacheKey(machine, filename, caseSensitiveFileName)); 466 467 if (item != null) 468 { 469 // Make sure the modification dates are the same, if not the 470 // cache is old. 471 472 if (date.equals(item.getModDate())) 473 { 474 return true; 475 } 476 } 477 478 return false; 479 } 480 481 /** 482 * Adds a STAX document to the cache, or if it already exists then 483 * that means that the document's modification date has changed, so 484 * then it updates the cache entry with the new document, its new 485 * modification date, and its last hit date. 486 * 487 * @param filename the file name of the STAX xml file. 488 * @param doc the parsed STAX document. 489 * @param modDate the modification date of the STAX file. 490 * @param caseSensitiveFileName a flag indicating if the filename is 491 * case-sensitive 492 */ addDocument(String machine, String filename, STAXDocument doc, Date modDate, boolean caseSensitiveFileName)493 public void addDocument(String machine, String filename, STAXDocument doc, 494 Date modDate, boolean caseSensitiveFileName) 495 { 496 machine = formatEndpoint(machine); 497 498 if (isLocalMachine(machine)) 499 { 500 machine = "local"; 501 } 502 503 cacheMissCount++; 504 505 // Don't add the document if the cache is off 506 507 if (maxCacheSize == 0 ) 508 { 509 return; 510 } 511 512 CacheKey key = new CacheKey(machine, filename, caseSensitiveFileName); 513 FileCacheEntry item = null; 514 STAXDocument clonedDoc = doc.cloneDocument(); 515 516 synchronized(cache) 517 { 518 item = cache.get(key); 519 520 if (item == null) 521 { 522 // Add document to the cache 523 524 item = new FileCacheEntry( 525 machine, filename, clonedDoc, modDate, 526 caseSensitiveFileName); 527 528 // If the cache size isn't unlimited (-1) and the cache is 529 // full, remove an entry from the cache to make room for 530 // this new entry 531 532 if ((maxCacheSize != -1) && (cache.size() >= maxCacheSize)) 533 { 534 while(cache.size() >= maxCacheSize) 535 { 536 if (algorithm == LRU) 537 removeOldest(); 538 else 539 removeLfu(); 540 } 541 } 542 } 543 else 544 { 545 // Already in cache, so update the document, the document's 546 // modification date, and the date the document was added/ 547 // updated in the cache, and the last hit date. 548 549 item.setDocument(clonedDoc); 550 item.setModDate(modDate); 551 item.setLastHitDate(new Date()); 552 } 553 554 // Add or update the cache entry 555 556 cache.put(key, item); 557 } 558 } 559 560 /** 561 * Removes extra items from the cache when the STAX_CACHE_SIZE setting 562 * changes. 563 */ cleanup()564 public void cleanup() 565 { 566 // If the cache size isn't unlimited (-1) and the number of entries 567 // in the cache exceeds the maximum cache size (e.g. because the 568 // maximum cache size was just decreased), remove entries from the 569 // cache so that its total number of entries does not exceed the 570 // maximum size. 571 572 if ((maxCacheSize != -1) && (cache.size() > maxCacheSize)) 573 { 574 synchronized(cache) 575 { 576 while(cache.size() > maxCacheSize) 577 { 578 if (algorithm == LRU) 579 removeOldest(); 580 else 581 removeLfu(); 582 } 583 } 584 } 585 } 586 587 /** 588 * Removes the oldest entry in the cache. 589 * 590 * Must call when synchronized on the cache object. 591 */ removeOldest()592 private void removeOldest() 593 { 594 Iterator<FileCacheEntry> iter = cache.values().iterator(); 595 596 if (!iter.hasNext()) 597 { 598 // No items in the cache 599 return; 600 } 601 602 FileCacheEntry oldest = iter.next(); 603 604 while(iter.hasNext()) 605 { 606 FileCacheEntry cached = iter.next(); 607 608 if (cached.getLastHitDate().before(oldest.getLastHitDate())) 609 { 610 oldest = cached; 611 } 612 } 613 614 cache.remove(oldest.getKey()); 615 } 616 617 /** 618 * Removes the entry with the least number of hits, the Least 619 * Frequently Used entry in the cache, unless there is a "stale" 620 * document in the cache (one that hasn't been accessed within the 621 * maxAge time period, assuming maxAge is not 0 which means that 622 * no documents will be considered to be stale). Then, the most 623 * "stale" document in the cache will be removed instead. 624 * 625 * Must call when synchronized on the cache object. 626 */ removeLfu()627 private void removeLfu() 628 { 629 // Find the entry with the least number of hits and remove it 630 631 Iterator<FileCacheEntry> iter = cache.values().iterator(); 632 633 if (!iter.hasNext()) 634 { 635 // No items in the cache 636 return; 637 } 638 639 FileCacheEntry lowest = iter.next(); 640 FileCacheEntry oldest = lowest; 641 642 while(iter.hasNext()) 643 { 644 FileCacheEntry cached = iter.next(); 645 646 if (maxAge.getAmount() != 0) 647 { 648 // A maximum age has been specified so determine the 649 // oldest entry in the cache 650 651 if (cached.getLastHitDate().before(oldest.getLastHitDate())) 652 { 653 oldest = cached; 654 } 655 } 656 657 // Determine the entry with the lowest number of hits and 658 // the least recent hit date 659 660 if (cached.getHits() < lowest.getHits()) 661 { 662 lowest = cached; 663 } 664 else if (cached.getHits() == lowest.getHits()) 665 { 666 // Set to the entry that is oldest 667 668 if (cached.getLastHitDate().before(lowest.getLastHitDate())) 669 { 670 lowest = cached; 671 } 672 } 673 } 674 675 // If a maximum age has been configured (e.g. != 0), check if the 676 // document with the oldest last hit date is stale and if so, 677 // remove it and return 678 679 if (isStale(oldest.getLastHitDate())) 680 { 681 cache.remove(oldest.getKey()); 682 683 return; 684 } 685 686 // Otherwise, remove the document with the least number of hits 687 // (the Least Frequently Used entry) 688 689 cache.remove(lowest.getKey()); 690 } 691 692 /** 693 * Check if a document is stale by checking if a document's last hit 694 * timestamp plus the maxAge is earlier than the current timestamp. 695 */ isStale(Date lastHitDate)696 private boolean isStale(Date lastHitDate) 697 { 698 // A maxAge amount of 0 means no maximum age, so no document will 699 // be considered to be stale 700 701 if (maxAge.getAmount() == 0) 702 return false; 703 704 Calendar calendar = new GregorianCalendar(); 705 706 // Tell the calendar to the last hit date/time passed in 707 calendar.setTime(lastHitDate); 708 709 // Add the time it takes before a document is considered stale to the 710 // last hit date 711 712 calendar.add(maxAge.getTimeField(), maxAge.getAmount()); 713 714 // Check if the last hit timestamp + maxAge is earlier than the 715 // current date/time 716 717 return calendar.before(Calendar.getInstance()); 718 } 719 720 /** 721 * Sets the maximum number of STAX files to cache. 722 * 723 * @param size the maximum STAX files that will be stored in cache. 724 */ setMaxCacheSize(int size)725 public void setMaxCacheSize(int size) 726 { 727 this.maxCacheSize = size; 728 cleanup(); 729 } 730 731 /** 732 * Gets the maximum number of STAX files that can be held in cache. 733 * 734 * @return the maximum cache size. 735 */ getMaxCacheSize()736 public int getMaxCacheSize() 737 { 738 return this.maxCacheSize; 739 } 740 741 /** 742 * Sets the cache algorithm used to determine the entry to be 743 * removed when the cache is full. 744 * 745 * @return A STAFResult object with rc 0 if the algorithm was 746 * set successfully. If an invalid algorithm is 747 * specified, its rc will be set to a non-zero value 748 * with an error message in its result. 749 */ setAlgorithm(String algorithm)750 public STAFResult setAlgorithm(String algorithm) 751 { 752 if (algorithm.equalsIgnoreCase(LRU_STRING)) 753 { 754 this.algorithm = LRU; 755 } 756 else if (algorithm.equalsIgnoreCase(LFU_STRING)) 757 { 758 this.algorithm = LFU; 759 } 760 else 761 { 762 return new STAFResult( 763 STAFResult.InvalidValue, 764 "FILECACHEALGORITHM must be set to LRU or LFU. " + 765 "Invalid value: " + algorithm); 766 } 767 768 return new STAFResult(STAFResult.Ok); 769 } 770 771 /** 772 * Gets the cache algorithm used to determine the entry to be 773 * removed when the cache is full. 774 * 775 * @return the cache algorithm. 776 */ getAlgorithm()777 public int getAlgorithm() 778 { 779 return this.algorithm; 780 } 781 782 /** 783 * Gets the cache algorithm in its String form 784 * 785 * @return the cache algorithm's name. 786 */ getAlgorithmString()787 public String getAlgorithmString() 788 { 789 if (this.algorithm == LRU) 790 return LRU_STRING; 791 else 792 return LFU_STRING; 793 } 794 795 /** 796 * Sets the maximum age for cached STAX documents. 797 * This is used only when the cache algorithm is LFU to determine 798 * if a document is stale. 799 * 800 * @param age the maximum age for a cached document (e.g. "30", 801 * "30s", "5m", "2h", "1d", "1w"). 802 */ setMaxAge(String maxAge)803 public STAFResult setMaxAge(String maxAge) 804 { 805 try 806 { 807 this.maxAge = new MaxAge(maxAge); 808 } 809 catch (Exception e) 810 { 811 return new STAFResult(STAFResult.InvalidValue, e.getMessage()); 812 } 813 814 return new STAFResult(STAFResult.Ok); 815 } 816 817 /** 818 * Gets the maximum age for cached STAX documents. 819 * 820 * @return the maximum age. 821 */ getMaxAge()822 public MaxAge getMaxAge() 823 { 824 return this.maxAge; 825 } 826 getCacheHitCount()827 public long getCacheHitCount() 828 { 829 return this.cacheHitCount; 830 } 831 getCacheMissCount()832 public long getCacheMissCount() 833 { 834 return this.cacheMissCount; 835 } 836 getLastPurgeDate()837 public Date getLastPurgeDate() 838 { 839 return this.lastPurgeDate; 840 } 841 842 /** 843 * This class's constructor sets its time field and amount field 844 * based on the maxAge string that is passed in the format of 845 * <Number>[s|m|h|d|w] so that it can be used to "add" the time 846 * to the last hit date to determine if a document is stale. 847 */ 848 private static class MaxAge 849 { 850 private static String sMAX_AGE_ERROR_MSG = 851 "The MAXFILECACHEAGE value may be expressed in seconds, " + 852 "minutes, hours, days, or weeks. Its format is " + 853 "<Number>[s|m|h|d|w] where <Number> must be an integer from 0 " + 854 "to 2147483647 and indicates seconds unless one of the " + 855 "following case-insensitive suffixes is specified: " + 856 "s (for seconds), m (for minutes), h (for hours), " + 857 "d (for days), or w (for weeks). If <Number> is 0, this " + 858 "means that there is no maximum age.\n\nExamples: \n" + 859 " 0 specifies no maximum age, \n" + 860 " 30 specifies 30 seconds, \n" + 861 " 30s specifies 30 seconds, \n" + 862 " 5m specifies 5 minutes, \n" + 863 " 2h specifies 2 hours, \n" + 864 " 1d specifies 1 day, \n" + 865 " 1w specifies 1 week."; 866 867 /** 868 * The value represented as a string (e.g. "30", "30s", "1m", "2h", 869 * "1d", "1w") 870 */ 871 private String displayString = "0"; 872 873 /** 874 * The time field (e.g. Calendar.SECOND, Calendar.MINUTE, 875 * Calendar.HOUR_OF_DAY, Calendar.Date, or Calendar.WEEK_OF_YEAR 876 */ 877 private int timeField = Calendar.SECOND; 878 879 /** 880 * The amount of date or time to be added 881 */ 882 private int amount = 0; 883 MaxAge()884 private MaxAge() 885 { 886 // Do nothing. Sets to 0 which means No maximum age 887 } 888 MaxAge(String maxAge)889 public MaxAge(String maxAge) throws Exception 890 { 891 maxAge = maxAge.trim(); 892 893 if ((maxAge == null) || (maxAge.length() == 0)) 894 throw new Exception(sMAX_AGE_ERROR_MSG); 895 896 displayString = maxAge; 897 898 // Check if the max age string is not all digits 899 900 try 901 { 902 amount = (new Integer(maxAge)).intValue(); 903 } 904 catch (NumberFormatException e) 905 { 906 // Get max age type (last character of max age string) 907 908 String maxAgeType = maxAge.substring( 909 maxAge.length() - 1).toLowerCase(); 910 911 if (maxAgeType.equals("s")) 912 timeField = Calendar.SECOND; 913 else if (maxAgeType.equals("m")) 914 timeField = Calendar.MINUTE; 915 else if (maxAgeType.equals("h")) 916 timeField = Calendar.HOUR_OF_DAY; 917 else if (maxAgeType.equals("d")) 918 timeField = Calendar.DATE; 919 else if (maxAgeType.equals("w")) 920 timeField = Calendar.WEEK_OF_YEAR; 921 else 922 throw new Exception(sMAX_AGE_ERROR_MSG); 923 924 // Assign numeric max age (all characters except the last one) 925 926 try 927 { 928 amount = (new Integer( 929 maxAge.substring(0, maxAge.length() - 1))).intValue(); 930 } 931 catch (NumberFormatException e2) 932 { 933 throw new Exception(sMAX_AGE_ERROR_MSG); 934 } 935 } 936 937 if (amount < 0) 938 throw new Exception(sMAX_AGE_ERROR_MSG); 939 } 940 toString()941 public String toString() 942 { 943 return displayString; 944 } 945 getTimeField()946 public int getTimeField() 947 { 948 return timeField; 949 } 950 getAmount()951 public int getAmount() 952 { 953 return amount; 954 } 955 } 956 957 private static class CacheKey 958 { 959 private String machine; 960 private String filename; 961 CacheKey(String machine, String filename, boolean caseSensitiveFileName)962 public CacheKey(String machine, String filename, 963 boolean caseSensitiveFileName) 964 { 965 this.machine = machine.toLowerCase(); 966 967 if (caseSensitiveFileName) 968 this.filename = filename; 969 else 970 this.filename = filename.toLowerCase(); 971 } 972 getMachine()973 public String getMachine() 974 { 975 return machine; 976 } 977 getFilename()978 public String getFilename() 979 { 980 return filename; 981 } 982 983 /* (non-Javadoc) 984 * @see java.lang.Object#equals(java.lang.Object) 985 */ equals(Object o)986 public boolean equals(Object o) 987 { 988 if (this == o) 989 { 990 return true; 991 } 992 993 if (o instanceof CacheKey) 994 { 995 CacheKey k = (CacheKey)o; 996 997 return getMachine().equals(k.getMachine()) && 998 getFilename().equals(k.getFilename()); 999 } 1000 1001 return false; 1002 } 1003 1004 /* (non-Javadoc) 1005 * @see java.lang.Object#hashCode() 1006 */ hashCode()1007 public int hashCode() 1008 { 1009 return machine.hashCode() + filename.hashCode(); 1010 } 1011 1012 } 1013 1014 /** 1015 * A Cached STAX document. 1016 */ 1017 public static class FileCacheEntry 1018 { 1019 private String machine; 1020 private String filename; 1021 private boolean caseSensitiveFileName = true; 1022 1023 private Date date; 1024 private Date modDate; 1025 private Date lastHit; 1026 private long hits; 1027 private STAXDocument doc; 1028 1029 private CacheKey key; 1030 1031 /** 1032 * Creates a cached document. 1033 * 1034 * @param doc the document to cache. 1035 */ FileCacheEntry(String machine, String filename, STAXDocument doc, Date modDate, boolean caseSensitiveFileName)1036 public FileCacheEntry(String machine, String filename, 1037 STAXDocument doc, Date modDate, 1038 boolean caseSensitiveFileName) 1039 { 1040 this.machine = machine; 1041 this.filename = filename; 1042 this.caseSensitiveFileName = caseSensitiveFileName; 1043 this.doc = doc; 1044 this.modDate = modDate; 1045 this.date = new Date(); 1046 this.lastHit = new Date(); 1047 this.hits = 0; 1048 this.key = new CacheKey(machine, filename, caseSensitiveFileName); 1049 } 1050 1051 /** 1052 * Gets the key used to cache this entry. 1053 * 1054 * @return the cache key for this entry. 1055 */ getKey()1056 public CacheKey getKey() 1057 { 1058 return key; 1059 } 1060 1061 /** 1062 * Gets the name of the machine that provided the file. 1063 * 1064 * @return the machine name. 1065 */ getMachine()1066 public String getMachine() 1067 { 1068 return machine; 1069 } 1070 1071 /** 1072 * Gets the name/path to the file as it exists on the providing 1073 * machine. 1074 * 1075 * @return the name/path to the file. 1076 */ getFilename()1077 public String getFilename() 1078 { 1079 return filename; 1080 } 1081 1082 /** 1083 * Gets a flag indicating if file names are case-sensitive 1084 * 1085 * @return true if the file name is case-sensitive; false if not 1086 * case-sensitive. 1087 */ getCaseSensitive()1088 public boolean getCaseSensitive() 1089 { 1090 return caseSensitiveFileName; 1091 } 1092 1093 /** 1094 * Gets the STAX document. 1095 * 1096 * @return the cached STAX document. 1097 */ getDocument()1098 public STAXDocument getDocument() 1099 { 1100 return doc; 1101 } 1102 1103 /** 1104 * Sets the STAX document. 1105 */ setDocument(STAXDocument doc)1106 public void setDocument(STAXDocument doc) 1107 { 1108 this.doc = doc; 1109 } 1110 1111 /** 1112 * Updates the cache hit for this document. 1113 */ hit()1114 public void hit() 1115 { 1116 lastHit = new Date(); 1117 hits++; 1118 } 1119 1120 /** 1121 * Gets the number of cache hits for this document. 1122 * 1123 * @return the number of cache hits. 1124 */ getHits()1125 public long getHits() 1126 { 1127 return hits; 1128 } 1129 1130 /** 1131 * Gets the date when the STAX file was last modified. 1132 * 1133 * @return 1134 */ getModDate()1135 public Date getModDate() 1136 { 1137 return modDate; 1138 } 1139 1140 /** 1141 * Sets the date when the STAX file was last modified. 1142 */ setModDate(Date modDate)1143 public void setModDate(Date modDate) 1144 { 1145 this.modDate = modDate; 1146 } 1147 1148 /** 1149 * Gets last time the document in the cache was accessed. 1150 * 1151 * @return the last cache hit time. 1152 */ getLastHitDate()1153 public Date getLastHitDate() 1154 { 1155 return lastHit; 1156 } 1157 1158 /** 1159 * Sets the date when the document was last accessed in the 1160 * cache. 1161 */ setLastHitDate(Date lastHit)1162 public void setLastHitDate(Date lastHit) 1163 { 1164 this.lastHit = lastHit; 1165 } 1166 1167 /** 1168 * Gets the date when this document was added to the cache. 1169 * 1170 * @return the date the document was added to the cache. 1171 */ getAddDate()1172 public Date getAddDate() 1173 { 1174 return date; 1175 } 1176 1177 /* (non-Javadoc) 1178 * @see java.lang.Object#equals(java.lang.Object) 1179 */ equals(Object o)1180 public boolean equals(Object o) 1181 { 1182 if (this == o) 1183 { 1184 return true; 1185 } 1186 1187 if (o instanceof FileCacheEntry) 1188 { 1189 FileCacheEntry o1 = (FileCacheEntry)o; 1190 1191 // Two documents are equivalent if they have the same machine, 1192 // the same filename (case-insensitive if Windows and case- 1193 // sensitive if Unix), and the same last modified date 1194 1195 boolean equals = 1196 this.getMachine().equalsIgnoreCase(o1.getMachine()) && 1197 ((this.getCaseSensitive() && 1198 this.getFilename().equals(o1.getFilename())) || 1199 (!this.getCaseSensitive() && 1200 this.getFilename().equalsIgnoreCase(o1.getFilename()))) && 1201 this.getModDate().equals(o1.getModDate()); 1202 1203 return equals; 1204 } 1205 1206 return false; 1207 } 1208 } 1209 } 1210