1 24 25 package org.objectweb.cjdbc.controller.cache.result; 26 27 import java.util.ArrayList ; 28 import java.util.HashMap ; 29 import java.util.HashSet ; 30 import java.util.Iterator ; 31 32 import org.objectweb.cjdbc.common.i18n.Translate; 33 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest; 34 import org.objectweb.cjdbc.common.sql.CreateRequest; 35 import org.objectweb.cjdbc.common.sql.ParsingGranularities; 36 import org.objectweb.cjdbc.common.sql.RequestType; 37 import org.objectweb.cjdbc.common.sql.SelectRequest; 38 import org.objectweb.cjdbc.common.sql.UpdateRequest; 39 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema; 40 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags; 41 import org.objectweb.cjdbc.controller.cache.CacheException; 42 import org.objectweb.cjdbc.controller.cache.CacheStatistics; 43 import org.objectweb.cjdbc.controller.cache.result.entries.AbstractResultCacheEntry; 44 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryEager; 45 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryNoCache; 46 import org.objectweb.cjdbc.controller.cache.result.entries.ResultCacheEntryRelaxed; 47 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseSchema; 48 import org.objectweb.cjdbc.controller.cache.result.schema.CacheDatabaseTable; 49 import org.objectweb.cjdbc.controller.cache.result.threads.EagerCacheThread; 50 import org.objectweb.cjdbc.controller.cache.result.threads.RelaxedCacheThread; 51 import org.objectweb.cjdbc.controller.virtualdatabase.ControllerResultSet; 52 import org.objectweb.cjdbc.driver.Field; 53 54 78 public abstract class ResultCache extends AbstractResultCache 79 { 80 90 private int maxEntries; 92 93 private long pendingQueryTimeout = 0; 94 private HashMap queries; 96 private HashSet pendingQueries; 98 private HashSet cachingRules; 100 private ResultCacheRule defaultRule; 101 private ArrayList relaxedCache; 102 103 private AbstractResultCacheEntry lruHead; 105 private AbstractResultCacheEntry lruTail; 107 108 protected CacheDatabaseSchema cdbs; 110 111 private CacheStatistics stats; 112 113 private RelaxedCacheThread relaxedThread; 114 private static final boolean[] TRUE_TRUE = new boolean[]{true, 115 true }; 116 private boolean flushingCache; 117 private EagerCacheThread eagerThread; 118 private ArrayList eagerCache; 119 120 123 124 130 public ResultCache(int maxEntries, int pendingTimeout) 131 { 132 this.maxEntries = maxEntries; 133 this.pendingQueryTimeout = pendingTimeout; 134 cdbs = null; 135 stats = new CacheStatistics(); 136 queries = new HashMap (1000, (float) 0.75); 137 pendingQueries = new HashSet (); 138 cachingRules = new HashSet (); 139 relaxedCache = new ArrayList (); 140 eagerCache = new ArrayList (); 141 lruHead = null; 142 lruTail = null; 143 defaultRule = null; 144 relaxedThread = new RelaxedCacheThread(this); 145 relaxedThread.setPriority(9); 146 relaxedThread.start(); 147 eagerThread = new EagerCacheThread(this); 148 eagerThread.setPriority(9); 149 eagerThread.start(); 150 } 151 152 155 public synchronized void shutdown() 156 { 157 relaxedThread.shutdown(); 158 eagerThread.shutdown(); 159 } 160 161 167 public int getPendingQueryTimeout() 168 { 169 return (int) (pendingQueryTimeout / 1000); 170 } 171 172 178 public void setPendingQueryTimeout(int pendingQueryTimeout) 179 { 180 this.pendingQueryTimeout = pendingQueryTimeout * 1000L; 181 } 182 183 188 public HashMap getQueries() 189 { 190 return this.queries; 191 } 192 193 199 public void setDatabaseSchema(DatabaseSchema dbs) 200 { 201 if (cdbs == null) 202 { 203 logger.info(Translate.get("resultcache.setting.database.schema")); 204 cdbs = new CacheDatabaseSchema(dbs); 205 } 206 else 207 { CacheDatabaseSchema newSchema = new CacheDatabaseSchema(dbs); 209 ArrayList tables = cdbs.getTables(); 210 ArrayList newTables = newSchema.getTables(); 211 if (newTables == null) 212 { logger.info(Translate.get("resultcache.flusing.whole.cache")); 214 flushCache(); 215 cdbs = null; 216 return; 217 } 218 219 for (int i = 0; i < tables.size(); i++) 221 { 222 CacheDatabaseTable t = (CacheDatabaseTable) tables.get(i); 223 if (!newSchema.hasTable(t.getName())) 224 { 225 t.invalidateAll(); 226 cdbs.removeTable(t); 227 if (logger.isInfoEnabled()) 228 logger.info(Translate 229 .get("resultcache.removing.table", t.getName())); 230 } 231 } 232 233 int size = newTables.size(); 235 for (int i = 0; i < size; i++) 236 { 237 CacheDatabaseTable t = (CacheDatabaseTable) newTables.get(i); 238 if (!cdbs.hasTable(t.getName())) 239 { 240 cdbs.addTable(t); 241 if (logger.isInfoEnabled()) 242 logger.info(Translate.get("resultcache.adding.table", t.getName())); 243 } 244 } 245 } 246 } 247 248 254 public void mergeDatabaseSchema(DatabaseSchema dbs) 255 { 256 try 257 { 258 logger.info(Translate.get("resultcache.merging.new.database.schema")); 259 cdbs.mergeSchema(new CacheDatabaseSchema(dbs)); 260 } 261 catch (Exception e) 262 { 263 logger.error(Translate.get("resultcache.error.while.merging", e)); 264 } 265 } 266 267 273 public void addCachingRule(ResultCacheRule rule) 274 { 275 cachingRules.add(rule); 276 } 277 278 281 public ResultCacheRule getDefaultRule() 282 { 283 return defaultRule; 284 } 285 286 289 public void setDefaultRule(ResultCacheRule defaultRule) 290 { 291 this.defaultRule = defaultRule; 292 } 293 294 302 private CacheBehavior getCacheBehavior(SelectRequest request) 303 { 304 CacheBehavior behavior = null; 305 for (Iterator iter = cachingRules.iterator(); iter.hasNext();) 306 { 307 behavior = ((ResultCacheRule) iter.next()).matches(request); 308 if (behavior != null) 309 { 310 break; 311 } 312 } 313 if (behavior == null) 314 behavior = defaultRule.getCacheBehavior(); 315 if (logger.isDebugEnabled()) 316 logger.debug(Translate.get("resultcache.behavior.for.request", 317 new String []{request.getSQL(), behavior.getType()})); 318 return behavior; 319 } 320 321 324 325 332 private String getCacheKeyFromRequest(SelectRequest request) 333 { 334 return request.getLogin() + "," + request.getSQL(); 335 } 336 337 347 public boolean[] needInvalidate(ControllerResultSet result, 348 UpdateRequest request) 349 { 350 HashMap updatedValues = request.getUpdatedValues(); 351 boolean needInvalidate = false; 352 boolean needToSendQuery = false; 353 String value; 354 String columnName; 355 try 356 { 357 if ((result == null) || (result.getData() == null) 359 || (result.getData().size() != 1)) 360 return TRUE_TRUE; 361 } 362 catch (Exception e) 363 { 364 return TRUE_TRUE; 365 } 366 Field[] fields = result.getFields(); 367 ArrayList data = result.getData(); 368 int size = fields.length; 369 for (Iterator iter = updatedValues.keySet().iterator(); iter.hasNext();) 370 { 371 columnName = (String ) iter.next(); 372 value = (String ) updatedValues.get(columnName); 373 for (int i = 0; i < size; i++) 374 { 377 if (columnName.equals(fields[i].getFieldName())) 380 { 381 Object o = ((Object []) data.get(0))[i]; 382 if (!value.equals(o)) 383 { 384 return TRUE_TRUE; 388 } 389 else 390 break; 391 } 392 } 393 needToSendQuery = true; 396 } 400 return new boolean[]{needInvalidate, needToSendQuery}; 401 } 402 403 411 public void addToCache(SelectRequest request, ControllerResultSet result) 412 throws CacheException 413 { 414 boolean notifyThread = false; 415 416 try 417 { 418 synchronized (pendingQueries) 419 { 420 removeFromPendingQueries(request); 423 424 String sqlQuery = getCacheKeyFromRequest(request); 425 426 if (request.getCacheAbility() == RequestType.UNCACHEABLE) 428 throw new CacheException(Translate.get( 429 "resultcache.uncacheable.request", sqlQuery)); 430 431 if (result == null) 432 throw new CacheException(Translate.get("resultcache.null.result", 433 sqlQuery)); 434 435 if (result.hasMoreData()) 437 { 438 logger.info(Translate.get("resultcache.streamed.resultset", request 439 .getSQLShortForm(20))); 440 return; 441 } 442 443 if (logger.isDebugEnabled()) 444 logger.debug(Translate.get("resultcache.adding.query", sqlQuery)); 445 446 AbstractResultCacheEntry ce; 447 synchronized (queries) 448 { 449 ce = (AbstractResultCacheEntry) queries.get(sqlQuery); 451 if (ce == null) 452 { 453 CacheBehavior behavior = getCacheBehavior(request); 456 ce = behavior.getCacheEntry(request, result, this); 457 if (ce instanceof ResultCacheEntryNoCache) 458 return; 459 460 if (maxEntries > 0) 462 { 463 int size = queries.size(); 464 if (size >= maxEntries) 465 removeOldest(); 467 } 468 queries.put(sqlQuery, ce); 470 471 notifyThread = true; 472 } 473 else 474 { if (ce.isValid()) 476 logger.warn(Translate.get( 477 "resultcache.modifying.result.valid.entry", sqlQuery)); 478 ce.setResult(result); 479 } 480 481 if (lruHead != null) 483 { 484 lruHead.setPrev(ce); 485 ce.setNext(lruHead); 486 ce.setPrev(null); 487 } 488 if (lruTail == null) 489 lruTail = ce; 490 lruHead = ce; } 492 processAddToCache(ce); 493 494 if (notifyThread) 498 { 499 if (ce instanceof ResultCacheEntryRelaxed) 501 { 502 ResultCacheEntryRelaxed qcer = (ResultCacheEntryRelaxed) ce; 503 synchronized (relaxedThread) 504 { 505 relaxedCache.add(qcer); 506 if (qcer.getDeadline() < relaxedThread.getThreadWakeUpTime() 507 || relaxedThread.getThreadWakeUpTime() == 0) 508 { 509 relaxedThread.notify(); 510 } 511 } 512 } 513 else if (ce instanceof ResultCacheEntryEager) 514 { 515 ResultCacheEntryEager qcee = (ResultCacheEntryEager) ce; 517 if (qcee.getDeadline() != AbstractResultCacheEntry.NO_DEADLINE) 518 { synchronized (eagerThread) 520 { 521 eagerCache.add(qcee); 522 if (qcee.getDeadline() < eagerThread.getThreadWakeUpTime() 523 || eagerThread.getThreadWakeUpTime() == 0) 524 { 525 eagerThread.notify(); 526 } 527 } 528 } 529 } 530 } 531 } 532 } 533 catch (OutOfMemoryError oome) 534 { 535 flushCache(); 536 System.gc(); 537 logger.warn(Translate.get("cache.memory.error.cache.flushed", this 538 .getClass())); 539 } 540 } 541 542 547 protected abstract void processAddToCache(AbstractResultCacheEntry qe); 548 549 564 public AbstractResultCacheEntry getFromCache(SelectRequest request, 565 boolean addToPendingQueries) 566 { 567 stats.addSelect(); 568 569 if (request.getCacheAbility() == RequestType.UNCACHEABLE) 570 { 571 stats.addUncacheable(); 572 return null; 573 } 574 575 String sqlQuery = getCacheKeyFromRequest(request); 576 577 synchronized (pendingQueries) 579 { 580 if (addToPendingQueries) 581 { 582 long timeout = pendingQueryTimeout; 583 while (pendingQueries.contains(sqlQuery)) 587 { 588 try 589 { 590 if (logger.isDebugEnabled()) 591 logger.debug(Translate.get("resultcache.waiting.pending.query", 592 sqlQuery)); 593 594 if (timeout > 0) 595 { 596 long start = System.currentTimeMillis(); 597 pendingQueries.wait(pendingQueryTimeout); 598 long end = System.currentTimeMillis(); 599 timeout = timeout - (end - start); 600 if (timeout <= 0) 601 { 602 logger.warn(Translate.get("resultcache.pending.query.timeout")); 603 break; 604 } 605 } 606 else 607 pendingQueries.wait(); 608 } 609 catch (InterruptedException e) 610 { 611 logger.warn(Translate.get("resultcache.pending.query.timeout")); 612 break; 613 } 614 } 615 } 616 617 AbstractResultCacheEntry ce; 619 synchronized (queries) 620 { 621 ce = (AbstractResultCacheEntry) queries.get(sqlQuery); 622 if (ce == null) 623 { if (addToPendingQueries) 626 { 627 pendingQueries.add(sqlQuery); 628 if (logger.isDebugEnabled()) 630 { 631 logger.debug(Translate.get("resultcache.cache.miss")); 632 logger.debug(Translate.get( 633 "resultcache.adding.to.pending.queries", sqlQuery)); 634 } 635 } 636 return null; 637 } 638 else 639 { AbstractResultCacheEntry before = ce.getPrev(); 642 if (before != null) 643 { 644 AbstractResultCacheEntry after = ce.getNext(); 645 before.setNext(after); 646 if (after != null) 647 after.setPrev(before); 648 else 649 lruTail = before; 651 ce.setNext(lruHead); 652 ce.setPrev(null); 653 if (lruHead != ce) 654 lruHead.setPrev(ce); 655 lruHead = ce; 656 } 657 } 659 } 660 661 if (ce.getResult() == null) 662 { 663 if (addToPendingQueries) 664 { 665 pendingQueries.add(sqlQuery); 666 if (logger.isDebugEnabled()) 668 { 669 logger.debug(Translate.get("resultcache.cache.miss")); 670 logger.debug(Translate.get("resultcache.adding.to.pending.queries", 671 sqlQuery)); 672 } 673 } 674 if (ce.isValid() && logger.isInfoEnabled()) 675 logger.info(Translate.get("resultcache.valid.entry.without.result", 676 ce.getRequest().getSQL())); 677 } 678 else 679 { 680 if (logger.isDebugEnabled()) 681 logger.debug(Translate.get("resultcache.cache.hit", sqlQuery)); 682 stats.addHits(); 683 } 684 685 return ce; 686 } 687 } 688 689 696 public void removeFromCache(SelectRequest request) 697 { 698 String sqlQuery = request.getSQL(); 699 700 if (logger.isDebugEnabled()) 701 logger.debug("Removing from cache: " + sqlQuery); 702 703 synchronized (queries) 704 { 705 AbstractResultCacheEntry ce = (AbstractResultCacheEntry) queries 707 .remove(sqlQuery); 708 if (ce == null) 709 return; else 711 { 712 ce.setResult(null); 714 AbstractResultCacheEntry before = ce.getPrev(); 716 AbstractResultCacheEntry after = ce.getNext(); 717 if (before != null) 718 { 719 before.setNext(after); 720 if (after != null) 721 after.setPrev(before); 722 else 723 lruTail = before; 725 } 726 else 727 { lruHead = ce.getNext(); 729 if (after != null) 730 after.setPrev(null); 731 else 732 lruTail = before; 734 } 735 ce.setNext(null); 737 ce.setPrev(null); 738 } 739 } 740 } 741 742 747 public void removeFromPendingQueries(SelectRequest request) 748 { 749 String sqlQuery = getCacheKeyFromRequest(request); 750 751 synchronized (pendingQueries) 752 { 753 if (pendingQueries.remove(sqlQuery)) 756 { 757 if (logger.isDebugEnabled()) 758 logger.debug(Translate.get("resultcache.removing.pending.query", 759 sqlQuery)); 760 pendingQueries.notifyAll(); 761 } 762 else 763 logger.warn(Translate.get("resultcache.removing.pending.query.failed", 764 sqlQuery)); 765 } 766 } 767 768 771 public abstract boolean isUpdateNecessary(UpdateRequest request) 772 throws CacheException; 773 774 782 public void writeNotify(AbstractWriteRequest request) throws CacheException 783 { 784 if (request.isInsert()) 786 stats.addInsert(); 787 else if (request.isUpdate()) 788 stats.addUpdate(); 789 else if (request.isDelete()) 790 stats.addDelete(); 791 else if (request.isCreate()) 792 { 793 stats.addCreate(); 794 if (parsingGranularity != ParsingGranularities.NO_PARSING) 796 { 797 CreateRequest createRequest = (CreateRequest) request; 798 if (createRequest.altersDatabaseSchema() 799 && (createRequest.getDatabaseTable() != null)) 800 cdbs 801 .addTable(new CacheDatabaseTable(createRequest.getDatabaseTable())); 802 } 803 return; 804 } 805 else if (request.isDrop()) 806 { 807 stats.addDrop(); 808 if (parsingGranularity != ParsingGranularities.NO_PARSING) 810 { 811 CacheDatabaseTable cdt = cdbs.getTable(request.getTableName()); 813 if (cdt != null) 814 { 815 cdt.invalidateAll(); 816 cdbs.removeTable(cdt); 817 return; 818 } 819 } 822 } 823 else 824 { 825 stats.addUnknown(); 826 } 827 if (logger.isDebugEnabled()) 828 logger.debug("Notifying write " + request.getSQL()); 829 830 processWriteNotify(request); 831 } 832 833 838 protected abstract void processWriteNotify(AbstractWriteRequest request); 839 840 843 public void flushCache() 844 { 845 synchronized (this) 847 { 848 if (flushingCache) 849 return; 850 flushingCache = true; 851 } 852 853 try 854 { 855 synchronized (queries) 856 { while (!queries.isEmpty()) 858 { 859 Iterator iter = queries.values().iterator(); 860 ((AbstractResultCacheEntry) iter.next()).invalidate(); 861 } 862 } 863 864 synchronized (pendingQueries) 865 { pendingQueries.clear(); 868 pendingQueries.notifyAll(); 869 } 870 } 871 finally 872 { 873 synchronized (this) 874 { 875 flushingCache = false; 876 } 877 if (logger.isDebugEnabled()) 878 logger.debug(Translate.get("resultcache.cache.flushed")); 879 } 880 } 881 882 887 public long getCacheSize() 888 { 889 return queries.size(); 891 } 892 893 899 private void removeOldest() 900 { 901 if (lruTail == null) 902 return; 903 AbstractResultCacheEntry oldce = lruTail; 905 lruTail = lruTail.getPrev(); 906 if (lruTail != null) 907 lruTail.setNext(null); 908 909 if (logger.isDebugEnabled()) 910 logger.debug(Translate.get("resultcache.removing.oldest.cache.entry", 911 oldce.getRequest().getSQL())); 912 913 921 queries.remove(oldce.getRequest().getSQL()); 922 923 if (oldce.isValid()) 924 { 925 oldce.setResult(null); 926 oldce.invalidate(); 927 } 928 929 stats.addRemove(); 930 } 931 932 937 public int getParsingGranularity() 938 { 939 return this.parsingGranularity; 940 } 941 942 947 public abstract String getName(); 948 949 953 959 public void commit(long transactionId) throws CacheException 960 { 961 } 963 964 970 public void rollback(long transactionId) throws CacheException 971 { 972 logger.info(Translate.get("resultcache.flushing.cache.cause.rollback", 973 transactionId)); 974 flushCache(); 975 } 976 977 980 981 984 public String [][] getCacheData() throws CacheException 985 { 986 try 987 { 988 synchronized (queries) 989 { 990 String [][] data = new String [queries.size()][]; 991 int count = 0; 992 for (Iterator iter = queries.values().iterator(); iter.hasNext(); count++) 993 { 994 AbstractResultCacheEntry qe = (AbstractResultCacheEntry) iter.next(); 995 if (qe != null) 996 { 997 data[count] = qe.toStringTable(); 998 } 999 } 1000 return data; 1001 } 1002 } 1003 catch (Exception e) 1004 { 1005 logger.error(Translate.get("resultcache.error.retrieving.cache.data", e)); 1006 throw new CacheException(e.getMessage()); 1007 } 1008 } 1009 1010 1013 public String [][] getCacheStatsData() throws CacheException 1014 { 1015 String [][] data = new String [1][]; 1016 String [] stat = stats.getCacheStatsData(); 1017 data[0] = new String [stat.length + 1]; 1018 for (int i = 0; i < stat.length; i++) 1019 data[0][i] = stat[i]; 1020 data[0][data[0].length - 1] = "" + queries.size(); 1021 return data; 1022 } 1023 1024 1027 public CacheStatistics getCacheStatistics() 1028 { 1029 return stats; 1030 } 1031 1032 1037 public ArrayList getEagerCache() 1038 { 1039 return eagerCache; 1040 } 1041 1042 1047 public ArrayList getRelaxedCache() 1048 { 1049 return relaxedCache; 1050 } 1051 1052 1057 protected String getXmlImpl() 1058 { 1059 StringBuffer info = new StringBuffer (); 1060 info.append("<" + DatabasesXmlTags.ELT_ResultCache + " " 1061 + DatabasesXmlTags.ATT_pendingTimeout + "=\"" + pendingQueryTimeout 1062 + "\" " + DatabasesXmlTags.ATT_maxNbOfEntries + "=\"" + maxEntries 1063 + "\" " + DatabasesXmlTags.ATT_granularity + "=\"" + getName() + "\">"); 1064 info.append("<" + DatabasesXmlTags.ELT_DefaultResultCacheRule + " " 1065 + DatabasesXmlTags.ATT_timestampResolution + "=\"" 1066 + defaultRule.getTimestampResolution() / 1000 + "\">"); 1067 info.append(defaultRule.getCacheBehavior().getXml()); 1068 info.append("</" + DatabasesXmlTags.ELT_DefaultResultCacheRule + ">"); 1069 for (Iterator iter = cachingRules.iterator(); iter.hasNext();) 1070 info.append(((ResultCacheRule) iter.next()).getXml()); 1071 info.append("</" + DatabasesXmlTags.ELT_ResultCache + ">"); 1072 return info.toString(); 1073 } 1074 1075} | Popular Tags |