| 1 24 25 package org.objectweb.cjdbc.controller.recoverylog; 26 27 import java.sql.Connection ; 28 import java.sql.DatabaseMetaData ; 29 import java.sql.PreparedStatement ; 30 import java.sql.ResultSet ; 31 import java.sql.SQLException ; 32 import java.sql.Statement ; 33 import java.util.ArrayList ; 34 35 import org.objectweb.cjdbc.common.exceptions.VirtualDatabaseException; 36 import org.objectweb.cjdbc.common.i18n.Translate; 37 import org.objectweb.cjdbc.common.log.Trace; 38 import org.objectweb.cjdbc.common.shared.BackendState; 39 import org.objectweb.cjdbc.common.shared.DumpInfo; 40 import org.objectweb.cjdbc.common.sql.AbstractRequest; 41 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest; 42 import org.objectweb.cjdbc.common.sql.AlterRequest; 43 import org.objectweb.cjdbc.common.sql.CreateRequest; 44 import org.objectweb.cjdbc.common.sql.DeleteRequest; 45 import org.objectweb.cjdbc.common.sql.DropRequest; 46 import org.objectweb.cjdbc.common.sql.InsertRequest; 47 import org.objectweb.cjdbc.common.sql.StoredProcedure; 48 import org.objectweb.cjdbc.common.sql.UpdateRequest; 49 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags; 50 import org.objectweb.cjdbc.common.xml.XmlComponent; 51 import org.objectweb.cjdbc.controller.connection.DriverManager; 52 import org.objectweb.cjdbc.controller.loadbalancer.tasks.BeginTask; 53 import org.objectweb.cjdbc.controller.loadbalancer.tasks.CommitTask; 54 import org.objectweb.cjdbc.controller.loadbalancer.tasks.ReadStoredProcedureTask; 55 import org.objectweb.cjdbc.controller.loadbalancer.tasks.ReleaseSavepointTask; 56 import org.objectweb.cjdbc.controller.loadbalancer.tasks.RollbackTask; 57 import org.objectweb.cjdbc.controller.loadbalancer.tasks.RollbackToSavepointTask; 58 import org.objectweb.cjdbc.controller.loadbalancer.tasks.SavepointTask; 59 import org.objectweb.cjdbc.controller.loadbalancer.tasks.WriteRequestTask; 60 import org.objectweb.cjdbc.controller.loadbalancer.tasks.WriteStoredProcedureTask; 61 import org.objectweb.cjdbc.controller.recoverylog.events.LogEntry; 62 import org.objectweb.cjdbc.controller.recoverylog.events.LogRequestEvent; 63 import org.objectweb.cjdbc.controller.recoverylog.events.LogRollbackEvent; 64 import org.objectweb.cjdbc.controller.recoverylog.events.ResetLogEvent; 65 import org.objectweb.cjdbc.controller.recoverylog.events.StoreDumpCheckpointEvent; 66 import org.objectweb.cjdbc.controller.recoverylog.events.UnlogRequestEvent; 67 import org.objectweb.cjdbc.controller.requestmanager.TransactionMarkerMetaData; 68 69 79 public class RecoveryLog implements XmlComponent 80 { 81 static Trace logger = Trace 82 .getLogger("org.objectweb.cjdbc.controller.recoverylog"); 83 84 85 private long recoveringNb = 0; 86 87 88 protected int recoveryBatchSize; 89 90 91 private String driverClassName; 92 93 94 private String driverName; 95 96 97 private String url; 98 99 100 private String login; 101 102 103 private String password; 104 105 106 private Connection internalConnectionManagedByGetDatabaseConnection = null; 107 108 114 private String logTableCreateTable; 115 private String logTableName; 116 private String logTableCreateStatement; 117 private String logTableIdType; 118 private String logTableVloginType; 119 private String logTableSqlColumnName; 120 private String logTableSqlType; 121 private String logTableTransactionIdType; 122 private String logTableExtraStatement; 123 124 130 private String checkpointTableCreateTable; 131 private String checkpointTableName; 132 private String checkpointTableCreateStatement; 133 private String checkpointTableNameType; 134 private String checkpointTableRequestIdType; 135 private String checkpointTableExtraStatement; 136 137 143 private String backendTableCreateStatement; 144 private String backendTableName; 145 private String backendTableCreateTable; 146 private String backendTableDatabaseName; 147 private String backendTableExtraStatement; 148 private String backendTableCheckpointName; 149 private String backendTableBackendState; 150 private String backendTableBackendName; 151 152 158 private String dumpTableCreateStatement; 159 private String dumpTableCreateTable; 160 private String dumpTableName; 161 private String dumpTableDumpNameColumnType; 162 private String dumpTableDumpDateColumnType; 163 private String dumpTableDumpPathColumnType; 164 private String dumpTableDumpFormatColumnType; 165 private String dumpTableCheckpointNameColumnType; 166 private String dumpTableBackendNameColumnType; 167 private String dumpTableTablesColumnName; 168 private String dumpTableTablesColumnType; 169 private String dumpTableExtraStatementDefinition; 170 171 172 private long logTableId = 0; 173 174 175 private int timeout; 176 177 private LoggerThread loggerThread; 178 179 191 public RecoveryLog(String driverName, String driverClassName, String url, 192 String login, String password, int requestTimeout, int recoveryBatchSize) 193 { 194 this.driverName = driverName; 195 this.driverClassName = driverClassName; 196 this.url = url; 197 this.login = login; 198 this.password = password; 199 this.timeout = requestTimeout; 200 if (recoveryBatchSize < 1) 201 { 202 logger 203 .warn("RecoveryBatchSize was set to a value lesser than 1, resetting value to 1."); 204 recoveryBatchSize = 1; 205 } 206 this.recoveryBatchSize = recoveryBatchSize; 207 208 try 210 { 211 getDatabaseConnection(); 212 } 213 catch (SQLException e) 214 { 215 throw new RuntimeException ("Unable to connect to the database: " + e); 216 } 217 218 } 221 222 226 233 protected Connection getDatabaseConnection() throws SQLException  234 { 235 try 236 { 237 if (internalConnectionManagedByGetDatabaseConnection == null) 238 { 239 if (logger.isDebugEnabled()) 240 logger.debug(Translate.get("recovery.jdbc.connect", new String []{url, 241 login})); 242 internalConnectionManagedByGetDatabaseConnection = DriverManager 243 .getConnection(url, login, password, driverName, driverClassName); 244 } 245 return internalConnectionManagedByGetDatabaseConnection; 246 } 247 catch (RuntimeException e) 248 { 249 String msg = Translate.get("recovery.jdbc.connect.failed", e); 250 if (logger.isDebugEnabled()) 251 logger.debug(msg, e); 252 throw new SQLException (msg); 253 } 254 catch (SQLException e) 255 { 256 invalidateInternalConnection(); 257 String msg = Translate.get("recovery.jdbc.connect.failed", e); 258 if (logger.isDebugEnabled()) 259 logger.debug(msg, e); 260 throw new SQLException (msg); 261 } 262 } 263 264 267 private synchronized long incrementLogTableId() 268 { 269 logTableId++; 270 return logTableId; 271 } 272 273 277 private void intializeDatabase() throws SQLException  278 { 279 boolean createLogTable = true; 280 boolean createCheckpointTable = true; 281 boolean createBackendTable = true; 282 boolean createDumpTable = true; 283 Connection connection; 284 try 286 { 287 connection = getDatabaseConnection(); 288 connection.setAutoCommit(false); 289 DatabaseMetaData metaData = connection.getMetaData(); 291 292 String [] types = {"TABLE", "VIEW"}; 299 ResultSet rs = metaData.getTables(null, null, "%", types); 300 301 String tableName; 303 while (rs.next()) 304 { 305 tableName = rs.getString(3); 307 if (logger.isDebugEnabled()) 308 logger.debug(Translate.get("recovery.jdbc.table.found", tableName)); 309 if (tableName.equalsIgnoreCase(logTableName)) 310 { 311 if (tableName.compareTo(logTableName) != 0) 312 logger.warn(Translate.get("recovery.jdbc.logtable.case.mismatch", 313 new String []{logTableName, tableName})); 314 createLogTable = false; 315 PreparedStatement p = null; 317 try 318 { 319 ResultSet result = null; 320 p = connection.prepareStatement("SELECT MAX(id) AS max_id FROM " 321 + logTableName); 322 result = p.executeQuery(); 323 if (result.next()) 324 logTableId = result.getLong("max_id"); 325 else 326 logTableId = 0; 327 p.close(); 328 } 329 catch (SQLException e) 330 { 331 try 332 { 333 if (p != null) 334 p.close(); 335 } 336 catch (Exception ignore) 337 { 338 } 339 throw new RuntimeException (Translate.get( 340 "recovery.jdbc.logtable.getvalue.failed", e)); 341 } 342 343 } 344 if (tableName.equalsIgnoreCase(checkpointTableName)) 345 { 346 if (tableName.compareTo(checkpointTableName) != 0) 347 logger.warn(Translate.get( 348 "recovery.jdbc.checkpointtable.case.mismatch", new String []{ 349 checkpointTableName, tableName})); 350 createCheckpointTable = false; 351 } 352 else if (tableName.equalsIgnoreCase(backendTableName)) 353 { 354 if (tableName.compareTo(backendTableName) != 0) 355 logger.warn(Translate.get( 356 "recovery.jdbc.backendtable.case.mismatch", new String []{ 357 backendTableName, tableName})); 358 createBackendTable = false; 359 } 360 else if (tableName.equalsIgnoreCase(dumpTableName)) 361 { 362 if (tableName.compareTo(dumpTableName) != 0) 363 logger.warn(Translate.get("recovery.jdbc.dumptable.case.mismatch", 364 new String []{backendTableName, tableName})); 365 createDumpTable = false; 366 } 367 } 368 try 369 { 370 connection.commit(); 371 connection.setAutoCommit(true); 372 } 373 catch (Exception ignore) 374 { 375 } 377 } 378 catch (SQLException e) 379 { 380 logger.error(Translate.get("recovery.jdbc.table.no.description"), e); 381 throw e; 382 } 383 384 Statement stmt = null; 386 if (createLogTable) 387 { 388 if (logger.isInfoEnabled()) 389 logger.info(Translate 390 .get("recovery.jdbc.logtable.create", logTableName)); 391 try 392 { 393 stmt = connection.createStatement(); 394 stmt.executeUpdate(logTableCreateStatement); 395 stmt.close(); 396 } 397 catch (SQLException e) 398 { 399 throw new SQLException (Translate.get( 400 "recovery.jdbc.logtable.create.failed", new String []{logTableName, 401 e.getMessage()})); 402 } 403 } 404 if (createCheckpointTable) 405 { 406 if (logger.isInfoEnabled()) 407 logger.info(Translate.get("recovery.jdbc.checkpointtable.create", 408 checkpointTableName)); 409 try 410 { 411 stmt = connection.createStatement(); 412 stmt.executeUpdate(checkpointTableCreateStatement); 413 stmt.close(); 414 } 415 catch (SQLException e) 416 { 417 throw new SQLException (Translate.get( 418 "recovery.jdbc.checkpointtable.create.failed", new String []{ 419 logTableName, e.getMessage()})); 420 } 421 422 String checkpointName = "Initial_empty_recovery_log"; 424 PreparedStatement pstmt = null; 425 try 426 { 427 if (logger.isDebugEnabled()) 428 logger.debug("Storing checkpoint " + checkpointName 429 + " at request id " + logTableId); 430 pstmt = connection.prepareStatement("INSERT INTO " 431 + checkpointTableName + " VALUES(?,?)"); 432 pstmt.setString(1, checkpointName); 433 pstmt.setLong(2, logTableId); 434 pstmt.executeUpdate(); 435 pstmt.close(); 436 } 437 catch (SQLException e) 438 { 439 try 440 { 441 if (pstmt != null) 442 pstmt.close(); 443 } 444 catch (Exception ignore) 445 { 446 } 447 throw new SQLException (Translate.get( 448 "recovery.jdbc.checkpoint.store.failed", new String []{ 449 checkpointName, e.getMessage()})); 450 } 451 } 452 if (createBackendTable) 453 { 454 if (logger.isInfoEnabled()) 455 logger.info(Translate.get("recovery.jdbc.backendtable.create", 456 backendTableName)); 457 try 458 { 459 stmt = connection.createStatement(); 460 stmt.executeUpdate(backendTableCreateStatement); 461 stmt.close(); 462 } 463 catch (SQLException e) 464 { 465 throw new SQLException (Translate.get( 466 "recovery.jdbc.backendtable.create.failed", new String []{ 467 logTableName, e.getMessage()})); 468 } 469 } 470 if (createDumpTable) 471 { 472 if (logger.isInfoEnabled()) 473 logger.info(Translate.get("recovery.jdbc.dumptable.create", 474 dumpTableName)); 475 try 476 { 477 stmt = connection.createStatement(); 478 stmt.executeUpdate(dumpTableCreateStatement); 479 stmt.close(); 480 } 481 catch (SQLException e) 482 { 483 throw new SQLException (Translate.get( 484 "recovery.jdbc.dumptable.create.failed", new String []{ 485 dumpTableName, e.getMessage()})); 486 } 487 } 488 } 489 490 496 protected void invalidateInternalConnection() 497 { 498 try 499 { 500 internalConnectionManagedByGetDatabaseConnection.close(); 501 } 502 catch (Exception ignore) 503 { 504 } 505 internalConnectionManagedByGetDatabaseConnection = null; 506 } 507 508 514 523 public long logAbort(TransactionMarkerMetaData tm) 524 { 525 return logRollback(tm); 527 } 528 529 535 public long logBegin(TransactionMarkerMetaData tm) 536 { 537 long id = incrementLogTableId(); 539 loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), 540 "begin", tm.getTransactionId(), false))); 541 return id; 542 } 543 544 550 public long logCommit(TransactionMarkerMetaData tm) 551 { 552 long id = incrementLogTableId(); 553 loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), 554 "commit", tm.getTransactionId(), false))); 555 return id; 556 } 557 558 563 public void logLogEntry(LogEntry logEntry) 564 { 565 loggerThread.log(new LogRequestEvent(logEntry)); 566 } 567 568 575 public long logReleaseSavepoint(TransactionMarkerMetaData tm, String name) 576 { 577 long id = incrementLogTableId(); 578 loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), 579 "release " + name, tm.getTransactionId(), false))); 580 return id; 581 } 582 583 589 public long logRequest(AbstractWriteRequest request) 590 { 591 long id = incrementLogTableId(); 592 loggerThread.log(new LogRequestEvent(new LogEntry(id, request.getLogin(), 593 request.getSQL(), request.getTransactionId(), request 594 .getEscapeProcessing()))); 595 return id; 596 } 597 598 605 public long logRequest(StoredProcedure proc, boolean isRead) 606 { 607 long id = incrementLogTableId(); 608 if (isRead) 609 loggerThread.log(new LogRequestEvent(new LogEntry(incrementLogTableId(), 610 proc.getLogin(), proc.getSQL(), proc.getTransactionId(), proc 611 .getEscapeProcessing()))); 612 else 613 { StringBuffer writeCall = new StringBuffer (proc.getSQL()); 615 writeCall.setCharAt(0, '}'); 616 loggerThread.log(new LogRequestEvent(new LogEntry(incrementLogTableId(), 617 proc.getLogin(), writeCall.toString(), proc.getTransactionId(), proc 618 .getEscapeProcessing()))); 619 } 620 return id; 621 } 622 623 629 public long logRollback(TransactionMarkerMetaData tm) 630 { 631 long id = incrementLogTableId(); 632 loggerThread.log(new LogRollbackEvent(new LogEntry(id, tm.getLogin(), 634 "rollback", tm.getTransactionId(), false))); 635 return id; 636 } 637 638 645 public long logRollback(TransactionMarkerMetaData tm, String savepointName) 646 { 647 long id = incrementLogTableId(); 648 loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), 649 "rollback " + savepointName, tm.getTransactionId(), false))); 650 return id; 651 } 652 653 660 public long logSetSavepoint(TransactionMarkerMetaData tm, String name) 661 { 662 long id = incrementLogTableId(); 663 loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), 664 "savepoint " + name, tm.getTransactionId(), false))); 665 return id; 666 } 667 668 678 public void resetLogTableIdAndDeleteRecoveryLog(String checkpointName, 679 long newCheckpointId) throws SQLException  680 { 681 long oldId = getCheckpointRequestId(checkpointName); 682 synchronized (this) 683 { 684 loggerThread 687 .log(new ResetLogEvent(oldId, newCheckpointId, checkpointName)); 688 logTableId = newCheckpointId + logTableId - oldId; 689 } 690 } 691 692 700 public void unlogCommit(TransactionMarkerMetaData tm) 701 { 702 loggerThread.log(new UnlogRequestEvent(new LogEntry(tm.getTimeout(), tm 704 .getLogin(), "commit", tm.getTransactionId(), false))); 705 } 706 707 717 public void unlogRequest(AbstractRequest request) 718 { 719 loggerThread.log(new UnlogRequestEvent(new LogEntry(request.getId(), 720 request.getLogin(), request.getSQL(), request.getTransactionId(), 721 request.getEscapeProcessing()))); 722 } 723 724 733 public void unlogRequest(StoredProcedure proc) 734 { 735 StringBuffer writeCall = new StringBuffer (proc.getSQL()); 738 writeCall.setCharAt(0, '}'); 739 loggerThread.log(new UnlogRequestEvent(new LogEntry(proc.getId(), proc 740 .getLogin(), writeCall.toString(), proc.getTransactionId(), proc 741 .getEscapeProcessing()))); 742 } 743 744 752 public void unlogRollback(TransactionMarkerMetaData tm) 753 { 754 loggerThread.log(new UnlogRequestEvent(new LogEntry(tm.getTimeout(), tm 756 .getLogin(), "rollback", tm.getTransactionId(), false))); 757 } 758 759 762 public void shutdown() 763 { 764 if (loggerThread != null) 765 loggerThread.shutdown(); 766 } 767 768 772 775 public synchronized void beginRecovery() 776 { 777 recoveringNb++; 778 } 779 780 786 public void cleanRecoveryLog() throws SQLException  787 { 788 PreparedStatement stmt = null; 789 790 ResultSet rs = null; 792 try 793 { 794 stmt = getDatabaseConnection().prepareStatement( 796 "SELECT transaction_id FROM " + logTableName + " WHERE " 797 + logTableSqlColumnName + " LIKE ?"); 798 stmt.setString(1, "rollback"); 799 rs = stmt.executeQuery(); 800 } 801 catch (SQLException e) 802 { 803 invalidateInternalConnection(); 804 try 805 { 806 if (stmt != null) 807 stmt.close(); 808 } 809 catch (Exception ignore) 810 { 811 } 812 throw new SQLException ("Unable get rollback statements : " + e); 813 } 814 PreparedStatement pstmt = null; 815 long transactionId = -1; 816 try 817 { 818 while (rs.next()) 820 { 821 transactionId = rs.getLong("transaction_id"); 822 pstmt = getDatabaseConnection().prepareStatement( 823 "DELETE FROM " + logTableName + " WHERE transaction_id=?"); 824 pstmt.setLong(1, transactionId); 825 pstmt.executeUpdate(); 826 pstmt.close(); 827 } 828 rs.close(); 829 stmt.close(); 830 } 831 catch (SQLException e) 832 { 833 invalidateInternalConnection(); 834 throw new SQLException (Translate.get( 835 "recovery.jdbc.transaction.remove.failed", new String []{ 836 String.valueOf(transactionId), e.getMessage()})); 837 } 838 finally 839 { 840 try 841 { 842 if (stmt != null) 843 stmt.close(); 844 } 845 catch (Exception ignore) 846 { 847 } 848 try 849 { 850 if (pstmt != null) 851 pstmt.close(); 852 } 853 catch (Exception ignore) 854 { 855 } 856 857 } 858 |