1 package org.apache.torque.oid; 2 3 21 22 import java.math.BigDecimal ; 23 import java.sql.Connection ; 24 import java.sql.ResultSet ; 25 import java.sql.Statement ; 26 import java.util.ArrayList ; 27 import java.util.Hashtable ; 28 import java.util.Iterator ; 29 import java.util.List ; 30 31 import org.apache.commons.configuration.Configuration; 32 import org.apache.commons.logging.Log; 33 import org.apache.commons.logging.LogFactory; 34 import org.apache.torque.Database; 35 import org.apache.torque.Torque; 36 import org.apache.torque.TorqueException; 37 import org.apache.torque.map.TableMap; 38 import org.apache.torque.util.Transaction; 39 40 45 80 public class IDBroker implements Runnable , IdGenerator 81 { 82 83 public static final String ID_TABLE = "ID_TABLE"; 84 85 86 public static final String COL_TABLE_NAME = "TABLE_NAME"; 87 88 89 public static final String TABLE_NAME = ID_TABLE + "." + COL_TABLE_NAME; 90 91 92 public static final String COL_TABLE_ID = "ID_TABLE_ID"; 93 94 95 public static final String TABLE_ID = ID_TABLE + "." + COL_TABLE_ID; 96 97 98 public static final String COL_NEXT_ID = "NEXT_ID"; 99 100 101 public static final String NEXT_ID = ID_TABLE + "." + COL_NEXT_ID; 102 103 104 public static final String COL_QUANTITY = "QUANTITY"; 105 106 107 public static final String QUANTITY = ID_TABLE + "." + COL_QUANTITY; 108 109 110 private String databaseName; 111 112 116 private static final int DEFAULT_SIZE = 40; 117 118 124 private Hashtable ids = new Hashtable (DEFAULT_SIZE); 125 126 132 private Hashtable quantityStore = new Hashtable (DEFAULT_SIZE); 133 134 140 private Hashtable lastQueryTime = new Hashtable (DEFAULT_SIZE); 141 142 145 private static final int SLEEP_PERIOD = 60000; 146 147 150 private static final float SAFETY_MARGIN = 1.2f; 151 152 155 private Thread houseKeeperThread = null; 156 157 160 private boolean transactionsSupported = false; 161 162 165 private static final BigDecimal ONE = new BigDecimal ("1"); 166 167 168 private Configuration configuration; 169 170 171 private static final String DB_IDBROKER_CLEVERQUANTITY = 172 "idbroker.clever.quantity"; 173 174 175 private static final String DB_IDBROKER_PREFETCH = 176 "idbroker.prefetch"; 177 178 179 private static final String DB_IDBROKER_USENEWCONNECTION = 180 "idbroker.usenewconnection"; 181 182 183 private Log log = LogFactory.getLog(IDBroker.class); 184 185 189 public IDBroker(Database database) 190 { 191 this(database.getName()); 192 } 193 194 201 public IDBroker(TableMap tMap) 202 { 203 this(tMap.getDatabaseMap().getName()); 204 } 205 206 213 private IDBroker(String databaseName) 214 { 215 this.databaseName = databaseName; 216 configuration = Torque.getConfiguration(); 217 218 if (configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) 220 { 221 houseKeeperThread = new Thread (this); 222 houseKeeperThread.setDaemon(true); 227 houseKeeperThread.setName("Torque - ID Broker thread"); 228 houseKeeperThread.start(); 229 } 230 231 Connection dbCon = null; 235 try 236 { 237 dbCon = Torque.getConnection(databaseName); 238 } 239 catch (Throwable t) 240 { 241 log.error("Could not open a connection to the database " 242 + databaseName, 243 t); 244 transactionsSupported = false; 245 } 246 try 247 { 248 transactionsSupported = dbCon.getMetaData().supportsTransactions(); 249 } 250 catch (Exception e) 251 { 252 log.warn("Could not read from connection Metadata" 253 + " whether transactions are supported for the database " 254 + databaseName, 255 e); 256 transactionsSupported = false; 257 } 258 finally 259 { 260 try 261 { 262 dbCon.close(); 264 } 265 catch (Exception e) 266 { 267 log.warn("Could not close the connection which was used " 268 + "for testing whether transactions are supported", 269 e); 270 } 271 } 272 if (!transactionsSupported) 273 { 274 log.warn("IDBroker is being used with db '" + databaseName 275 + "', which does not support transactions. IDBroker " 276 + "attempts to use transactions to limit the possibility " 277 + "of duplicate key generation. Without transactions, " 278 + "duplicate key generation is possible if multiple JVMs " 279 + "are used or other means are used to write to the " 280 + "database."); 281 } 282 } 283 284 289 public void setConfiguration(Configuration configuration) 290 { 291 this.configuration = configuration; 292 } 293 294 306 public int getIdAsInt(Connection connection, Object tableName) 307 throws Exception 308 { 309 return getIdAsBigDecimal(connection, tableName).intValue(); 310 } 311 312 313 325 public long getIdAsLong(Connection connection, Object tableName) 326 throws Exception 327 { 328 return getIdAsBigDecimal(connection, tableName).longValue(); 329 } 330 331 343 public BigDecimal getIdAsBigDecimal(Connection connection, 344 Object tableName) 345 throws Exception 346 { 347 BigDecimal [] id = getNextIds((String ) tableName, 1, connection); 348 return id[0]; 349 } 350 351 363 public String getIdAsString(Connection connection, Object tableName) 364 throws Exception 365 { 366 return getIdAsBigDecimal(connection, tableName).toString(); 367 } 368 369 370 374 public boolean isPriorToInsert() 375 { 376 return true; 377 } 378 379 384 public boolean isPostInsert() 385 { 386 return false; 387 } 388 389 395 public boolean isConnectionRequired() 396 { 397 return false; 398 } 399 400 408 public synchronized BigDecimal [] getNextIds(String tableName, 409 int numOfIdsToReturn) 410 throws Exception 411 { 412 return getNextIds(tableName, numOfIdsToReturn, null); 413 } 414 415 428 public synchronized BigDecimal [] getNextIds(String tableName, 429 int numOfIdsToReturn, 430 Connection connection) 431 throws Exception 432 { 433 if (tableName == null) 434 { 435 throw new Exception ("getNextIds(): tableName == null"); 436 } 437 438 447 List availableIds = (List ) ids.get(tableName); 448 449 if (availableIds == null || availableIds.size() < numOfIdsToReturn) 450 { 451 if (availableIds == null) 452 { 453 log.debug("Forced id retrieval - no available list"); 454 } 455 else 456 { 457 log.debug("Forced id retrieval - " + availableIds.size()); 458 } 459 storeIDs(tableName, true, connection); 460 availableIds = (List ) ids.get(tableName); 461 } 462 463 int size = availableIds.size() < numOfIdsToReturn 464 ? availableIds.size() : numOfIdsToReturn; 465 466 BigDecimal [] results = new BigDecimal [size]; 467 468 for (int i = size - 1; i >= 0; i--) 474 { 475 results[i] = (BigDecimal ) availableIds.get(i); 476 availableIds.remove(i); 477 } 478 480 return results; 481 } 482 483 490 public boolean exists(String tableName) 491 throws Exception 492 { 493 String query = new StringBuffer (100) 494 .append("select ") 495 .append(TABLE_NAME) 496 .append(" where ") 497 .append(TABLE_NAME).append("='").append(tableName).append('\'') 498 .toString(); 499 500 boolean exists = false; 501 Connection dbCon = null; 502 try 503 { 504 dbCon = Torque.getConnection(databaseName); 505 Statement statement = dbCon.createStatement(); 506 ResultSet rs = statement.executeQuery(query); 507 exists = rs.next(); 508 statement.close(); 509 } 510 finally 511 { 512 try 514 { 515 dbCon.close(); 516 } 517 catch (Exception e) 518 { 519 log.error("Release of connection failed.", e); 520 } 521 } 522 return exists; 523 } 524 525 530 public void run() 531 { 532 log.debug("IDBroker thread was started."); 533 534 Thread thisThread = Thread.currentThread(); 535 while (houseKeeperThread == thisThread) 536 { 537 try 538 { 539 Thread.sleep(SLEEP_PERIOD); 540 } 541 catch (InterruptedException exc) 542 { 543 } 545 546 Iterator it = ids.keySet().iterator(); 548 while (it.hasNext()) 549 { 550 String tableName = (String ) it.next(); 551 if (log.isDebugEnabled()) 552 { 553 log.debug("IDBroker thread checking for more keys " 554 + "on table: " + tableName); 555 } 556 List availableIds = (List ) ids.get(tableName); 557 int quantity = getQuantity(tableName, null).intValue(); 558 if (quantity > availableIds.size()) 559 { 560 try 561 { 562 storeIDs(tableName, false, null); 566 if (log.isDebugEnabled()) 567 { 568 log.debug("Retrieved more ids for table: " + tableName); 569 } 570 } 571 catch (Exception exc) 572 { 573 log.error("There was a problem getting new IDs " 574 + "for table: " + tableName, exc); 575 } 576 } 577 } 578 } 579 log.debug("IDBroker thread finished."); 580 } 581 582 589 public void stop() 590 { 591 houseKeeperThread = null; 592 } 593 594 603 private void checkTiming(String tableName) 604 { 605 if (!configuration.getBoolean(DB_IDBROKER_CLEVERQUANTITY, true) 608 || !configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) 609 { 610 return; 611 } 612 613 java.util.Date lastTime = (java.util.Date ) lastQueryTime.get(tableName); 615 java.util.Date now = new java.util.Date (); 616 617 if (lastTime != null) 618 { 619 long thenLong = lastTime.getTime(); 620 long nowLong = now.getTime(); 621 int timeLapse = (int) (nowLong - thenLong); 622 if (timeLapse < SLEEP_PERIOD && timeLapse > 0) 623 { 624 if (log.isDebugEnabled()) 625 { 626 log.debug("Unscheduled retrieval of more ids for table: " 627 + tableName); 628 } 629 float rate = getQuantity(tableName, null).floatValue() 632 / (float) timeLapse; 633 quantityStore.put(tableName, new BigDecimal ( 634 Math.ceil(SLEEP_PERIOD * rate * SAFETY_MARGIN))); 635 } 636 } 637 lastQueryTime.put(tableName, now); 638 } 639 640 650 private synchronized void storeIDs(String tableName, 651 boolean adjustQuantity, 652 Connection connection) 653 throws Exception 654 { 655 BigDecimal nextId = null; 656 BigDecimal quantity = null; 657 658 if (adjustQuantity) 664 { 665 checkTiming(tableName); 666 } 667 668 boolean useNewConnection = (connection == null) || (configuration 669 .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)); 670 try 671 { 672 if (useNewConnection) 673 { 674 connection = Transaction.beginOptional(databaseName, 675 transactionsSupported); 676 } 677 678 quantity = getQuantity(tableName, connection); 684 updateQuantity(connection, tableName, quantity); 685 686 BigDecimal [] results = selectRow(connection, tableName); 688 nextId = results[0]; 690 BigDecimal newNextId = nextId.add(quantity); 693 updateNextId(connection, tableName, newNextId.toString()); 694 695 if (useNewConnection) 696 { 697 Transaction.commit(connection); 698 } 699 } 700 catch (Exception e) 701 { 702 if (useNewConnection) 703 { 704 Transaction.rollback(connection); 705 } 706 throw e; 707 } 708 709 List availableIds = (List ) ids.get(tableName); 710 if (availableIds == null) 711 { 712 availableIds = new ArrayList (); 713 ids.put(tableName, availableIds); 714 } 715 716 int numId = quantity.intValue(); 718 for (int i = 0; i < numId; i++) 719 { 720 availableIds.add(nextId); 721 nextId = nextId.add(ONE); 722 } 723 } 725 726 740 private BigDecimal getQuantity(String tableName, Connection connection) 741 { 742 BigDecimal quantity = null; 743 744 if (!configuration.getBoolean(DB_IDBROKER_PREFETCH, true)) 746 { 747 quantity = new BigDecimal (1); 748 } 749 else if (quantityStore.containsKey(tableName)) 751 { 752 quantity = (BigDecimal ) quantityStore.get(tableName); 753 } 754 else 755 { 756 Connection dbCon = null; 757 try 758 { 759 if (connection == null || configuration 760 .getBoolean(DB_IDBROKER_USENEWCONNECTION, true)) 761 { 762 dbCon = Torque.getConnection(databaseName); 764 } 765 766 BigDecimal [] results = selectRow(dbCon, tableName); 768 769 quantity = results[1]; 771 quantityStore.put(tableName, quantity); 772 } 773 catch (Exception e) 774 { 775 quantity = new BigDecimal (10); 776 } 777 finally 778 { 779 try 781 { 782 dbCon.close(); 783 } 784 catch (Exception e) 785 { 786 log.error("Release of connection failed.", e); 787 } 788 } 789 } 790 return quantity; 791 } 792 793 802 private BigDecimal [] selectRow(Connection con, String tableName) 803 throws Exception 804 { 805 StringBuffer stmt = new StringBuffer (); 806 stmt.append("SELECT ") 807 .append(COL_NEXT_ID) 808 .append(", ") 809 .append(COL_QUANTITY) 810 .append(" FROM ") 811 .append(ID_TABLE) 812 .append(" WHERE ") 813 .append(COL_TABLE_NAME) 814 .append(" = '") 815 .append(tableName) 816 .append('\''); 817 818 Statement statement = null; 819 820 BigDecimal [] results = new BigDecimal [2]; 821 try 822 { 823 statement = con.createStatement(); 824 ResultSet rs = statement.executeQuery(stmt.toString()); 825 826 if (rs.next()) 827 { 828 results[0] = new BigDecimal (rs.getString(1)); results[1] = new BigDecimal (rs.getString(2)); } 834 else 835 { 836 throw new TorqueException("The table " + tableName 837 + " does not have a proper entry in the " + ID_TABLE); 838 } 839 } 840 finally 841 { 842 if (statement != null) 843 { 844 statement.close(); 845 } 846 } 847 848 return results; 849 } 850 851 860 private void updateNextId(Connection con, String tableName, String id) 861 throws Exception 862 { 863 864 865 StringBuffer stmt = new StringBuffer (id.length() 866 + tableName.length() + 50); 867 stmt.append("UPDATE " + ID_TABLE) 868 .append(" SET ") 869 .append(COL_NEXT_ID) 870 .append(" = ") 871 .append(id) 872 .append(" WHERE ") 873 .append(COL_TABLE_NAME) 874 .append(" = '") 875 .append(tableName) 876 .append('\''); 877 878 Statement statement = null; 879 880 if (log.isDebugEnabled()) 881 { 882 log.debug("updateNextId: " + stmt.toString()); 883 } 884 885 try 886 { 887 statement = con.createStatement(); 888 statement.executeUpdate(stmt.toString()); 889 } 890 finally 891 { 892 if (statement != null) 893 { 894 statement.close(); 895 } 896 } 897 } 898 899 908 private void updateQuantity(Connection con, String tableName, 909 BigDecimal quantity) 910 throws Exception 911 { 912 StringBuffer stmt = new StringBuffer (quantity.toString().length() 913 + tableName.length() + 50); 914 stmt.append("UPDATE ") 915 .append(ID_TABLE) 916 .append(" SET ") 917 .append(COL_QUANTITY) 918 .append(" = ") 919 .append(quantity) 920 .append(" WHERE ") 921 .append(COL_TABLE_NAME) 922 .append(" = '") 923 .append(tableName) 924 .append('\''); 925 926 Statement statement = null; 927 928 if (log.isDebugEnabled()) 929 { 930 log.debug("updateQuantity: " + stmt.toString()); 931 } 932 933 try 934 { 935 statement = con.createStatement(); 936 statement.executeUpdate(stmt.toString()); 937 } 938 finally 939 { 940 if (statement != null) 941 { 942 statement.close(); 943 } 944 } 945 } 946 } 947 | Popular Tags |