KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > recoverylog > RecoveryLog


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2002-2004 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
6  * Copyright (C) 2005-2006 Continuent, Inc.
7  * Contact: sequoia@continuent.org
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  *
21  * Initial developer(s): Emmanuel Cecchet.
22  * Contributor(s): Julie Marguerite, Greg Ward, Jess Sightler, Jean-Bernard van Zuylen, Charles Cordingley.
23  */

24
25 package org.continuent.sequoia.controller.recoverylog;
26
27 import java.sql.Connection JavaDoc;
28 import java.sql.DatabaseMetaData JavaDoc;
29 import java.sql.PreparedStatement JavaDoc;
30 import java.sql.ResultSet JavaDoc;
31 import java.sql.SQLException JavaDoc;
32 import java.sql.Statement JavaDoc;
33 import java.sql.Timestamp JavaDoc;
34 import java.util.ArrayList JavaDoc;
35 import java.util.Iterator JavaDoc;
36 import java.util.List JavaDoc;
37 import java.util.Map JavaDoc;
38 import java.util.TreeMap JavaDoc;
39
40 import org.continuent.sequoia.common.exceptions.VirtualDatabaseException;
41 import org.continuent.sequoia.common.i18n.Translate;
42 import org.continuent.sequoia.common.jmx.management.BackendState;
43 import org.continuent.sequoia.common.jmx.management.DumpInfo;
44 import org.continuent.sequoia.common.log.Trace;
45 import org.continuent.sequoia.common.xml.DatabasesXmlTags;
46 import org.continuent.sequoia.common.xml.XmlComponent;
47 import org.continuent.sequoia.controller.connection.DriverManager;
48 import org.continuent.sequoia.controller.core.ControllerConstants;
49 import org.continuent.sequoia.controller.loadbalancer.tasks.BeginTask;
50 import org.continuent.sequoia.controller.loadbalancer.tasks.CallableStatementExecuteTask;
51 import org.continuent.sequoia.controller.loadbalancer.tasks.ClosePersistentConnectionTask;
52 import org.continuent.sequoia.controller.loadbalancer.tasks.CommitTask;
53 import org.continuent.sequoia.controller.loadbalancer.tasks.OpenPersistentConnectionTask;
54 import org.continuent.sequoia.controller.loadbalancer.tasks.ReleaseSavepointTask;
55 import org.continuent.sequoia.controller.loadbalancer.tasks.RollbackTask;
56 import org.continuent.sequoia.controller.loadbalancer.tasks.RollbackToSavepointTask;
57 import org.continuent.sequoia.controller.loadbalancer.tasks.SavepointTask;
58 import org.continuent.sequoia.controller.loadbalancer.tasks.StatementExecuteQueryTask;
59 import org.continuent.sequoia.controller.loadbalancer.tasks.StatementExecuteTask;
60 import org.continuent.sequoia.controller.loadbalancer.tasks.StatementExecuteUpdateTask;
61 import org.continuent.sequoia.controller.recoverylog.events.DeleteLogEntriesAndCheckpointBetweenEvent;
62 import org.continuent.sequoia.controller.recoverylog.events.FindCommitEvent;
63 import org.continuent.sequoia.controller.recoverylog.events.FindRollbackEvent;
64 import org.continuent.sequoia.controller.recoverylog.events.GetCheckpointLogEntryEvent;
65 import org.continuent.sequoia.controller.recoverylog.events.GetCheckpointLogIdEvent;
66 import org.continuent.sequoia.controller.recoverylog.events.GetNumberOfLogEntriesEvent;
67 import org.continuent.sequoia.controller.recoverylog.events.GetUpdateCountEvent;
68 import org.continuent.sequoia.controller.recoverylog.events.LogCommitEvent;
69 import org.continuent.sequoia.controller.recoverylog.events.LogEntry;
70 import org.continuent.sequoia.controller.recoverylog.events.LogEvent;
71 import org.continuent.sequoia.controller.recoverylog.events.LogRequestCompletionEvent;
72 import org.continuent.sequoia.controller.recoverylog.events.LogRequestEvent;
73 import org.continuent.sequoia.controller.recoverylog.events.LogRollbackEvent;
74 import org.continuent.sequoia.controller.recoverylog.events.RemoveCheckpointEvent;
75 import org.continuent.sequoia.controller.recoverylog.events.ResetLogEvent;
76 import org.continuent.sequoia.controller.recoverylog.events.ShiftLogEntriesEvent;
77 import org.continuent.sequoia.controller.recoverylog.events.ShutdownLogEvent;
78 import org.continuent.sequoia.controller.recoverylog.events.StoreCheckpointWithLogIdEvent;
79 import org.continuent.sequoia.controller.requestmanager.TransactionMetaData;
80 import org.continuent.sequoia.controller.requestmanager.distributed.DistributedRequestManager;
81 import org.continuent.sequoia.controller.requests.AbstractRequest;
82 import org.continuent.sequoia.controller.requests.AbstractWriteRequest;
83 import org.continuent.sequoia.controller.requests.RequestFactory;
84 import org.continuent.sequoia.controller.requests.SelectRequest;
85 import org.continuent.sequoia.controller.requests.StoredProcedure;
86 import org.continuent.sequoia.controller.requests.UnknownWriteRequest;
87 import org.continuent.sequoia.controller.scheduler.AbstractScheduler;
88
89 /**
90  * Recovery Log using a database accessed through JDBC.
91  *
92  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
93  * @author <a HREF="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
94  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>*
95  * @author <a HREF="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
96  * </a>
97  * @version 1.0
98  */

99 public class RecoveryLog implements XmlComponent
100 {
101   static final String JavaDoc BEGIN = "begin";
102   private static final String JavaDoc COMMIT = "commit";
103   private static final String JavaDoc ROLLBACK = "rollback";
104   private static final String JavaDoc CLOSE_PERSISTENT_CONNECTION = "close";
105   private static final String JavaDoc OPEN_PERSISTENT_CONNECTION = "open";
106   /**
107    * Unkown user used for transaction abort when the transactional context
108    * cannot be retrieved
109    */

110   public static final String JavaDoc UNKNOWN_USER = "_u u_";
111
112   /** Index of the log id column in the log table */
113   public static final int COLUMN_INDEX_LOG_ID = 1;
114   /** Index of the vlogin column in the log table */
115   public static final int COLUMN_INDEX_VLOGIN = 2;
116   /** Index of the sql column in the log table */
117   public static final int COLUMN_INDEX_SQL = 3;
118   /** Index of the sql_param column in the log table */
119   public static final int COLUMN_INDEX_SQL_PARAMS = 4;
120   /** Index of the auto_conn_tran column in the log table */
121   public static final int COLUMN_INDEX_AUTO_CONN_TRAN = 5;
122   /** Index of the transaction id column in the log table */
123   public static final int COLUMN_INDEX_TRANSACTION_ID = 6;
124   /** Index of the request id column in the log table */
125   public static final int COLUMN_INDEX_REQUEST_ID = 7;
126   /** Index of the exec_status column in the log table */
127   public static final int COLUMN_INDEX_EXEC_STATUS = 8;
128   /** Index of the exec_time column in the log table */
129   public static final int COLUMN_INDEX_EXEC_TIME = 9;
130   /** Index of the update_count column in the log table */
131   public static final int COLUMN_INDEX_UPDATE_COUNT = 10;
132   /** Index of the completion_log_id column in the log table */
133   static public final int COLUMN_INDEX_COMPLETION_LOG_ID = 11;
134
135   static Trace logger = Trace
136                                                                                    .getLogger("org.continuent.sequoia.controller.recoverylog");
137   static Trace endUserLogger = Trace
138                                                                                    .getLogger("org.continuent.sequoia.enduser");
139
140   private RequestFactory requestFactory = ControllerConstants.CONTROLLER_FACTORY
141                                                                                    .getRequestFactory();
142
143   /** Number of backends currently recovering */
144   private long recoveringNb = 0;
145
146   /** Size of the pendingRecoveryTasks queue used by the recover thread */
147   protected int recoveryBatchSize;
148
149   /** Driver class name. */
150   private String JavaDoc driverClassName;
151
152   /** Driver jar file or directory where the class files can be found. */
153   private String JavaDoc driverPath;
154
155   /** Driver URL. */
156   private String JavaDoc url;
157
158   /** User's login. */
159   private String JavaDoc login;
160
161   /** User's password. */
162   private String JavaDoc password;
163
164   /** Database connection */
165   private Connection JavaDoc internalConnectionManagedByGetDatabaseConnection = null;
166
167   /**
168    * Recovery log table options.
169    *
170    * @see #setLogTableCreateStatement(String, String, String, String, String,
171    * String, String, String, String, String, String, String, String)
172    */

173   private String JavaDoc logTableCreateTable;
174   private String JavaDoc logTableName;
175   private String JavaDoc logTableCreateStatement;
176   private String JavaDoc logTableLogIdType;
177   private String JavaDoc logTableVloginType;
178   private String JavaDoc logTableSqlColumnName;
179   private String JavaDoc logTableSqlType;
180   private String JavaDoc logTableAutoConnTranColumnType;
181   private String JavaDoc logTableTransactionIdType;
182   private String JavaDoc logTableRequestIdType;
183   private String JavaDoc logTableExecTimeType;
184   private String JavaDoc logTableUpdateCountType;
185   private String JavaDoc logTableExtraStatement;
186
187   /**
188    * Checkpoint table options.
189    *
190    * @see #setCheckpointTableCreateStatement(String, String, String, String,
191    * String)
192    */

193   private String JavaDoc checkpointTableCreateTable;
194   private String JavaDoc checkpointTableName;
195   private String JavaDoc checkpointTableCreateStatement;
196   private String JavaDoc checkpointTableNameType;
197   private String JavaDoc checkpointTableLogIdType;
198   private String JavaDoc checkpointTableExtraStatement;
199
200   /**
201    * Backend table options
202    *
203    * @see #setBackendTableCreateStatement(String, String, String, String,
204    * String, String, String)
205    */

206   private String JavaDoc backendTableCreateStatement;
207   private String JavaDoc backendTableName;
208   private String JavaDoc backendTableCreateTable;
209   private String JavaDoc backendTableDatabaseName;
210   private String JavaDoc backendTableExtraStatement;
211   private String JavaDoc backendTableCheckpointName;
212   private String JavaDoc backendTableBackendState;
213   private String JavaDoc backendTableBackendName;
214
215   /**
216    * Dump table options
217    *
218    * @see #setDumpTableCreateStatement(String, String, String, String, String,
219    * String, String, String, String, String, String)
220    */

221   private String JavaDoc dumpTableCreateStatement;
222   private String JavaDoc dumpTableCreateTable;
223   private String JavaDoc dumpTableName;
224   private String JavaDoc dumpTableDumpNameColumnType;
225   private String JavaDoc dumpTableDumpDateColumnType;
226   private String JavaDoc dumpTableDumpPathColumnType;
227   private String JavaDoc dumpTableDumpFormatColumnType;
228   private String JavaDoc dumpTableCheckpointNameColumnType;
229   private String JavaDoc dumpTableBackendNameColumnType;
230   private String JavaDoc dumpTableTablesColumnName;
231   private String JavaDoc dumpTableTablesColumnType;
232   private String JavaDoc dumpTableExtraStatementDefinition;
233
234   /** Current maximum value of the primary key in logTableName. */
235   private long logTableId = 0;
236
237   /** Timeout for SQL requests. */
238   private int timeout;
239
240   private LoggerThread loggerThread;
241
242   private boolean isShuttingDown = false;
243   private String JavaDoc logTableAddCompletionLogIdStatement;
244
245   /**
246    * Creates a new <code>RecoveryLog</code> instance.
247    *
248    * @param driverPath the driver jar file or directory where the class files
249    * can be found.
250    * @param driverClassName the driverClassName class name.
251    * @param url the JDBC URL.
252    * @param login the login to use to connect to the database.
253    * @param password the password to connect to the database.
254    * @param requestTimeout timeout in seconds for update queries.
255    * @param recoveryBatchSize number of queries that can be accumulated into a
256    * batch when recovering
257    */

258   public RecoveryLog(String JavaDoc driverPath, String JavaDoc driverClassName, String JavaDoc url,
259       String JavaDoc login, String JavaDoc password, int requestTimeout, int recoveryBatchSize)
260   {
261     this.driverPath = driverPath;
262     this.driverClassName = driverClassName;
263     this.url = url;
264     this.login = login;
265     this.password = password;
266     this.timeout = requestTimeout;
267     if (recoveryBatchSize < 1)
268     {
269       logger
270           .warn("RecoveryBatchSize was set to a value lesser than 1, resetting value to 1.");
271       recoveryBatchSize = 1;
272     }
273     this.recoveryBatchSize = recoveryBatchSize;
274
275     if (url.startsWith("jdbc:hsqldb:file:"))
276     {
277       if (url.indexOf("shutdown=true") == -1)
278       {
279         this.url = url + ";shutdown=true";
280         String JavaDoc msg = "Hsqldb RecoveryLog url has no shutdown=true option.\n"
281             + "This prevents the recovery log from shutting down correctly.\n"
282             + "Please update your vdb.xml file.\n" + "Setting url to '"
283             + this.url + "'.";
284         logger.warn(msg);
285         endUserLogger.warn(msg);
286       }
287     }
288
289     // Connect to the database
290
try
291     {
292       getDatabaseConnection();
293     }
294     catch (SQLException JavaDoc e)
295     {
296       throw new RuntimeException JavaDoc("Unable to connect to the database: " + e);
297     }
298
299     // Logger thread will be created in checkRecoveryLogTables()
300
// after database has been initialized
301
}
302
303   //
304
// Database manipulation and access
305
//
306

307   /**
308    * Gets a connection to the database.
309    *
310    * @return a connection to the database
311    * @exception SQLException if an error occurs.
312    * @see #invalidateInternalConnection()
313    */

314   protected Connection JavaDoc getDatabaseConnection() throws SQLException JavaDoc
315   {
316     try
317     {
318       if (internalConnectionManagedByGetDatabaseConnection == null)
319       {
320         if (logger.isDebugEnabled())
321           logger.debug(Translate.get("recovery.jdbc.connect", new String JavaDoc[]{url,
322               login}));
323         internalConnectionManagedByGetDatabaseConnection = DriverManager
324             .getConnection(url, login, password, driverPath, driverClassName);
325       }
326       return internalConnectionManagedByGetDatabaseConnection;
327     }
328     catch (RuntimeException JavaDoc e)
329     {
330       String JavaDoc msg = Translate.get("recovery.jdbc.connect.failed", e);
331       if (logger.isDebugEnabled())
332         logger.debug(msg, e);
333       throw new SQLException JavaDoc(msg);
334     }
335     catch (SQLException JavaDoc e)
336     {
337       invalidateInternalConnection();
338       String JavaDoc msg = Translate.get("recovery.jdbc.connect.failed", e);
339       if (logger.isDebugEnabled())
340         logger.debug(msg, e);
341       throw new SQLException JavaDoc(msg);
342     }
343   }
344
345   /**
346    * Increments the value of logTableId.
347    */

348   synchronized long incrementLogTableId()
349   {
350     logTableId++;
351     return logTableId;
352   }
353
354   /**
355    * Checks if the tables (log and checkpoint) already exist, and create them if
356    * needed.
357    */

358   private void intializeDatabase() throws SQLException JavaDoc
359   {
360     boolean createLogTable = true;
361     boolean createCheckpointTable = true;
362     boolean createBackendTable = true;
363     boolean createDumpTable = true;
364     boolean addCompletionLogId = false;
365     Connection JavaDoc connection;
366     // Check if tables exist
367
try
368     {
369       connection = getDatabaseConnection();
370
371       if (connection == null)
372         throw new SQLException JavaDoc(Translate.get("recovery.jdbc.connect.failed",
373             "null connection returned by DriverManager"));
374
375       connection.setAutoCommit(false);
376       // Get DatabaseMetaData
377
DatabaseMetaData JavaDoc metaData = connection.getMetaData();
378
379       // Get a description of tables matching the catalog, schema, table name
380
// and type.
381
// Sending in null for catalog and schema drop them from the selection
382
// criteria. Replace the last argument in the getTables method with
383
// types below to obtain only database tables. (Sending in null
384
// retrieves all types).
385
String JavaDoc[] types = {"TABLE", "VIEW"};
386       ResultSet JavaDoc rs = metaData.getTables(null, null, "%", types);
387
388       // Get tables metadata
389
String JavaDoc tableName;
390       while (rs.next())
391       {
392         // 1 is table catalog, 2 is table schema, 3 is table name, 4 is type
393
tableName = rs.getString(3);
394         if (logger.isDebugEnabled())
395           logger.debug(Translate.get("recovery.jdbc.table.found", tableName));
396         if (tableName.equalsIgnoreCase(logTableName))
397         {
398           if (tableName.compareTo(logTableName) != 0)
399             logger.warn(Translate.get("recovery.jdbc.logtable.case.mismatch",
400                 new String JavaDoc[]{logTableName, tableName}));
401           createLogTable = false;
402           // initialize logTableId
403
PreparedStatement JavaDoc p = null;
404           try
405           {
406             ResultSet JavaDoc result = null;
407             p = connection
408                 .prepareStatement("SELECT MAX(log_id) AS max_log_id FROM "
409                     + logTableName);
410             result = p.executeQuery();
411             if (result.next())
412               logTableId = result.getLong(1);
413             else
414               logTableId = 0;
415             p.close();
416             String JavaDoc name = "completion_log_id";
417             if (!metaData.supportsMixedCaseIdentifiers()
418                 && metaData.storesUpperCaseIdentifiers())
419               name = name.toUpperCase();
420             ResultSet JavaDoc columns = metaData
421                 .getColumns(null, null, tableName, name);
422             if (!columns.next())
423               addCompletionLogId = true;
424           }
425           catch (SQLException JavaDoc e)
426           {
427             try
428             {
429               if (p != null)
430                 p.close();
431             }
432             catch (Exception JavaDoc ignore)
433             {
434             }
435             throw new RuntimeException JavaDoc(Translate.get(
436                 "recovery.jdbc.logtable.getvalue.failed", e));
437           }
438
439         }
440         if (tableName.equalsIgnoreCase(checkpointTableName))
441         {
442           if (tableName.compareTo(checkpointTableName) != 0)
443             logger.warn(Translate.get(
444                 "recovery.jdbc.checkpointtable.case.mismatch", new String JavaDoc[]{
445                     checkpointTableName, tableName}));
446           createCheckpointTable = false;
447           if (logger.isDebugEnabled())
448           {
449             // Dump checkpoints.
450
StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
451             sb.append("Checkpoint list...");
452
453             Map JavaDoc checkpoints = this.getCheckpoints();
454             Iterator JavaDoc checkPointIterator = checkpoints.keySet().iterator();
455             while (checkPointIterator.hasNext())
456             {
457               String JavaDoc name = (String JavaDoc) checkPointIterator.next();
458               String JavaDoc logId = (String JavaDoc) checkpoints.get(name);
459               sb.append("\n");
460               sb.append("name=[").append(name).append("] log_id=[").append(
461                   logId).append("]");
462             }
463             logger.debug(sb.toString());
464           }
465         }
466         else if (tableName.equalsIgnoreCase(backendTableName))
467         {
468           if (tableName.compareTo(backendTableName) != 0)
469             logger.warn(Translate.get(
470                 "recovery.jdbc.backendtable.case.mismatch", new String JavaDoc[]{
471                     backendTableName, tableName}));
472           createBackendTable = false;
473         }
474         else if (tableName.equalsIgnoreCase(dumpTableName))
475         {
476           if (tableName.compareTo(dumpTableName) != 0)
477             logger.warn(Translate.get("recovery.jdbc.dumptable.case.mismatch",
478                 new String JavaDoc[]{backendTableName, tableName}));
479           createDumpTable = false;
480         }
481       }
482       try
483       {
484         connection.commit();
485         connection.setAutoCommit(true);
486       }
487       catch (Exception JavaDoc ignore)
488       {
489         // Read-only transaction we don't care
490
}
491     }
492     catch (SQLException JavaDoc e)
493     {
494       logger.error(Translate.get("recovery.jdbc.table.no.description"), e);
495       throw e;
496     }
497
498     // Create the missing tables
499
Statement JavaDoc stmt = null;
500     if (createLogTable)
501     {
502       if (logger.isInfoEnabled())
503         logger.info(Translate
504             .get("recovery.jdbc.logtable.create", logTableName));
505       try
506       {
507         stmt = connection.createStatement();
508         stmt.executeUpdate(logTableCreateStatement);
509         stmt.close();
510       }
511       catch (SQLException JavaDoc e)
512       {
513         throw new SQLException JavaDoc(Translate.get(
514             "recovery.jdbc.logtable.create.failed", new String JavaDoc[]{logTableName,
515                 e.getMessage()}));
516       }
517     }
518     else if (addCompletionLogId)
519     {
520       if (logger.isInfoEnabled())
521         logger.info(Translate.get("recovery.jdbc.logtable.add.completionlogid",
522             logTableName));
523       try
524       {
525         stmt = connection.createStatement();
526         stmt.executeUpdate(logTableAddCompletionLogIdStatement);
527         stmt.close();
528       }
529       catch (SQLException JavaDoc e)
530       {
531         throw new SQLException JavaDoc(Translate.get(
532             "recovery.jdbc.logtable.add.completionlogid.failed", new String JavaDoc[]{
533                 logTableName, e.getMessage()}));
534       }
535     }
536     if (createCheckpointTable)
537     {
538       if (logger.isInfoEnabled())
539         logger.info(Translate.get("recovery.jdbc.checkpointtable.create",
540             checkpointTableName));
541       try
542       {
543         stmt = connection.createStatement();
544         stmt.executeUpdate(checkpointTableCreateStatement);
545         stmt.close();
546       }
547       catch (SQLException JavaDoc e)
548       {
549         throw new SQLException JavaDoc(Translate.get(
550             "recovery.jdbc.checkpointtable.create.failed", new String JavaDoc[]{
551                 logTableName, e.getMessage()}));
552       }
553
554       // Add an initial checkpoint in the table
555
setInitialEmptyRecoveryLogCheckpoint();
556     }
557     if (createBackendTable)
558     {
559       if (logger.isInfoEnabled())
560         logger.info(Translate.get("recovery.jdbc.backendtable.create",
561             backendTableName));
562       try
563       {
564         stmt = connection.createStatement();
565         stmt.executeUpdate(backendTableCreateStatement);
566         stmt.close();
567       }
568       catch (SQLException JavaDoc e)
569       {
570         throw new SQLException JavaDoc(Translate.get(
571             "recovery.jdbc.backendtable.create.failed", new String JavaDoc[]{
572                 logTableName, e.getMessage()}));
573       }
574     }
575     if (createDumpTable)
576     {
577       if (logger.isInfoEnabled())
578         logger.info(Translate.get("recovery.jdbc.dumptable.create",
579             dumpTableName));
580       try
581       {
582         stmt = connection.createStatement();
583         stmt.executeUpdate(dumpTableCreateStatement);
584         stmt.close();
585       }
586       catch (SQLException JavaDoc e)
587       {
588         throw new SQLException JavaDoc(Translate.get(
589             "recovery.jdbc.dumptable.create.failed", new String JavaDoc[]{
590                 dumpTableName, e.getMessage()}));
591       }
592     }
593   }
594
595   /**
596    * Adds a checkpoint called InitialEmptyRecoveryLog in the checkpoint table.
597    * The checkpoint points to the current logTableId.
598    *
599    * @throws SQLException if an error occurs
600    */

601   private void setInitialEmptyRecoveryLogCheckpoint() throws SQLException JavaDoc
602   {
603     String JavaDoc checkpointName = "Initial_empty_recovery_log";
604     PreparedStatement JavaDoc pstmt = null;
605     try
606     {
607       if (logger.isDebugEnabled())
608         logger.debug("Storing checkpoint " + checkpointName + " at request id "
609             + logTableId);
610       pstmt = getDatabaseConnection().prepareStatement(
611           "INSERT INTO " + checkpointTableName + " VALUES(?,?)");
612       pstmt.setString(1, checkpointName);
613       pstmt.setLong(2, logTableId);
614       pstmt.executeUpdate();
615       pstmt.close();
616     }
617     catch (SQLException JavaDoc e)
618     {
619       try
620       {
621         if (pstmt != null)
622           pstmt.close();
623       }
624       catch (Exception JavaDoc ignore)
625       {
626       }
627       throw new SQLException JavaDoc(Translate.get(
628           "recovery.jdbc.checkpoint.store.failed", new String JavaDoc[]{checkpointName,
629               e.getMessage()}));
630     }
631   }
632
633   /**
634    * Check the recovery log consistency for the first controller starting in a
635    * cluster (might come back from a complete cluster outage).
636    *
637    * @throws SQLException if a recovery log access error occurs
638    */

639   public void checkRecoveryLogConsistency() throws SQLException JavaDoc
640   {
641     logger.info(Translate.get("recovery.consistency.checking"));
642
643     PreparedStatement JavaDoc stmt = null;
644     ResultSet JavaDoc rs = null;
645     PreparedStatement JavaDoc updateStmt = null;
646     try
647     {
648       // Look for requests with the execution status still to EXECUTING.
649
stmt = getDatabaseConnection().prepareStatement(
650           "SELECT * FROM " + getLogTableName() + " WHERE exec_status LIKE ?");
651       stmt.setString(1, LogEntry.EXECUTING);
652       rs = stmt.executeQuery();
653
654       if (rs.next())
655       { // Change entry status to UNKNOWN
656
updateStmt = getDatabaseConnection()
657             .prepareStatement(
658                 "UPDATE " + getLogTableName()
659                     + " SET exec_status=? WHERE log_id=?");
660         updateStmt.setString(1, LogEntry.UNKNOWN);
661         do
662         {
663           long logId = rs.getLong("log_id");
664           if (logger.isWarnEnabled())
665             logger
666                 .warn("Log entry "
667                     + logId
668                     + " ("
669                     + rs.getString(getLogTableSqlColumnName())
670                     + ") still has an executing status in the recovery log. Switching to unknown state.");
671           updateStmt.setLong(2, logId);
672           updateStmt.executeUpdate();
673         }
674         while (rs.next());
675         updateStmt.close();
676       }
677       rs.close();
678       stmt.close();
679
680       // Look for open transactions
681
stmt = getDatabaseConnection().prepareStatement(
682           "SELECT * FROM " + getLogTableName() + " WHERE "
683               + getLogTableSqlColumnName() + " LIKE ? AND "
684               + "transaction_id not in (SELECT transaction_id" + " FROM "
685               + getLogTableName() + " WHERE " + getLogTableSqlColumnName()
686               + " = ? OR " + getLogTableSqlColumnName() + " = ?) ");
687
688       stmt.setString(1, BEGIN + "%");
689       stmt.setString(2, COMMIT);
690       stmt.setString(3, ROLLBACK);
691
692       rs = stmt.executeQuery();
693       while (rs.next())
694       {
695         // Add a rollback in the log
696
long tid = rs.getLong("transaction_id");
697         if (logger.isWarnEnabled())
698           logger.warn("Transaction " + tid
699               + " has not completed. Inserting a rollback in the recovery log");
700         long logId = logRollback(new TransactionMetaData(tid, 0, UNKNOWN_USER,
701             false, 0));
702         logRequestCompletion(logId, true, 0);
703       }
704       rs.close();
705     }
706     catch (SQLException JavaDoc e)
707     {
708       logger.error("Failed to check recovery log consistency", e);
709       throw new SQLException JavaDoc(Translate.get(
710           "recovery.consistency.checking.failed", e.getMessage()));
711     }
712     finally
713     {
714       try
715       {
716         if (rs != null)
717           rs.close();
718       }
719       catch (Exception JavaDoc ignore)
720       {
721       }
722       try
723       {
724         if (stmt != null)
725           stmt.close();
726       }
727       catch (Exception JavaDoc ignore)
728       {
729       }
730       try
731       {
732         if (updateStmt != null)
733           updateStmt.close();
734       }
735       catch (Exception JavaDoc ignore)
736       {
737       }
738     }
739
740   }
741
742   /**
743    * Reset the current log table id and delete the recovery log information
744    * older than the given checkpoint. This method also deletes all entries in
745    * the checkpoint table. This method is asynchronous: the delete is performed
746    * via a post to the logger thread.
747    *
748    * @param checkpointName the checkpoint name to delete from.
749    * @param newCheckpointId the new checkpoint identifier
750    * @throws SQLException if an error occurs
751    */

752   public void resetLogTableIdAndDeleteRecoveryLog(String JavaDoc checkpointName,
753       long newCheckpointId) throws SQLException JavaDoc
754   {
755     long oldId = getCheckpointLogId(checkpointName);
756     synchronized (this)
757     {
758       // resetLog cleans the recovery log table, resets the checkpointTable and
759
// renumber the queries since oldId (checkpoint assigned to the transfer).
760
loggerThread
761           .log(new ResetLogEvent(oldId, newCheckpointId, checkpointName));
762       logTableId = newCheckpointId + logTableId - oldId;
763     }
764   }
765
766   /**
767    * Reset the recovery log by deleting the log table and checkpoint table. This
768    * method also clears checkpoint names in the dump table. This method should
769    * only be used to re-initialize the virtual database at initial startup or
770    * after a complete cluster outage.
771    *
772    * @throws SQLException if a database access error occurs
773    */

774   public void resetRecoveryLog() throws SQLException JavaDoc
775   {
776     PreparedStatement JavaDoc pstmt = null;
777     try
778     {
779       // Clean the tables
780

781       if (logger.isDebugEnabled())
782         logger.debug("Deleting " + logTableName + " table.");
783       pstmt = getDatabaseConnection().prepareStatement(
784           "DELETE FROM " + logTableName);
785       pstmt.executeUpdate();
786       pstmt.close();
787
788       if (logger.isDebugEnabled())
789         logger.debug("Deleting " + checkpointTableName + " table.");
790       pstmt = getDatabaseConnection().prepareStatement(
791           "DELETE FROM " + checkpointTableName);
792       pstmt.executeUpdate();
793       pstmt.close();
794
795       if (logger.isDebugEnabled())
796         logger.debug("Resetting checkpoint associated to dumps in "
797             + dumpTableName + " table.");
798       pstmt = getDatabaseConnection().prepareStatement(
799           "UPDATE " + dumpTableName + " SET checkpoint_name=?");
800       pstmt.setString(1, "");
801       pstmt.executeUpdate();
802       pstmt.close();
803     }
804     catch (SQLException JavaDoc e)
805     {
806       String JavaDoc msg = "Error while resetting recovery log";
807       logger.error(msg, e);
808       try
809       {
810         if (pstmt != null)
811           pstmt.close();
812       }
813       catch (Exception JavaDoc ignore)
814       {
815       }
816       throw new SQLException JavaDoc(msg + " (" + e + ")");
817     }
818
819     // Put back an initial empty recovery log in the checkpoint table
820
setInitialEmptyRecoveryLogCheckpoint();
821   }
822
823   /**
824    * Invalidate the connection when an error occurs so that the next call to
825    * getDatabaseConnection() re-allocates a new connection.
826    *
827    * @see #getDatabaseConnection()
828    */

829   protected void invalidateInternalConnection()
830   {
831     try
832     {
833       internalConnectionManagedByGetDatabaseConnection.close();
834     }
835     catch (Exception JavaDoc ignore)
836     {
837     }
838     internalConnectionManagedByGetDatabaseConnection = null;
839   }
840
841   //
842
//
843
// Logging related methods
844
//
845
//
846

847   /**
848    * Log a transaction abort. This is used only for transaction that were
849    * started but where no request was executed, which is in fact an empty
850    * transaction. The underlying implementation might safely discard the
851    * corresponding begin from the log as an optimization.
852    *
853    * @param tm The transaction marker metadata
854    * @return the identifier of the entry in the recovery log
855    */

856   public long logAbort(TransactionMetaData tm)
857   {
858     // We have to perform exactly the same job as a rollback
859
return logRollback(tm);
860   }
861
862   /**
863    * Log the beginning of a new transaction (always as a SUCCESS not EXECUTING).
864    *
865    * @param tm The transaction marker metadata
866    * @return the identifier of the entry in the recovery log
867    */

868   public long logBegin(TransactionMetaData tm)
869   {
870     // Store the begin in the database
871
long id = incrementLogTableId();
872     LogEntry logEntry;
873     if (tm.isPersistentConnection())
874     { // Append the persistent connection id to the SQL
875
logEntry = new LogEntry(id, tm.getLogin(), BEGIN + " "
876           + tm.getPersistentConnectionId(), null, LogEntry.TRANSACTION, tm
877           .getTransactionId());
878     }
879     else
880     {
881       logEntry = new LogEntry(id, tm.getLogin(), BEGIN, null,
882           LogEntry.TRANSACTION, tm.getTransactionId());
883     }
884     logEntry.setExecutionStatus(LogEntry.SUCCESS);
885     loggerThread.log(new LogRequestEvent(logEntry));
886     return id;
887   }
888
889   /**
890    * Log the closing of the given persistent connection (always as a SUCCESS).
891    *
892    * @param login the login executing the request
893    * @param persistentConnectionId the id of the persistent connection to close
894    * @return the identifier of the entry in the recovery log
895    */

896   public long logClosePersistentConnection(String JavaDoc login,
897       long persistentConnectionId)
898   {
899     long id = incrementLogTableId();
900     LogEntry logEntry = new LogEntry(id, login, CLOSE_PERSISTENT_CONNECTION,
901         null, LogEntry.PERSISTENT_CONNECTION, persistentConnectionId);
902     logEntry.setExecutionStatus(LogEntry.SUCCESS);
903     loggerThread.log(new LogRequestEvent(logEntry));
904     return id;
905   }
906
907   /**
908    * Log a transaction commit.
909    *
910    * @param tm The transaction marker metadata
911    * @return the identifier of the entry in the recovery log
912    */

913   public long logCommit(TransactionMetaData tm)
914   {
915     long id = incrementLogTableId();
916     loggerThread.log(new LogCommitEvent(new LogEntry(id, tm.getLogin(), COMMIT,
917         null, LogEntry.TRANSACTION, tm.getTransactionId())));
918     tm.setLogId(id);
919     return id;
920   }
921
922   /**
923    * Log a log entry in the recovery log.
924    *
925    * @param logEntry the log entry to to be written in the recovery log.
926    */

927   public void logLogEntry(LogEntry logEntry)
928   {
929     loggerThread.log(new LogRequestEvent(logEntry));
930   }
931
932   /**
933    * Log the opening of the given persistent connection (always as a SUCCESS).
934    *
935    * @param login the login executing the request
936    * @param persistentConnectionId the id of the persistent connection to open
937    * @return the identifier of the entry in the recovery log
938    */

939   public long logOpenPersistentConnection(String JavaDoc login,
940       long persistentConnectionId)
941   {
942     long id = incrementLogTableId();
943     LogEntry logEntry = new LogEntry(id, login, OPEN_PERSISTENT_CONNECTION,
944         null, LogEntry.PERSISTENT_CONNECTION, persistentConnectionId);
945     logEntry.setExecutionStatus(LogEntry.EXECUTING);
946     loggerThread.log(new LogRequestEvent(logEntry));
947     return id;
948   }
949
950   /**
951    * Log a transaction savepoint removal.
952    *
953    * @param tm The transaction marker metadata
954    * @param name The name of the savepoint to log
955    * @return the identifier of the entry in the recovery log
956    */

957   public long logReleaseSavepoint(TransactionMetaData tm, String JavaDoc name)
958   {
959     long id = incrementLogTableId();
960     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
961         "release " + name, null, LogEntry.TRANSACTION, tm.getTransactionId())));
962     return id;
963   }
964
965   private LogEntry buildLogEntry(AbstractRequest request, long id,
966       long execTime, int updateCount)
967   {
968     String JavaDoc autoConnTrans;
969     long tid;
970     if (request.isAutoCommit())
971     {
972       if (request.isPersistentConnection())
973       {
974         autoConnTrans = LogEntry.PERSISTENT_CONNECTION;
975         tid = request.getPersistentConnectionId();
976       }
977       else
978       {
979         autoConnTrans = LogEntry.AUTOCOMMIT;
980         tid = 0;
981       }
982     }
983     else
984     {
985       autoConnTrans = LogEntry.TRANSACTION;
986       tid = request.getTransactionId();
987     }
988     LogEntry logEntry = new LogEntry(id, request.getLogin(), request
989         .getSqlOrTemplate(), request.getPreparedStatementParameters(),
990         autoConnTrans, tid, request.getEscapeProcessing(), request.getId(),
991         execTime, updateCount);
992     return logEntry;
993   }
994
995   /**
996    * Log a request (read or write) that is going to execute and set the logId on
997    * the request object.
998    *
999    * @param request The request to log (read or write)
1000   * @return the identifier of the entry in the recovery log
1001   */

1002  public long logRequestExecuting(AbstractRequest request)
1003  {
1004    long id = incrementLogTableId();
1005    long execTime = 0;
1006    if (request.getStartTime() != 0)
1007    {
1008      if (request.getEndTime() != 0)
1009        execTime = request.getEndTime() - request.getStartTime();
1010      else
1011        execTime = System.currentTimeMillis() - request.getStartTime();
1012    }
1013    loggerThread.log(new LogRequestEvent(
1014        buildLogEntry(request, id, execTime, 0)));
1015    request.setLogId(id);
1016    return id;
1017  }
1018
1019  /**
1020   * Update the completion status of a request completion in the recovery log
1021   * given its id.
1022   *
1023   * @param logId recovery log id for this request as it was originally logged
1024   * @param success true if the request execution was successful
1025   * @param execTime request execution time in ms
1026   */

1027  public void logRequestCompletion(long logId, boolean success, long execTime)
1028  {
1029    logRequestExecuteUpdateCompletion(logId, success, 0, execTime);
1030  }
1031
1032  /**
1033   * Update the completion status of a request completion in the recovery log
1034   * given its id.
1035   *
1036   * @param logId recovery log id for this request as it was originally logged
1037   * @param success true if the request execution was successful
1038   * @param execTimeInMs request execution time in ms
1039   * @param updateCount request update count when successful
1040   */

1041  public void logRequestCompletion(long logId, boolean success,
1042      long execTimeInMs, int updateCount)
1043  {
1044    logRequestExecuteUpdateCompletion(logId, success, updateCount, execTimeInMs);
1045
1046  }
1047
1048  /**
1049   * Update the completion status of a request executed with executeUpdate().
1050   *
1051   * @param logId recovery log id for this request as it was originally logged
1052   * @param success true if the request execution was successful
1053   * @param updateCount the number of updated rows returned by executeUpdate()
1054   * (meaningful only if success)
1055   * @param execTime request execution time in ms
1056   */

1057  public void logRequestExecuteUpdateCompletion(long logId, boolean success,
1058      int updateCount, long execTime)
1059  {
1060    loggerThread.log(new LogRequestCompletionEvent(logId, success, updateCount,
1061        execTime, getCurrentLogId()));
1062  }
1063
1064  /**
1065   * Log a transaction rollback.
1066   *
1067   * @param tm The transaction marker metadata
1068   * @return the identifier of the entry in the recovery log
1069   */

1070  public long logRollback(TransactionMetaData tm)
1071  {
1072    long id = incrementLogTableId();
1073    // Some backends started a recovery process, log the rollback
1074
loggerThread.log(new LogRollbackEvent(new LogEntry(id, tm.getLogin(),
1075        ROLLBACK, null, LogEntry.TRANSACTION, tm.getTransactionId())));
1076    tm.setLogId(id);
1077    return id;
1078  }
1079
1080  /**
1081   * Log a transaction rollback to a savepoint
1082   *
1083   * @param tm The transaxtion marker metadata
1084   * @param savepointName The name of the savepoint
1085   * @return the identifier of the entry in the recovery log
1086   */

1087  public long logRollbackToSavepoint(TransactionMetaData tm,
1088      String JavaDoc savepointName)
1089  {
1090    long id = incrementLogTableId();
1091    loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
1092        "rollback " + savepointName, null, LogEntry.TRANSACTION, tm
1093            .getTransactionId())));
1094    tm.setLogId(id);
1095    return id;
1096  }
1097
1098  /**
1099   * Log a transaction savepoint.
1100   *
1101   * @param tm The transaction marker metadata
1102   * @param name The name of the savepoint to log
1103   * @return the identifier of the entry in the recovery log
1104   */

1105  public long logSetSavepoint(TransactionMetaData tm, String JavaDoc name)
1106  {
1107    long id = incrementLogTableId();
1108    loggerThread
1109        .log(new LogRequestEvent(new LogEntry(id, tm.getLogin(), "savepoint "
1110            + name, null, LogEntry.TRANSACTION, tm.getTransactionId())));
1111    tm.setLogId(id);
1112    return id;
1113  }
1114
1115  /**
1116   * Throw an SQLException if the recovery log is shutting down
1117   *
1118   * @throws SQLException if recovery log is shutting down
1119   */

1120  private synchronized void checkIfShuttingDown() throws SQLException JavaDoc
1121  {
1122    if (isShuttingDown)
1123      throw new SQLException JavaDoc(
1124          "Recovery log is shutting down, log access has been denied");
1125  }
1126
1127  /**
1128   * Shutdown the recovery log and all its threads by enqueueing a log shutdown
1129   * event and waiting until it is processed.
1130   */

1131  public synchronized void shutdown()
1132  {
1133    if (isShuttingDown)
1134      return;
1135    isShuttingDown = true;
1136    if (loggerThread != null)
1137    {
1138      ShutdownLogEvent event = new ShutdownLogEvent();
1139      try
1140      {
1141        postAndWaitFor(event);
1142      }
1143      catch (SQLException JavaDoc e) // ignore error
1144
{
1145        logger.warn("Thread interrupted while awaiting log shutdown", e);
1146      }
1147    }
1148  }
1149
1150  //
1151
// Recovery process
1152
//
1153

1154  /**
1155   * Notify the recovery log that a recovery process has started.
1156   */

1157  public synchronized void beginRecovery()
1158  {
1159    recoveringNb++;
1160  }
1161
1162  /**
1163   * Possibly clean the recovery log after all recovery process are done. This
1164   * removes all failed statements or transactions without side effect from the
1165   * recovery log.
1166   *
1167   * @exception SQLException if an error occurs.
1168   */

1169  public void cleanRecoveryLog() throws SQLException JavaDoc
1170  {
1171    checkIfShuttingDown();
1172
1173    PreparedStatement JavaDoc stmt = null;
1174
1175    ResultSet JavaDoc rs = null;
1176    PreparedStatement JavaDoc pstmt = null;
1177    try
1178    {
1179      // Get the list of failed requests
1180
stmt = getDatabaseConnection().prepareStatement(
1181          "SELECT log_id," + logTableSqlColumnName + " FROM " + logTableName
1182              + " WHERE exec_status LIKE ?");
1183      stmt.setString(1, LogEntry.FAILED);
1184      rs = stmt.executeQuery();
1185      if (rs.next())
1186      { // Check entries to see if statement can be removed
1187
pstmt = getDatabaseConnection().prepareStatement(
1188            "DELETE FROM " + logTableName + " WHERE log_id=?");
1189        do
1190        {
1191          String JavaDoc sql = rs.getString(2);
1192          AbstractRequest decodedRequest = requestFactory.requestFromString(
1193              sql, false, false, timeout, "\n");
1194          // For now delete all requests that are not stored procedures
1195

1196          // TODO: Add a flag on requests to tell whether they have
1197
// unrollback-able side effects (SEQUOIA-587)
1198
if (decodedRequest instanceof StoredProcedure)
1199          {
1200            pstmt.setLong(1, rs.getLong(1));
1201            pstmt.executeUpdate();
1202          }
1203        }
1204        while (rs.next());
1205        pstmt.close();
1206      }
1207      rs.close();
1208      stmt.close();
1209    }
1210    catch (SQLException JavaDoc e)
1211    {
1212      invalidateInternalConnection();
1213      try
1214      {
1215        if (stmt != null)
1216          stmt.close();
1217      }
1218      catch (Exception JavaDoc ignore)
1219      {
1220      }
1221      try
1222      {
1223        if (pstmt != null)
1224          pstmt.close();
1225      }
1226      catch (Exception JavaDoc ignore)
1227      {
1228      }
1229      try
1230      {
1231        if (rs != null)
1232          rs.close();
1233      }
1234      catch (Exception JavaDoc ignore)
1235      {
1236      }
1237      throw new SQLException JavaDoc("Unable get cleanup recovery log (" + e + ")");
1238    }
1239  }
1240
1241  /**
1242   * Notify the recovery log that a recovery process has finished. If this is
1243   * the last recovery process to finish, the cleanRecoveryLog method is called
1244   *
1245   * @see #cleanRecoveryLog()
1246   */

1247  public synchronized void endRecovery()
1248  {
1249    recoveringNb--;
1250    if (recoveringNb == 0)
1251    {
1252      try
1253      {
1254        cleanRecoveryLog();
1255      }
1256      catch (SQLException JavaDoc e)
1257      {
1258        logger.error(Translate.get("recovery.cleaning.failed"), e);
1259      }
1260    }
1261  }
1262
1263  /**
1264   * Retrieve recovery information on a backend. This includes, the last known
1265   * state of the backend, and the last known checkpoint
1266   *
1267   * @param databaseName the virtual database name
1268   * @param backendName the backend name
1269   * @return <code>BackendRecoveryInfo<code> instance or <code>null</code> if the backend does not exist
1270   * @throws SQLException if a database access error occurs
1271   */

1272  public BackendRecoveryInfo getBackendRecoveryInfo(String JavaDoc databaseName,
1273      String JavaDoc backendName) throws SQLException JavaDoc
1274  {
1275    checkIfShuttingDown();
1276
1277    PreparedStatement JavaDoc stmt = null;
1278    String JavaDoc checkpoint = null;
1279    int backendState = BackendState.UNKNOWN;
1280    try
1281    {
1282      // 1. Get the reference point to delete
1283
stmt = getDatabaseConnection().prepareStatement(
1284          "SELECT * FROM " + backendTableName
1285              + " WHERE backend_name LIKE ? AND database_name LIKE ?");
1286      stmt.setString(1, backendName);
1287      stmt.setString(2, databaseName);
1288      ResultSet JavaDoc rs = stmt.executeQuery();
1289
1290      if (rs.next())
1291      {
1292        checkpoint = rs.getString("checkpoint_name");
1293        backendState = rs.getInt("backend_state");
1294      }
1295      rs.close();
1296    }
1297    catch (SQLException JavaDoc e)
1298    {
1299      invalidateInternalConnection();
1300      logger.info(
1301          "An error occured while retrieving backend recovery information", e);
1302      throw e;
1303    }
1304    finally
1305    {
1306      try
1307      {
1308        if (stmt != null)
1309          stmt.close();
1310      }
1311      catch (Exception JavaDoc ignore)
1312      {
1313      }
1314    }
1315    return new BackendRecoveryInfo(backendName, checkpoint, backendState,
1316        databaseName);
1317  }
1318
1319  /**
1320   * Return the current log id.
1321   *
1322   * @return the current log id.
1323   */

1324  synchronized protected long getCurrentLogId()
1325  {
1326    return logTableId;
1327  }
1328
1329  /**
1330   * Get the id of the last request logged in the recovery log.
1331   *
1332   * @param controllerId the controller ID which determines the search space for
1333   * request IDs
1334   * @return the last request id.
1335   * @throws SQLException if an error occured while retrieving the id.
1336   */

1337  public long getLastRequestId(long controllerId) throws SQLException JavaDoc
1338  {
1339    String JavaDoc request = "select max(request_id) from " + logTableName
1340        + " where (request_id > ?) and (request_id <= ?)";
1341    return getLastId(request, controllerId, null);
1342  }
1343
1344  /**
1345   * Get the id of the last transaction logged in the recovery log.
1346   *
1347   * @param controllerId the controller ID which determines the search space for
1348   * transaction IDs
1349   * @return the last transaction id.
1350   * @throws SQLException if an error occured while retrieving the id.
1351   */

1352  public long getLastTransactionId(long controllerId) throws SQLException JavaDoc
1353  {
1354    String JavaDoc request = "select max(transaction_id) from " + logTableName
1355        + " where (transaction_id > ?) and (transaction_id <= ?)";
1356    return getLastId(request, controllerId, null);
1357  }
1358
1359  /**
1360   * Get the id of the last transaction logged in the recovery log.
1361   *
1362   * @param controllerId the controller ID which determines the search space for
1363   * transaction IDs
1364   * @return the last transaction id.
1365   * @throws SQLException if an error occured while retrieving the id.
1366   */

1367  public long getLastConnectionId(long controllerId) throws SQLException JavaDoc
1368  {
1369    String JavaDoc request = "select max(transaction_id) from " + logTableName
1370        + " where (transaction_id > ?) and (transaction_id <= ?) " + " and "
1371        + getLogTableSqlColumnName() + " like ?";
1372    return getLastId(request, controllerId, OPEN_PERSISTENT_CONNECTION);
1373  }
1374
1375  /**
1376   * Return the last id found in the log which was allocated for (by)
1377   * controllerId, if any, or [ controllerId | 0 ] if no previous id was used in
1378   * this space.<br>
1379   * Helper method used to get last transaction/request/connection Ids.
1380   *
1381   * @param controllerId the controller ID which determines the search space for
1382   * already used IDs.
1383   * @param sql optional SQL to match (null if useless)
1384   * @return the last id found in the log which was allocated for controllerId,
1385   * if any, or [ controllerId | 0 ] if no previous id was used in this
1386   * space.
1387   * @throws SQLException if an error occured while inspecting the log.
1388   */

1389  private long getLastId(String JavaDoc request, long controllerId, String JavaDoc sql)
1390      throws SQLException JavaDoc
1391  {
1392    checkIfShuttingDown();
1393
1394    PreparedStatement JavaDoc stmt = null;
1395    ResultSet JavaDoc rs = null;
1396    try
1397    {
1398      stmt = getDatabaseConnection().prepareStatement(request);
1399      long minIdForThisController = controllerId << DistributedRequestManager.CONTROLLER_ID_SHIFT_BITS;
1400      long maxIdForThisController = minIdForThisController
1401          | ~DistributedRequestManager.CONTROLLER_ID_BIT_MASK;
1402      stmt.setLong(1, minIdForThisController);
1403      stmt.setLong(2, maxIdForThisController);
1404      if (sql != null)
1405        stmt.setString(3, sql);
1406      rs = stmt.executeQuery();
1407      if (rs.next())
1408        return rs.getLong(1);
1409      else
1410        // Table is empty: return [ controllerId | 0 ]
1411
return minIdForThisController;
1412    }
1413    catch (SQLException JavaDoc e)
1414    {
1415      invalidateInternalConnection();
1416      throw e;
1417    }
1418    finally
1419    {
1420      try
1421      {
1422        if (rs != null)
1423          rs.close();
1424      }
1425      catch (Exception JavaDoc ignore)
1426      {
1427      }
1428      try
1429      {
1430        if (stmt != null)
1431          stmt.close();
1432      }
1433      catch (Exception JavaDoc ignore)
1434      {
1435      }
1436    }
1437  }
1438
1439  /**
1440   * Returns the recoveringNb value.
1441   *
1442   * @return Returns the recoveringNb.
1443   */

1444  public long getRecoveringNb()
1445  {
1446    return recoveringNb;
1447  }
1448
1449  /**
1450   * Number of queries that can be accumulated into a batch when recovering
1451   *
1452   * @return the recovery batch size
1453   */

1454  public int getRecoveryBatchSize()
1455  {
1456    return recoveryBatchSize;
1457  }
1458
1459  /**
1460   * Returns true if the recovery log has logged a Begin for the given
1461   * transaction id.
1462   *
1463   * @param tid the transaction identifier
1464   * @return true if begin has been logged for this transaction
1465   * @throws SQLException if the query fails on the recovery database
1466   */

1467  public boolean hasLoggedBeginForTransaction(Long JavaDoc tid) throws SQLException JavaDoc
1468  {
1469    // Check for something in the queue first
1470
if (loggerThread.hasLogEntryForTransaction(tid.longValue()))
1471      return true;
1472
1473    // Check in the database
1474
PreparedStatement JavaDoc pstmt = null;
1475    ResultSet JavaDoc rs = null;
1476    try
1477    {
1478      pstmt = getDatabaseConnection().prepareStatement(
1479          "select count(*) from " + logTableName + " where transaction_id=?");
1480      // If something was logged for this transaction then begin was logged
1481
// first so if the ResultSet is not empty the begin has been logged.
1482
pstmt.setLong(1, tid.longValue());
1483      rs = pstmt.executeQuery();
1484      return rs.next();
1485    }
1486    catch (SQLException JavaDoc e)
1487    {
1488      invalidateInternalConnection();
1489      throw e;
1490    }
1491    finally
1492    {
1493      try
1494      {
1495        if (rs != null)
1496          rs.close();
1497      }
1498      catch (Exception JavaDoc ignore)
1499      {
1500      }
1501      try
1502      {
1503        if (pstmt != null)
1504          pstmt.close();
1505      }
1506      catch (Exception JavaDoc ignore)
1507      {
1508      }
1509    }
1510  }
1511
1512  /**
1513   * Returns <code>true</code> if at least one backend has started a recover
1514   * process.
1515   *
1516   * @return <code>boolean</code>
1517   */

1518  public synchronized boolean isRecovering()
1519  {
1520    return recoveringNb > 0;
1521  }
1522
1523  /**
1524   * Get the next log entry from the recovery log given the id of the previous
1525   * log entry.
1526   *
1527   * @param previousLogEntryId previous log entry identifier
1528   * @return the next log entry from the recovery log or null if no further
1529   * entry can be found
1530   * @throws SQLException if an error occurs while accesing the recovery log
1531   */

1532  public LogEntry getNextLogEntry(long previousLogEntryId) throws SQLException JavaDoc
1533  {
1534    checkIfShuttingDown();
1535
1536    ResultSet JavaDoc rs = null;
1537    boolean emptyResult;
1538    PreparedStatement JavaDoc stmt = null;
1539    try
1540    {
1541      stmt = getDatabaseConnection().prepareStatement(
1542          "SELECT * FROM " + logTableName + " WHERE log_id=?");
1543      // Note that the statement is closed in the finally block
1544
do
1545      {
1546        previousLogEntryId++;
1547        stmt.setLong(1, previousLogEntryId);
1548        // Close ResultSet of previous loop step to free resources.
1549
if (rs != null)
1550          rs.close();
1551        rs = stmt.executeQuery();
1552        emptyResult = !rs.next();
1553      }
1554      while (emptyResult && (previousLogEntryId <= logTableId));
1555
1556      // No more request after this one
1557
if (emptyResult)
1558        return null;
1559
1560      // Read columns in order to prevent issues with MS SQL Server as reported
1561
// Charles Cordingley.
1562
long id = rs.getLong(COLUMN_INDEX_LOG_ID);
1563      String JavaDoc user = rs.getString(COLUMN_INDEX_VLOGIN);
1564      String JavaDoc sql = rs.getString(COLUMN_INDEX_SQL);
1565      String JavaDoc sqlParams = rs.getString(COLUMN_INDEX_SQL_PARAMS);
1566      String JavaDoc autoConnTran = rs.getString(COLUMN_INDEX_AUTO_CONN_TRAN);
1567      long transactionId = rs.getLong(COLUMN_INDEX_TRANSACTION_ID);
1568      long requestId = rs.getLong(COLUMN_INDEX_REQUEST_ID);
1569      long execTime = rs.getLong(COLUMN_INDEX_EXEC_TIME);
1570      int updateCount = rs.getInt(COLUMN_INDEX_UPDATE_COUNT);
1571      String JavaDoc status = rs.getString(COLUMN_INDEX_EXEC_STATUS);
1572      long completionLogId = rs.getLong(COLUMN_INDEX_COMPLETION_LOG_ID);
1573      // Note that booleanProcessing = true is the default value in
1574
// AbstractRequest
1575
return new LogEntry(id, user, sql, sqlParams, autoConnTran,
1576          transactionId, false, requestId, execTime, updateCount, status,
1577          completionLogId);
1578    }
1579    catch (SQLException JavaDoc e)
1580    {
1581      invalidateInternalConnection();
1582      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.recover.failed", e));
1583    }
1584    finally
1585    {
1586      try
1587      {
1588        if (rs != null)
1589          rs.close();
1590      }
1591      catch (Exception JavaDoc ignore)
1592      {
1593      }
1594      try
1595      {
1596        if (stmt != null)
1597          stmt.close();
1598      }
1599      catch (Exception JavaDoc ignore)
1600      {
1601      }
1602    }
1603  }
1604
1605  /**
1606   * Return the real number of log entries between 2 log ids (usually matching
1607   * checkpoint indices). The SELECT includes both boundaries. The value is
1608   * retrieved through an event posted in the logger thread queue (makes sure
1609   * that all previous entries have been flushed to the log).
1610   *
1611   * @param lowerLogId the lower log id
1612   * @param upperLogId the upper log id
1613   * @return the number of entries between the 2 ids
1614   * @throws SQLException if an error occurs querying the recovery log
1615   */

1616  public long getNumberOfLogEntries(long lowerLogId, long upperLogId)
1617      throws SQLException JavaDoc
1618  {
1619    checkIfShuttingDown();
1620
1621    GetNumberOfLogEntriesEvent event = new GetNumberOfLogEntriesEvent(
1622        lowerLogId, upperLogId);
1623    postAndWaitFor(event);
1624    return event.getNbOfLogEntries();
1625  }
1626
1627  /**
1628   * Returns the number of log entries currently in the log table. The method is
1629   * not accurate since this number can change very quickly but it is correct
1630   * enough for management purposes.
1631   *
1632   * @return the number of log entries currently in the log table
1633   * @throws SQLException if an error occurs while counting the number of log
1634   * entries in the log table
1635   */

1636  long getNumberOfLogEntries() throws SQLException JavaDoc
1637  {
1638    Statement JavaDoc stmt;
1639    stmt = getDatabaseConnection().createStatement();
1640    ResultSet JavaDoc rs = null;
1641    try
1642    {
1643      rs = stmt.executeQuery("select count(*) from " + logTableName);
1644      rs.next();
1645      return rs.getLong(1);
1646    }
1647    finally
1648    {
1649      if (rs != null)
1650      {
1651        rs.close();
1652      }
1653      if (stmt != null)
1654      {
1655        stmt.close();
1656      }
1657    }
1658  }
1659
1660  /**
1661   * Get the update count result for the execution of the query that has the
1662   * provided unique id.
1663   *
1664   * @param requestId request result to look for
1665   * @return update count or -1 if not found
1666   * @throws SQLException if an error occured
1667   */

1668  public int getUpdateCountResultForQuery(long requestId) throws SQLException JavaDoc
1669  {
1670    checkIfShuttingDown();
1671
1672    GetUpdateCountEvent event = new GetUpdateCountEvent(
1673        getDatabaseConnection(), this, requestId);
1674    postAndWaitFor(event);
1675    if (event.getCatchedException() instanceof SQLException JavaDoc)
1676      throw (SQLException JavaDoc) event.getCatchedException();
1677
1678    // Result might not have been found (NoResultAvailableException thrown) in
1679
// which case we just return the default update count (-1) that will cause
1680
// the driver to re-execute the query. If result was found,
1681
// event.getUpdateCount() contains the right value.
1682
return event.getUpdateCount();
1683  }
1684
1685  /**
1686   * Tries to find a commit for a given transaction ID.
1687   *
1688   * @param transactionId the transaction id to look for
1689   * @return true if commit was found in recovery log, false otherwise
1690   * @throws SQLException if there was a problem while searching the recovery
1691   * log
1692   */

1693  public boolean findCommitForTransaction(long transactionId)
1694      throws SQLException JavaDoc
1695  {
1696    checkIfShuttingDown();
1697
1698    FindCommitEvent event = new FindCommitEvent(getDatabaseConnection(), this,
1699        transactionId);
1700    postAndWaitFor(event);
1701    if (event.getCatchedException() != null)
1702      throw event.getCatchedException();
1703    return event.wasFound();
1704  }
1705
1706  /**
1707   * Tries to find a commit for a given transaction ID.
1708   *
1709   * @param transactionId the transaction id to look for
1710   * @return commit status if found in recovery log, "" otherwise
1711   * @throws SQLException if there was a problem while searching the recovery
1712   * log
1713   */

1714  public String JavaDoc getCommitStatusForTransaction(long transactionId)
1715      throws SQLException JavaDoc
1716  {
1717    checkIfShuttingDown();
1718
1719    FindCommitEvent event = new FindCommitEvent(getDatabaseConnection(), this,
1720        transactionId);
1721    postAndWaitFor(event);
1722    if (event.getCatchedException() != null)
1723      throw event.getCatchedException();
1724    return event.getStatus();
1725  }
1726
1727  /**
1728   * Tries to find a rollback for a given transaction ID.
1729   *
1730   * @param transactionId the transaction id to look for
1731   * @return true if rollback was found in recovery log, false otherwise
1732   * @throws SQLException if there was a problem while searching the recovery
1733   * log
1734   */

1735  public boolean findRollbackForTransaction(long transactionId)
1736      throws SQLException JavaDoc
1737  {
1738    checkIfShuttingDown();
1739
1740    FindRollbackEvent event = new FindRollbackEvent(getDatabaseConnection(),
1741        this, transactionId);
1742    postAndWaitFor(event);
1743    if (event.getCatchedException() != null)
1744      throw event.getCatchedException();
1745    return event.wasFound();
1746  }
1747
1748  /**
1749   * Tries to find a rollback for a given transaction ID and returns its status
1750   * if found.
1751   *
1752   * @param transactionId the transaction id to look for
1753   * @return commit status if found in recovery log, "" otherwise
1754   * @throws SQLException if there was a problem while searching the recovery
1755   * log
1756   */

1757  public String JavaDoc getRollbackStatusForTransaction(long transactionId)
1758      throws SQLException JavaDoc
1759  {
1760    checkIfShuttingDown();
1761
1762    FindRollbackEvent event = new FindRollbackEvent(getDatabaseConnection(),
1763        this, transactionId);
1764    postAndWaitFor(event);
1765    if (event.getCatchedException() != null)
1766      throw event.getCatchedException();
1767    return event.getStatus();
1768  }
1769
1770  /**
1771   * Get the next request (begin/commit/rollback or WriteRequest) from the
1772   * recovery log given the id of the previously recovered request.
1773   * <p>
1774   * The id of the request before the first one to recover is given by
1775   * getCheckpointRequestId.
1776   *
1777   * @param previousRequestId id of the previously recovered request
1778   * @param scheduler Scheduler that will be used to generate fake TransactionId
1779   * when recovering requests in autocommit mode
1780   * @return AbstractTask task corresponding to the next request to recover or
1781   * null if no such request exists
1782   * @exception SQLException if an error occurs
1783   * @see #getCheckpointLogId(String)
1784   */

1785  public RecoveryTask recoverNextRequest(long previousRequestId,
1786      AbstractScheduler scheduler) throws SQLException JavaDoc
1787  {
1788    RecoveryTask task = null;
1789
1790    // Get the request with the id after previousRequestId.
1791
LogEntry logEntry = getNextLogEntry(previousRequestId);
1792    if (logEntry == null)
1793      return null;
1794
1795    // Construct the request object according to its type
1796
long transactionId = logEntry.getTid();
1797    long id = logEntry.getLogId();
1798    String JavaDoc user = logEntry.getLogin();
1799    String JavaDoc sql = logEntry.getQuery().trim();
1800    String JavaDoc status = logEntry.getExecutionStatus();
1801    long completionLogId = logEntry.getCompletionLogId();
1802
1803    boolean escapeProcessing = true;
1804
1805    if (CLOSE_PERSISTENT_CONNECTION.equals(sql))
1806    {
1807      if (logger.isDebugEnabled())
1808        logger.debug("closing persistent connection: " + transactionId);
1809      task = new RecoveryTask(transactionId, id,
1810          new ClosePersistentConnectionTask(1, 1, user, transactionId), status);
1811    }
1812    else if (OPEN_PERSISTENT_CONNECTION.equals(sql))
1813    {
1814      if (logger.isDebugEnabled())
1815        logger.debug("opening persistent connection: " + transactionId);
1816      task = new RecoveryTask(transactionId, id,
1817          new OpenPersistentConnectionTask(1, 1, user, transactionId), status);
1818    }
1819    else if (BEGIN.equals(sql))
1820    {
1821      // DO NOT SWITCH THIS BLOCK WITH THE NEXT if BLOCK
1822
// begin on a non-persistent connection
1823
task = new RecoveryTask(transactionId, id, new BeginTask(1, 1,
1824          new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1825              false, 0)), status);
1826      if (logger.isDebugEnabled())
1827        logger.debug("begin transaction: " + transactionId);
1828    }
1829    else if (sql.startsWith(BEGIN))
1830    {
1831      // DO NOT SWITCH THIS BLOCK WITH THE PREVIOUS if BLOCK
1832
// begin on a persistent connection, extract the persistent connection id
1833
long persistentConnectionId = Long.parseLong(sql
1834          .substring(BEGIN.length()).trim());
1835      task = new RecoveryTask(transactionId, id, new BeginTask(1, 1,
1836          new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1837              true, persistentConnectionId)), status);
1838      if (logger.isDebugEnabled())
1839        logger.debug("begin transaction: " + transactionId
1840            + " on persistent connection " + persistentConnectionId);
1841    }
1842    else if (COMMIT.equals(sql))
1843    { // commit (we do not care about the persistent connection id here)
1844
task = new RecoveryTask(transactionId, id, new CommitTask(1, 1,
1845          new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1846              false, 0)), status);
1847      if (logger.isDebugEnabled())
1848        logger.debug("commit transaction: " + transactionId);
1849    }
1850    else if (ROLLBACK.equals(sql))
1851    { // rollback (we do not care about the persistent connection id here)
1852
int index = sql.indexOf(' ');
1853      if (index == -1)
1854      {
1855        task = new RecoveryTask(transactionId, id, new RollbackTask(1, 1,
1856            new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1857                false, 0)), status);
1858        if (logger.isDebugEnabled())
1859          logger.debug("rollback transaction: " + transactionId);
1860      }
1861      else
1862      { // Rollback to savepoint
1863
String JavaDoc savepointName = sql.substring(index).trim();
1864        task = new RecoveryTask(transactionId, id, new RollbackToSavepointTask(
1865            1, 1, new TransactionMetaData(transactionId, (long) timeout * 1000,
1866                user, false, 0), savepointName), status);
1867        if (logger.isDebugEnabled())
1868          logger.debug("rollback transaction to savepoint: " + transactionId);
1869      }
1870    }
1871    else if (sql.startsWith("savepoint "))
1872    { // set savepoint (we do not care about the persistent connection id here)
1873
String JavaDoc savepointName = sql.substring(sql.indexOf(' ')).trim();
1874      task = new RecoveryTask(transactionId, id, new SavepointTask(1, 1,
1875          new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1876              false, 0), savepointName), status);
1877      if (logger.isDebugEnabled())
1878        logger.debug("transaction set savepoint: " + transactionId);
1879    }
1880    else if (sql.startsWith("release "))
1881    { // release savepoint (we do not care about the persistent connection id
1882
// here)
1883
String JavaDoc savepointName = sql.substring(sql.indexOf(' '));
1884      task = new RecoveryTask(transactionId, id, new ReleaseSavepointTask(1, 1,
1885          new TransactionMetaData(transactionId, (long) timeout * 1000, user,
1886              false, 0), savepointName), status);
1887      if (logger.isDebugEnabled())
1888        logger.debug("transaction release savepoint: " + transactionId);
1889    }
1890    else
1891    {
1892      // Use regular expressions to determine object to rebuild
1893
AbstractRequest decodedRequest = requestFactory.requestFromString(sql,
1894          false, escapeProcessing, timeout, "\n");
1895      if (decodedRequest != null)
1896      {
1897        setRequestParameters(logEntry, decodedRequest, scheduler);
1898        if (logger.isDebugEnabled())
1899          logger.debug("recovering " + decodedRequest.getType());
1900
1901        if (decodedRequest instanceof AbstractWriteRequest)
1902        {
1903          task = new RecoveryTask(transactionId, id,
1904              new StatementExecuteUpdateTask(1, 1,
1905                  (AbstractWriteRequest) decodedRequest), status);
1906        }
1907        else if (decodedRequest instanceof StoredProcedure)
1908        {
1909          task = new RecoveryTask(transactionId, id,
1910              new CallableStatementExecuteTask(1, 1,
1911                  (StoredProcedure) decodedRequest, null), status);
1912        }
1913        else
1914        {
1915          if (decodedRequest instanceof UnknownWriteRequest)
1916          {
1917            task = new RecoveryTask(transactionId, id,
1918                new StatementExecuteTask(1, 1,
1919                    (AbstractWriteRequest) decodedRequest, null), status);
1920          }
1921          else
1922          // read request
1923
{
1924            if (decodedRequest instanceof SelectRequest)
1925            {
1926              // Set a fake cursor name in all cases (addresses SEQUOIA-396)
1927
decodedRequest.setCursorName("replay_cursor");
1928            }
1929            /*
1930             * For unknown read requests, we do not care about "select for
1931             * update" pattern here since no broadcast will occur anyway
1932             */

1933            task = new RecoveryTask(transactionId, id,
1934                new StatementExecuteQueryTask(1, 1,
1935                    (SelectRequest) decodedRequest, null), status);
1936          }
1937        }
1938      }
1939    }
1940
1941    task.setCompletionLogId(completionLogId);
1942    return task;
1943  }
1944
1945  private void setRequestParameters(LogEntry entry,
1946      AbstractRequest decodedRequest, AbstractScheduler scheduler)
1947  {
1948    decodedRequest.setLogin(entry.getLogin());
1949    decodedRequest.setSqlOrTemplate(entry.getQuery());
1950    decodedRequest.setPreparedStatementParameters(entry.getQueryParams());
1951    if (LogEntry.TRANSACTION.equals(entry.getAutoConnTrans()))
1952    {
1953      decodedRequest.setIsAutoCommit(false);
1954      decodedRequest.setTransactionId(entry.getTid());
1955      decodedRequest
1956          .setTransactionIsolation(org.continuent.sequoia.driver.Connection.DEFAULT_TRANSACTION_ISOLATION_LEVEL);
1957    }
1958    else
1959    {
1960      decodedRequest.setIsAutoCommit(true);
1961      decodedRequest.setTransactionId(scheduler.getNextTransactionId());
1962      if (LogEntry.PERSISTENT_CONNECTION.equals(entry.getAutoConnTrans()))
1963      {
1964        decodedRequest.setPersistentConnection(true);
1965        decodedRequest.setPersistentConnectionId(entry.getTid());
1966      }
1967    }
1968  }
1969
1970  //
1971
//
1972
// Checkpoint Management
1973
//
1974
//
1975

1976  /**
1977   * Deletes recovery log entries that are older than specified checkpoint.
1978   * Entries are deleted directly into the recovery log database, as opposed to
1979   * via a post to the logger thread (as resetLogTableIdAndDeleteRecoveryLog).
1980   *
1981   * @param checkpointName the name of the checkpoint uptil which log entries
1982   * should be removed
1983   * @throws SQLException in case of error.
1984   */

1985  public void deleteLogEntriesBeforeCheckpoint(String JavaDoc checkpointName)
1986      throws SQLException JavaDoc
1987  {
1988    checkIfShuttingDown();
1989
1990    long id = getCheckpointLogId(checkpointName);
1991    PreparedStatement JavaDoc stmt = null;
1992    try
1993    {
1994      stmt = getDatabaseConnection().prepareStatement(
1995          "DELETE FROM " + getLogTableName() + " WHERE log_id<=?");
1996      stmt.setLong(1, id);
1997      stmt.executeUpdate();
1998    }
1999    catch (SQLException JavaDoc e)
2000    {
2001      throw new SQLException JavaDoc(Translate.get(
2002          "recovery.jdbc.entries.remove.failed", e.getMessage()));
2003    }
2004    finally
2005    {
2006      try
2007      {
2008        if (stmt != null)
2009          stmt.close();
2010      }
2011      catch (Exception JavaDoc ignore)
2012      {
2013      }
2014    }
2015  }
2016
2017  /**
2018   * Returns a time-ordered odered (most recent first) array of names of all the
2019   * checkpoint available in the recovery log. <strong>this method may not
2020   * return the exact list of checkpoint names (since it is called concurrently
2021   * to events posted to the recovery log queue).</strong>
2022   *
2023   * @return a time-ordered odered (most recent first) <code>ArrayList</code>
2024   * of <code>String</code> checkpoint names
2025   * @throws SQLException if fails
2026   * @deprecated use getCheckpoint().values() instead
2027   */

2028  public ArrayList JavaDoc getCheckpointNames() throws SQLException JavaDoc
2029  {
2030    checkIfShuttingDown();
2031
2032    PreparedStatement JavaDoc stmt = null;
2033
2034    try
2035    {
2036      // Ordering is not quite newest first if two checkpoints happen to have
2037
// the same log_id. We sort by name to make improper ordering less
2038
// likely. TODO: Add timestamp or reformat checkpoint names so that they
2039
// sort by date.
2040
if (logger.isDebugEnabled())
2041        logger.debug("Retrieving checkpoint names list");
2042      stmt = getDatabaseConnection().prepareStatement(
2043          "SELECT name from " + checkpointTableName
2044              + " ORDER BY log_id DESC, name DESC");
2045      ResultSet JavaDoc rs = stmt.executeQuery();
2046      ArrayList JavaDoc list = new ArrayList JavaDoc();
2047      while (rs.next())
2048      {
2049        list.add(rs.getString(1));
2050      }
2051      rs.close();
2052      return list;
2053    }
2054    catch (Exception JavaDoc e)
2055    {
2056      invalidateInternalConnection();
2057      throw new SQLException JavaDoc(Translate.get(
2058          "recovery.jdbc.checkpoint.list.failed", e));
2059    }
2060    finally
2061    {
2062      try
2063      {
2064        if (stmt != null)
2065          stmt.close();
2066      }
2067      catch (SQLException JavaDoc ignore)
2068      {
2069      }
2070    }
2071  }
2072
2073  /**
2074   * Returns a <code>Map&lt;String, String&gt;</code> of checkpoints where the
2075   * keys are the checkpoint names and the values are the corresponding log IDs.
2076   * The Map is orderered by log IDs (newest first).
2077   *
2078   * @return a <code>Map&lt;String, String&gt;</code> of checkpoints (key=
2079   * checkpoint name, value = log id)
2080   * @throws SQLException if an error occurs while retrieving the checkpoints
2081   * information from the checkpoint table
2082   */

2083  Map JavaDoc/* <String, String> */getCheckpoints() throws SQLException JavaDoc
2084  {
2085    checkIfShuttingDown();
2086
2087    PreparedStatement JavaDoc stmt = null;
2088
2089    try
2090    {
2091      if (logger.isDebugEnabled())
2092        logger.debug("Retrieving checkpoint names list");
2093      stmt = getDatabaseConnection().prepareStatement(
2094          "SELECT log_id, name from " + checkpointTableName
2095              + " ORDER BY log_id DESC");
2096      ResultSet JavaDoc rs = stmt.executeQuery();
2097      Map JavaDoc checkpoints = new TreeMap JavaDoc();
2098      while (rs.next())
2099      {
2100        String JavaDoc name = rs.getString("name");
2101        String JavaDoc id = rs.getString("log_id");
2102        checkpoints.put(name, id);
2103      }
2104      rs.close();
2105      return checkpoints;
2106    }
2107    catch (Exception JavaDoc e)
2108    {
2109      invalidateInternalConnection();
2110      throw new SQLException JavaDoc(Translate.get(
2111          "recovery.jdbc.checkpoint.list.failed", e));
2112    }
2113    finally
2114    {
2115      try
2116      {
2117        if (stmt != null)
2118          stmt.close();
2119      }
2120      catch (SQLException JavaDoc ignore)
2121      {
2122      }
2123    }
2124  }
2125
2126  /**
2127   * Returns a list of checkpoint names (strings) that all represent the same
2128   * point in time (same log id) as specified 'checkpointName'. The list is
2129   * never null and includes the specified 'checkpointName'.
2130   *
2131   * @param checkpointName the checkpoint name
2132   * @return a list of checkpoint names (strings) that all represent the same
2133   * point in time (same log id)
2134   * @throws SQLException if no such checkpoint exists of if there is an error
2135   */

2136  public String JavaDoc[] getCheckpointNameAliases(String JavaDoc checkpointName)
2137      throws SQLException JavaDoc
2138  {
2139    long logId = getCheckpointLogId(checkpointName);
2140    Object JavaDoc ret = executeQuery("SELECT name from " + checkpointTableName
2141        + " where log_id=" + logId + " ORDER BY log_id DESC",
2142        new QueryHandler()
2143        {
2144          public Object JavaDoc run(ResultSet JavaDoc rs) throws SQLException JavaDoc
2145          {
2146            ArrayList JavaDoc aliases = new ArrayList JavaDoc();
2147            while (rs.next())
2148              aliases.add(rs.getString("name"));
2149            return aliases.toArray(new String JavaDoc[aliases.size()]);
2150          }
2151        });
2152    return (String JavaDoc[]) ret;
2153  }
2154
2155  abstract class QueryHandler
2156  {
2157    /**
2158     * To be defined by query handler. This is supposed to contain the
2159     * processing of the result set that is returned by the query execution.
2160     *
2161     * @param rs the ResultSet returned by the query execution
2162     * @return an object containing the result of the processing of the handler.
2163     * @throws SQLException
2164     */

2165    abstract public Object JavaDoc run(ResultSet JavaDoc rs) throws SQLException JavaDoc;
2166  };
2167
2168  Object JavaDoc executeQuery(String JavaDoc query, QueryHandler handler) throws SQLException JavaDoc
2169  {
2170    checkIfShuttingDown();
2171
2172    PreparedStatement JavaDoc stmt = null;
2173    try
2174    {
2175      stmt = getDatabaseConnection().prepareStatement(query);
2176      ResultSet JavaDoc rs = stmt.executeQuery();
2177      return handler.run(rs);
2178    }
2179    catch (Exception JavaDoc e)
2180    {
2181      invalidateInternalConnection();
2182      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.query.failed", e));
2183    }
2184    finally
2185    {
2186      try
2187      {
2188        if (stmt != null)
2189          stmt.close(); // this also closes the resultSet
2190
}
2191      catch (SQLException JavaDoc ignore)
2192      {
2193      }
2194    }
2195  }
2196
2197  /**
2198   * Get the log id corresponding to a given checkpoint. This is the first step
2199   * in a recovery process. Following steps consist in calling
2200   * recoverNextRequest.
2201   *
2202   * @param checkpointName Name of the checkpoint
2203   * @return long the log identifier corresponding to this checkpoint.
2204   * @exception SQLException if an error occurs or the checkpoint does not exist
2205   * @see #recoverNextRequest(long, AbstractScheduler)
2206   */

2207  public long getCheckpointLogId(String JavaDoc checkpointName) throws SQLException JavaDoc
2208  {
2209    checkIfShuttingDown();
2210
2211    GetCheckpointLogIdEvent event = new GetCheckpointLogIdEvent(
2212        getDatabaseConnection(), getCheckpointTableName(), checkpointName);
2213    postAndWaitFor(event);
2214    if (event.getCatchedException() != null)
2215      throw event.getCatchedException();
2216    return event.getCheckpointLogId();
2217  }
2218
2219  /**
2220   * Get the log entry corresponding to a given checkpoint. This is used to set
2221   * a checkpoint globally cluster-wide using the unique log entry.
2222   *
2223   * @param checkpointName Name of the checkpoint
2224   * @return a CheckpointLogEntry corresponding to this checkpoint.
2225   * @exception SQLException if an error occurs or the checkpoint does not exist
2226   * @see #recoverNextRequest(long, AbstractScheduler)
2227   */

2228  public CheckpointLogEntry getCheckpointLogEntry(String JavaDoc checkpointName)
2229      throws SQLException JavaDoc
2230  {
2231    checkIfShuttingDown();
2232
2233    GetCheckpointLogEntryEvent event = new GetCheckpointLogEntryEvent(
2234        getDatabaseConnection(), this, checkpointName);
2235    postAndWaitFor(event);
2236    if (event.getCatchedException() != null)
2237      throw event.getCatchedException();
2238    return event.getCheckpointLogEntry();
2239  }
2240
2241  /**
2242   * Shift the log entries from the given checkpoint id by the shift factor
2243   * provided. All entries with an id below nowCheckpointId and all the others
2244   * will have their id increased by shift.
2245   *
2246   * @param fromId id where to start the move
2247   * @param shift increment to add to log entries id
2248   */

2249  public void moveEntries(long fromId, long shift)
2250  {
2251    synchronized (this)
2252    {
2253      // Move current entries in the database log
2254
loggerThread.log(new ShiftLogEntriesEvent(fromId, shift));
2255      // Move current id forward so that next entries do not have to be
2256
// relocated
2257
logTableId = logTableId + shift;
2258    }
2259  }
2260
2261  /**
2262   * Delete the log entries from the fromId id to toId.
2263   *
2264   * @param fromId id where to start the move
2265   * @param toId increment to add to log entries id
2266   */

2267  public void deleteLogEntriesAndCheckpointBetween(long fromId, long toId)
2268  {
2269    synchronized (this)
2270    {
2271      // Delete entries in the database log
2272
loggerThread.log(new DeleteLogEntriesAndCheckpointBetweenEvent(fromId,
2273          toId));
2274    }
2275  }
2276
2277  /**
2278   * Remove a checkpoint from the recovery. This is useful for recovery
2279   * maintenance
2280   *
2281   * @param checkpointName to remove
2282   */

2283  public void removeCheckpoint(String JavaDoc checkpointName)
2284  {
2285    RemoveCheckpointEvent removeCheckpointEvent = new RemoveCheckpointEvent(
2286        checkpointName);
2287    synchronized (removeCheckpointEvent)
2288    {
2289      loggerThread.log(removeCheckpointEvent);
2290      try
2291      {
2292        removeCheckpointEvent.wait();
2293      }
2294      catch (InterruptedException JavaDoc ignore)
2295      {
2296      }
2297    }
2298  }
2299
2300  /**
2301   * Store the state of the backend in the recovery log
2302   *
2303   * @param databaseName the virtual database name
2304   * @param backendRecoveryInfo the backend recovery information to store
2305   * @throws SQLException if cannot proceed
2306   */

2307  public void storeBackendRecoveryInfo(String JavaDoc databaseName,
2308      BackendRecoveryInfo backendRecoveryInfo) throws SQLException JavaDoc
2309  {
2310    checkIfShuttingDown();
2311
2312    PreparedStatement JavaDoc stmt = null;
2313    PreparedStatement JavaDoc stmt2 = null;
2314    if ((backendRecoveryInfo.getCheckpoint() == null)
2315        || ((backendRecoveryInfo.getBackendState() != BackendState.DISABLED) && (backendRecoveryInfo
2316            .getBackendState() != BackendState.UNKNOWN)))
2317      backendRecoveryInfo.setCheckpoint(""); // No checkpoint
2318
else
2319    { // Check checkpoint name validity
2320
getCheckpointLogId(backendRecoveryInfo.getCheckpoint());
2321    }
2322
2323    try
2324    {
2325      // 1. Get the reference point to delete
2326
stmt = getDatabaseConnection().prepareStatement(
2327          "SELECT * FROM " + backendTableName
2328              + " WHERE backend_name LIKE ? and database_name LIKE ?");
2329      stmt.setString(1, backendRecoveryInfo.getBackendName());
2330      stmt.setString(2, databaseName);
2331      ResultSet JavaDoc rs = stmt.executeQuery();
2332      boolean mustUpdate = rs.next();
2333      rs.close();
2334      if (!mustUpdate)
2335      {
2336        stmt2 = getDatabaseConnection().prepareStatement(
2337            "INSERT INTO " + backendTableName + " values(?,?,?,?)");
2338        stmt2.setString(1, databaseName);
2339        stmt2.setString(2, backendRecoveryInfo.getBackendName());
2340        stmt2.setInt(3, backendRecoveryInfo.getBackendState());
2341        stmt2.setString(4, backendRecoveryInfo.getCheckpoint());
2342        if (stmt2.executeUpdate() != 1)
2343          throw new SQLException JavaDoc(
2344              "Error while inserting new backend reference. Incorrect number of rows");
2345      }
2346      else
2347      {
2348        stmt2 = getDatabaseConnection()
2349            .prepareStatement(
2350                "UPDATE "
2351                    + backendTableName
2352                    + " set backend_state=?,checkpoint_name=? where backend_name=? and database_name=?");
2353        stmt2.setInt(1, backendRecoveryInfo.getBackendState());
2354        stmt2.setString(2, backendRecoveryInfo.getCheckpoint());
2355        stmt2.setString(3, backendRecoveryInfo.getBackendName());
2356        stmt2.setString(4, databaseName);
2357        if (stmt2.executeUpdate() != 1)
2358          throw new SQLException JavaDoc(
2359              "Error while updating backend reference. Incorrect number of rows");
2360      }
2361    }
2362    catch (SQLException JavaDoc e)
2363    {
2364      invalidateInternalConnection();
2365
2366      logger.warn("Failed to store backend recovery info", e);
2367
2368      throw new SQLException JavaDoc("Unable to update checkpoint '"
2369          + backendRecoveryInfo.getCheckpoint() + "' for backend:"
2370          + backendRecoveryInfo.getBackendName());
2371    }
2372    finally
2373    {
2374      try
2375      {
2376        if (stmt != null)
2377          stmt.close();
2378      }
2379      catch (Exception JavaDoc ignore)
2380      {
2381      }
2382      try
2383      {
2384        if (stmt2 != null)
2385          stmt2.close();
2386      }
2387      catch (Exception JavaDoc ignore)
2388      {
2389      }
2390    }
2391  }
2392
2393  /**
2394   * Store a Checkpoint using the current log state.
2395   *
2396   * @param checkpointName Name of the checkpoint
2397   * @exception SQLException if an error occurs
2398   */

2399  public void storeCheckpoint(String JavaDoc checkpointName) throws SQLException JavaDoc
2400  {
2401    // Check if a checkpoint with the name checkpointName already exists
2402
if (!validCheckpointName(checkpointName))
2403    {
2404      throw new SQLException JavaDoc(Translate.get(
2405          "recovery.jdbc.checkpoint.duplicate", checkpointName));
2406    }
2407
2408    loggerThread.log(new StoreCheckpointWithLogIdEvent(checkpointName,
2409        logTableId));
2410  }
2411
2412  /**
2413   * Store a Checkpoint with the given log id.
2414   *
2415   * @param checkpointName Name of the checkpoint
2416   * @param logId log id this checkpoint points to
2417   * @exception SQLException if an error occurs
2418   */

2419  public void storeCheckpoint(String JavaDoc checkpointName, long logId)
2420      throws SQLException JavaDoc
2421  {
2422    // Check if a checkpoint with the name checkpointName already exists
2423
if (!validCheckpointName(checkpointName))
2424    {
2425      throw new SQLException JavaDoc(Translate.get(
2426          "recovery.jdbc.checkpoint.duplicate", checkpointName));
2427    }
2428
2429    loggerThread.log(new StoreCheckpointWithLogIdEvent(checkpointName, logId));
2430  }
2431
2432  //
2433
//
2434
// Dump management
2435
//
2436
//
2437

2438  /**
2439   * Get the DumpInfo element corresponding to the given dump name. Returns null
2440   * if no information is found for this dump name.
2441   *
2442   * @param dumpName the name of the dump to look for
2443   * @return a <code>DumpInfo</code> object or null if not found in the table
2444   * @throws SQLException if a recovery log database access error occurs
2445   */

2446  public DumpInfo getDumpInfo(String JavaDoc dumpName) throws SQLException JavaDoc
2447  {
2448    checkIfShuttingDown();
2449
2450    PreparedStatement JavaDoc stmt = null;
2451
2452    try
2453    {
2454      if (logger.isDebugEnabled())
2455        logger.debug("Retrieving dump " + dumpName + " information");
2456      stmt = getDatabaseConnection().prepareStatement(
2457          "SELECT * from " + dumpTableName + " WHERE dump_name LIKE ?");
2458      stmt.setString(1, dumpName);
2459
2460      ResultSet JavaDoc rs = stmt.executeQuery();
2461      DumpInfo dumpInfo = null;
2462      if (rs.next())
2463      {
2464        dumpInfo = new DumpInfo(rs.getString("dump_name"), rs
2465            .getTimestamp("dump_date"), rs.getString("dump_path"), rs
2466            .getString("dump_format"), rs.getString("checkpoint_name"), rs
2467            .getString("backend_name"), rs.getString(dumpTableTablesColumnName));
2468      }
2469      // else not found, return dumpInfo=null;
2470

2471      rs.close();
2472      return dumpInfo;
2473    }
2474    catch (Exception JavaDoc e)
2475    {
2476      invalidateInternalConnection();
2477      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.info.failed",
2478          new String JavaDoc[]{dumpName, e.getMessage()}));
2479    }
2480    finally
2481    {
2482      try
2483      {
2484        if (stmt != null)
2485          stmt.close();
2486      }
2487      catch (SQLException JavaDoc ignore)
2488      {
2489      }
2490    }
2491  }
2492
2493  /**
2494   * Retrieve the list of available dumps.
2495   *
2496   * @return an <code>ArrayList</code> of <code>DumpInfo</code> objects
2497   * @throws SQLException if a recovery log database access error occurs
2498   */

2499  public ArrayList JavaDoc getDumpList() throws SQLException JavaDoc
2500  {
2501    checkIfShuttingDown();
2502
2503    PreparedStatement JavaDoc stmt = null;
2504
2505    try
2506    {
2507      if (logger.isDebugEnabled())
2508        logger.debug("Retrieving dump list");
2509      stmt = getDatabaseConnection().prepareStatement(
2510          "SELECT * FROM " + dumpTableName + " ORDER BY dump_date DESC");
2511      ResultSet JavaDoc rs = stmt.executeQuery();
2512      ArrayList JavaDoc list = new ArrayList JavaDoc();
2513      while (rs.next())
2514      {
2515        list
2516            .add(new DumpInfo(rs.getString("dump_name"), rs
2517                .getTimestamp("dump_date"), rs.getString("dump_path"), rs
2518                .getString("dump_format"), rs.getString("checkpoint_name"), rs
2519                .getString("backend_name"), rs
2520                .getString(dumpTableTablesColumnName)));
2521      }
2522      rs.close();
2523      return list;
2524    }
2525    catch (Exception JavaDoc e)
2526    {
2527      invalidateInternalConnection();
2528      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.list.failed", e));
2529    }
2530    finally
2531    {
2532      try
2533      {
2534        if (stmt != null)
2535          stmt.close();
2536      }
2537      catch (SQLException JavaDoc ignore)
2538      {
2539      }
2540    }
2541  }
2542
2543  /**
2544   * Remove a dump information from the dump table base.
2545   *
2546   * @param dumpInfo the <code>DumpInfo</code> to remove
2547   * @throws SQLException if the dump has has not been removed from the dump
2548   * table
2549   */

2550  public void removeDump(DumpInfo dumpInfo) throws SQLException JavaDoc
2551  {
2552    checkIfShuttingDown();
2553
2554    PreparedStatement JavaDoc stmt = null;
2555
2556    try
2557    {
2558      if (logger.isDebugEnabled())
2559      {
2560        logger.debug("removing dump " + dumpInfo.getDumpName());
2561      }
2562      stmt = getDatabaseConnection().prepareStatement(
2563          "DELETE FROM " + dumpTableName + " WHERE dump_name=?");
2564      stmt.setString(1, dumpInfo.getDumpName());
2565
2566      stmt.executeUpdate();
2567    }
2568    catch (Exception JavaDoc e)
2569    {
2570      invalidateInternalConnection();
2571      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.remove.failed",
2572          new String JavaDoc[]{dumpInfo.getDumpName(), e.getMessage()}));
2573    }
2574    finally
2575    {
2576      try
2577      {
2578        if (stmt != null)
2579          stmt.close();
2580      }
2581      catch (SQLException JavaDoc ignore)
2582      {
2583      }
2584    }
2585  }
2586
2587  /**
2588   * Set DumpInfo, thereby making a new dump available for restore.
2589   *
2590   * @param dumpInfo the dump info to create.
2591   * @throws VirtualDatabaseException if an error occurs
2592   */

2593  public void setDumpInfo(DumpInfo dumpInfo) throws VirtualDatabaseException
2594  {
2595    try
2596    {
2597      storeDump(dumpInfo);
2598    }
2599    catch (SQLException JavaDoc e)
2600    {
2601      throw new VirtualDatabaseException(e);
2602    }
2603  }
2604
2605  /**
2606   * Store the given dump information in the dump table
2607   *
2608   * @param dump the <code>DumpInfo</code> to store
2609   * @throws SQLException if a recovery log database access error occurs
2610   */

2611  public void storeDump(DumpInfo dump) throws SQLException JavaDoc
2612  {
2613    checkIfShuttingDown();
2614
2615    PreparedStatement JavaDoc stmt = null;
2616
2617    if (dump == null)
2618      throw new NullPointerException JavaDoc(
2619          "Invalid null dump in JDBCRecoverylog.storeDump");
2620
2621    try
2622    {
2623      if (logger.isDebugEnabled())
2624        logger.debug("Storing dump " + dump.getDumpName());
2625      stmt = getDatabaseConnection().prepareStatement(
2626          "INSERT INTO " + dumpTableName + " VALUES (?,?,?,?,?,?,?)");
2627      stmt.setString(1, dump.getDumpName());
2628      stmt.setTimestamp(2, new Timestamp JavaDoc(dump.getDumpDate().getTime()));
2629      stmt.setString(3, dump.getDumpPath());
2630      stmt.setString(4, dump.getDumpFormat());
2631      stmt.setString(5, dump.getCheckpointName());
2632      stmt.setString(6, dump.getBackendName());
2633      stmt.setString(7, dump.getTables());
2634
2635      stmt.executeUpdate();
2636    }
2637    catch (Exception JavaDoc e)
2638    {
2639      invalidateInternalConnection();
2640      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.store.failed",
2641          new String JavaDoc[]{dump.getDumpName(), e.getMessage()}));
2642    }
2643    finally
2644    {
2645      try
2646      {
2647        if (stmt != null)
2648          stmt.close();
2649      }
2650      catch (SQLException JavaDoc ignore)
2651      {
2652      }
2653    }
2654  }
2655
2656  /**
2657   * @see StoreCheckpointWithLogIdEvent
2658   * @param dumpCheckpointName name of the checkpoint to store
2659   * @param checkpointId id of the checkpoint
2660   */

2661  public void storeDumpCheckpointName(String JavaDoc dumpCheckpointName,
2662      long checkpointId)
2663  {
2664    loggerThread.log(new StoreCheckpointWithLogIdEvent(dumpCheckpointName,
2665        checkpointId));
2666  }
2667
2668  /**
2669   * Updates specified DumpTable entry column with specified value.
2670   *
2671   * @param dumpName the name of the dump record to update
2672   * @param columnName the name of the column to update
2673   * @param value the value to set
2674   * @throws SQLException if any error occurs (invalid dump name, sql query
2675   * syntax errors, hsqldb access errors, ...)
2676   */

2677  void updateDumpTableColumn(String JavaDoc dumpName, String JavaDoc columnName, String JavaDoc value)
2678      throws SQLException JavaDoc
2679  {
2680    checkIfShuttingDown();
2681
2682    DumpInfo dumpInfo = getDumpInfo(dumpName);
2683    if (dumpInfo == null)
2684      throw new SQLException JavaDoc("No such dump name: " + dumpName);
2685
2686    PreparedStatement JavaDoc stmt = null;
2687    int updateCount = 0;
2688    try
2689    {
2690      if (logger.isDebugEnabled())
2691        logger.debug("Updating '" + columnName + "' for dump '"
2692            + dumpInfo.getDumpName() + "' to " + value);
2693      stmt = getDatabaseConnection().prepareStatement(
2694          "UPDATE " + dumpTableName + " SET " + columnName
2695              + "=? WHERE dump_name=?");
2696      stmt.setString(1, value);
2697      stmt.setString(2, dumpName);
2698      updateCount = stmt.executeUpdate();
2699    }
2700    catch (Exception JavaDoc e)
2701    {
2702      invalidateInternalConnection();
2703      throw new SQLException JavaDoc(Translate.get(
2704          "recovery.jdbc.dump.update.column.failed", new String JavaDoc[]{dumpName,
2705              e.getMessage()}));
2706    }
2707    finally
2708    {
2709      try
2710      {
2711        if (stmt != null)
2712          stmt.close();
2713      }
2714      catch (SQLException JavaDoc ignore)
2715      {
2716      }
2717    }
2718
2719    if (updateCount != 1)
2720    {
2721      String JavaDoc msg = "Invalid update count after dumpTable update.";
2722      logger.error(msg);
2723      throw new SQLException JavaDoc(msg);
2724    }
2725  }
2726
2727  /**
2728   * Rename a dump.
2729   *
2730   * @param dumpName the old dump name
2731   * @param newDumpName the new dump name to set
2732   * @throws SQLException if a recovery log database access error occurs
2733   */

2734  public void updateDumpName(String JavaDoc dumpName, String JavaDoc newDumpName)
2735      throws SQLException JavaDoc
2736  {
2737    updateDumpTableColumn(dumpName, "dump_name", newDumpName);
2738  }
2739
2740  /**
2741   * Update the path name for a given dump.
2742   *
2743   * @param dumpName the dump name
2744   * @param newPath the new path to set
2745   * @throws SQLException if a recovery log database access error occurs
2746   */

2747  public void updateDumpPath(String JavaDoc dumpName, String JavaDoc newPath)
2748      throws SQLException JavaDoc
2749  {
2750    updateDumpTableColumn(dumpName, "dump_path", newPath);
2751  }
2752
2753  /**
2754   * Update the checkpoint name for specified dump, making it available for
2755   * restore operations.
2756   *
2757   * @param dumpName the dump name
2758   * @param checkpointName the new chekpoint to set
2759   * @throws SQLException if a recovery log database access error occurs
2760   */

2761  public void updateDumpCheckpoint(String JavaDoc dumpName, String JavaDoc checkpointName)
2762      throws SQLException JavaDoc
2763  {
2764    updateDumpTableColumn(dumpName, "checkpoint_name", checkpointName);
2765  }
2766
2767  //
2768
//
2769
// Recovery log database tables management
2770
//
2771
//
2772

2773  /**
2774   * Checks if the recovery log and checkpoint tables exist, and create them if
2775   * they do not exist. This method also starts the logger thread.
2776   */

2777  public void checkRecoveryLogTables()
2778  {
2779    try
2780    {
2781      intializeDatabase();
2782    }
2783    catch (SQLException JavaDoc e)
2784    {
2785      throw new RuntimeException JavaDoc("Unable to initialize the database: " + e);
2786    }
2787
2788    // Start the logger thread
2789
loggerThread = new LoggerThread(this);
2790    loggerThread.start();
2791  }
2792
2793  /**
2794   * Returns the backendTableName value.
2795   *
2796   * @return Returns the backendTableName.
2797   */

2798  public String JavaDoc getBackendTableName()
2799  {
2800    return backendTableName;
2801  }
2802
2803  /**
2804   * Returns the checkpointTableName value.
2805   *
2806   * @return Returns the checkpointTableName.
2807   */

2808  public String JavaDoc getCheckpointTableName()
2809  {
2810    return checkpointTableName;
2811  }
2812
2813  /**
2814   * Returns the logTableName value.
2815   *
2816   * @return Returns the logTableName.
2817   */

2818  public String JavaDoc getLogTableName()
2819  {
2820    return logTableName;
2821  }
2822
2823  /**
2824   * Returns the logTableSqlColumnName value.
2825   *
2826   * @return Returns the logTableSqlColumnName.
2827   */

2828  public String JavaDoc getLogTableSqlColumnName()
2829  {
2830    return logTableSqlColumnName;
2831  }
2832
2833  /**
2834   * Sets the backend table create statement
2835   *
2836   * @param createTable statement to create the table
2837   * @param tableName the backend table name
2838   * @param checkpointNameType type for the checkpointName column
2839   * @param backendNameType type for the backendName column
2840   * @param backendStateType type for the backendState column
2841   * @param databaseNameType type for the databaseName column
2842   * @param extraStatement like primary keys
2843   */

2844  public void setBackendTableCreateStatement(String JavaDoc createTable,
2845      String JavaDoc tableName, String JavaDoc checkpointNameType, String JavaDoc backendNameType,
2846      String JavaDoc backendStateType, String JavaDoc databaseNameType, String JavaDoc extraStatement)
2847  {
2848    this.backendTableCreateTable = createTable;
2849    this.backendTableName = tableName;
2850    this.backendTableDatabaseName = databaseNameType;
2851    this.backendTableBackendName = backendNameType;
2852    this.backendTableBackendState = backendStateType;
2853    this.backendTableCheckpointName = checkpointNameType;
2854    this.backendTableExtraStatement = extraStatement;
2855    this.backendTableCreateStatement = createTable + " " + backendTableName
2856        + " (database_name " + databaseNameType + ", backend_name "
2857        + backendNameType + ",backend_state " + backendStateType
2858        + ", checkpoint_name " + checkpointNameType + " " + extraStatement
2859        + ")";
2860
2861    if (logger.isDebugEnabled())
2862      logger.debug(Translate.get("recovery.jdbc.backendtable.statement",
2863          backendTableCreateStatement));
2864  }
2865
2866  /**
2867   * Sets the checkpoint table name and create statement.
2868   *
2869   * @param createTable statement to create the table
2870   * @param tableName name of the checkpoint table.
2871   * @param nameType type for the name column
2872   * @param logIdType type for the log_id column
2873   * @param extraStatement like primary keys
2874   */

2875  public void setCheckpointTableCreateStatement(String JavaDoc createTable,
2876      String JavaDoc tableName, String JavaDoc nameType, String JavaDoc logIdType, String JavaDoc extraStatement)
2877  {
2878    this.checkpointTableCreateTable = createTable;
2879    this.checkpointTableName = tableName;
2880    this.checkpointTableNameType = nameType;
2881    this.checkpointTableLogIdType = logIdType;
2882    this.checkpointTableExtraStatement = extraStatement;
2883    // CREATE TABLE tableName (
2884
// name checkpointNameColumnType,
2885
// log_id logIdColumnType,
2886
// extraStatement)
2887

2888    checkpointTableCreateStatement = createTable + " " + tableName + " (name "
2889        + nameType + ",log_id " + logIdType + extraStatement + ")";
2890    if (logger.isDebugEnabled())
2891      logger.debug(Translate.get("recovery.jdbc.checkpointtable.statement",
2892          checkpointTableCreateStatement));
2893  }
2894
2895  /**
2896   * Sets the dump table name and create statement.
2897   *
2898   * @param createTable statement to create the table
2899   * @param tableName name of the checkpoint table.
2900   * @param dumpNameColumnType the dump name column type
2901   * @param dumpDateColumnType the dump data column type
2902   * @param dumpPathColumnType the dump path column type
2903   * @param dumpFormatColumnType the dump tpe column type
2904   * @param checkpointNameColumnType the checkpoint name column type
2905   * @param backendNameColumnType the backend name column type
2906   * @param tablesColumnName the database tables column name
2907   * @param tablesColumnType the database tables column type
2908   * @param extraStatement like primary keys
2909   */

2910  public void setDumpTableCreateStatement(String JavaDoc createTable, String JavaDoc tableName,
2911      String JavaDoc dumpNameColumnType, String JavaDoc dumpDateColumnType,
2912      String JavaDoc dumpPathColumnType, String JavaDoc dumpFormatColumnType,
2913      String JavaDoc checkpointNameColumnType, String JavaDoc backendNameColumnType,
2914      String JavaDoc tablesColumnName, String JavaDoc tablesColumnType, String JavaDoc extraStatement)
2915  {
2916    this.dumpTableCreateTable = createTable;
2917    this.dumpTableName = tableName;
2918    this.dumpTableDumpNameColumnType = dumpNameColumnType;
2919    this.dumpTableDumpDateColumnType = dumpDateColumnType;
2920    this.dumpTableDumpPathColumnType = dumpPathColumnType;
2921    this.dumpTableDumpFormatColumnType = dumpFormatColumnType;
2922    this.dumpTableCheckpointNameColumnType = checkpointNameColumnType;
2923    this.dumpTableBackendNameColumnType = backendNameColumnType;
2924    this.dumpTableTablesColumnName = tablesColumnName;
2925    this.dumpTableTablesColumnType = tablesColumnType;
2926    this.dumpTableExtraStatementDefinition = extraStatement;
2927
2928    // CREATE TABLE DumpTable (
2929
// dump_name TEXT NOT NULL,
2930
// dump_date DATE,
2931
// dump_path TEXT NOT NULL,
2932
// dump_type TEXT NOT NULL,
2933
// checkpoint_name TEXT NOT NULL,
2934
// backend_name TEXT NOT NULL,
2935
// tables TEXT NOT NULL
2936
// )
2937

2938    dumpTableCreateStatement = dumpTableCreateTable + " " + dumpTableName
2939        + " (dump_name " + dumpTableDumpNameColumnType + ",dump_date "
2940        + dumpDateColumnType + ",dump_path " + dumpPathColumnType
2941        + ",dump_format " + dumpFormatColumnType + ",checkpoint_name "
2942        + checkpointNameColumnType + ",backend_name " + backendNameColumnType
2943        + "," + dumpTableTablesColumnName + " " + tablesColumnType
2944        + extraStatement + ")";
2945    if (logger.isDebugEnabled())
2946      logger.debug(Translate.get("recovery.jdbc.dumptable.statement",
2947          dumpTableCreateStatement));
2948  }
2949
2950  /**
2951   * Sets the log table name and create statement.
2952   *
2953   * @param createTable statement to create the table
2954   * @param tableName name of the log table
2955   * @param idType type of the id column
2956   * @param vloginType type of the login column
2957   * @param sqlName name of the sql statement column
2958   * @param sqlType type of the sql column
2959   * @param sqlParamsType type of the sql_param column
2960   * @param autoConnTranColumnType type of the auto_conn_tran column
2961   * @param transactionIdType type of the transaction column
2962   * @param extraStatement extra statement like primary keys ...
2963   * @param requestIdType request id column type
2964   * @param execTimeType execution time in ms column type
2965   * @param updateCountType update count column type
2966   */

2967  public void setLogTableCreateStatement(String JavaDoc createTable, String JavaDoc tableName,
2968      String JavaDoc idType, String JavaDoc vloginType, String JavaDoc sqlName, String JavaDoc sqlType,
2969      String JavaDoc sqlParamsType, String JavaDoc autoConnTranColumnType,
2970      String JavaDoc transactionIdType, String JavaDoc requestIdType, String JavaDoc execTimeType,
2971      String JavaDoc updateCountType, String JavaDoc extraStatement)
2972  {
2973    this.logTableCreateTable = createTable;
2974    this.logTableName = tableName;
2975    this.logTableLogIdType = idType;
2976    this.logTableVloginType = vloginType;
2977    this.logTableSqlColumnName = sqlName;
2978    this.logTableSqlType = sqlType;
2979    this.logTableAutoConnTranColumnType = autoConnTranColumnType;
2980    this.logTableTransactionIdType = transactionIdType;
2981    this.logTableRequestIdType = requestIdType;
2982    this.logTableExecTimeType = execTimeType;
2983    this.logTableUpdateCountType = updateCountType;
2984    this.logTableExtraStatement = extraStatement;
2985    logTableCreateStatement = createTable + " " + tableName + " (log_id "
2986        + idType + ",vlogin " + vloginType + "," + logTableSqlColumnName + " "
2987        + sqlType + "," + logTableSqlColumnName + "_param " + sqlParamsType
2988        + ",auto_conn_tran " + autoConnTranColumnType + ",transaction_id "
2989        + transactionIdType + ",request_id " + requestIdType + ",exec_status "
2990        + autoConnTranColumnType + ",exec_time " + execTimeType
2991        + ",update_count " + updateCountType + ", completion_log_id " + idType
2992        + extraStatement + ")";
2993    if (logger.isDebugEnabled())
2994      logger.debug(Translate.get("recovery.jdbc.logtable.statement",
2995          logTableCreateStatement));
2996    String JavaDoc type = idType;
2997    int index = type.toUpperCase().indexOf("NOT NULL");
2998    if (index >= 0)
2999      type = type.substring(0, index) + " DEFAULT -1 NOT NULL";
3000    else
3001      type += " DEFAULT -1";
3002
3003    logTableAddCompletionLogIdStatement = "alter table " + tableName
3004        + " add completion_log_id " + type;
3005  }
3006
3007  //
3008
//
3009
// Log utility functions
3010
//
3011
//
3012

3013  /**
3014   * Checks if a checkpoint with the name checkpointName is already stored in
3015   * the database.
3016   *
3017   * @param checkpointName name of the checkpoint.
3018   * @return true if no checkpoint was found.
3019   */

3020  private boolean validCheckpointName(String JavaDoc checkpointName)
3021      throws SQLException JavaDoc
3022  {
3023    PreparedStatement JavaDoc stmt = null;
3024    ResultSet JavaDoc rs = null;
3025    try
3026    {
3027      stmt = getDatabaseConnection().prepareStatement(
3028          "SELECT * FROM " + checkpointTableName + " WHERE name LIKE ?");
3029      stmt.setString(1, checkpointName);
3030      rs = stmt.executeQuery();
3031
3032      // If the query returned any rows, the checkpoint name is already
3033
// in use and therefore invalid.
3034
boolean checkpointExists = rs.next();
3035      rs.close();
3036      return !checkpointExists;
3037    }
3038    catch (SQLException JavaDoc e)
3039    {
3040      invalidateInternalConnection();
3041      throw new SQLException JavaDoc(Translate.get(
3042          "recovery.jdbc.checkpoint.check.failed", e));
3043    }
3044    finally
3045    {
3046      try
3047      {
3048        if (stmt != null)
3049          stmt.close();
3050      }
3051      catch (SQLException JavaDoc ignore)
3052      {
3053      }
3054    }
3055  }
3056
3057  //
3058
//
3059
// Info/Monitoring/Debug related functions
3060
//
3061
//
3062

3063  /**
3064   * @see org.continuent.sequoia.controller.jmx.AbstractStandardMBean#getAssociatedString()
3065   */

3066  public String JavaDoc getAssociatedString()
3067  {
3068    return "jdbcrecoverylog";
3069  }
3070
3071  /**
3072   * Allow to get the content of the recovery log for viewing
3073   *
3074   * @return <code>String[][]</code>
3075   * @see org.continuent.sequoia.controller.monitoring.datacollector.DataCollector#retrieveRecoveryLogData(String)
3076   */

3077  public String JavaDoc[][] getData()
3078  {
3079    Statement JavaDoc stmt = null;
3080    ResultSet JavaDoc rs = null;
3081    try
3082    {
3083      stmt = getDatabaseConnection().createStatement();
3084      rs = stmt.executeQuery("select * from " + logTableName);
3085      ArrayList JavaDoc list = new ArrayList JavaDoc();
3086      while (rs.next())
3087      {
3088        // 3: Query 2: User 1: ID 4: TID
3089
list.add(new String JavaDoc[]{rs.getString(3), rs.getString(2),
3090            rs.getString(1), rs.getString(4)});
3091      }
3092      String JavaDoc[][] result = new String JavaDoc[list.size()][4];
3093      for (int i = 0; i < list.size(); i++)
3094        result[i] = (String JavaDoc[]) list.get(i);
3095      return result;
3096    }
3097    catch (SQLException JavaDoc e)
3098    {
3099      return null;
3100    }
3101    finally
3102    {
3103      try
3104      {
3105        rs.close();
3106      }
3107      catch (SQLException JavaDoc ignore)
3108      {
3109      }
3110      try
3111      {
3112        stmt.close();
3113      }
3114      catch (SQLException JavaDoc ignore)
3115      {
3116      }
3117    }
3118  }
3119
3120  String JavaDoc[] getColumnNames()
3121  {
3122    Statement JavaDoc stmt = null;
3123    ResultSet JavaDoc rs = null;
3124    try
3125    {
3126      stmt = getDatabaseConnection().createStatement();
3127      rs = stmt.executeQuery("select * from " + logTableName
3128          + " where log_id=0");
3129      int columnCount = rs.getMetaData().getColumnCount();
3130      String JavaDoc[] columnNames = new String JavaDoc[columnCount];
3131      for (int i = 0; i < columnCount; i++)
3132      {
3133        columnNames[i] = rs.getMetaData().getColumnName(i + 1);
3134      }
3135      return columnNames;
3136    }
3137    catch (SQLException JavaDoc e)
3138    {
3139      return new String JavaDoc[0];
3140    }
3141    finally
3142    {
3143      try
3144      {
3145        rs.close();
3146      }
3147      catch (SQLException JavaDoc ignore)
3148      {
3149      }
3150      try
3151      {
3152        stmt.close();
3153      }
3154      catch (SQLException JavaDoc ignore)
3155      {
3156      }
3157    }
3158  }
3159
3160  /**
3161   * returns an long[2] for min and max log_id in the log table
3162   *
3163   * @return an long[2] for min and max log_id in the log table
3164   * @throws SQLException if an error occurs while computing the index of the
3165   * log table
3166   */

3167  long[] getIndexes() throws SQLException JavaDoc
3168  {
3169    Statement JavaDoc stmt;
3170    stmt = getDatabaseConnection().createStatement();
3171    ResultSet JavaDoc rs = null;
3172    try
3173    {
3174      rs = stmt.executeQuery("select min(log_id),max(log_id) from "
3175          + logTableName);
3176      rs.next();
3177      long min = rs.getLong(1);
3178      long max = rs.getLong(2);
3179      return new long[]{min, max};
3180    }
3181    finally
3182    {
3183      if (rs != null)
3184      {
3185        rs.close();
3186      }
3187      if (stmt != null)
3188      {
3189        stmt.close();
3190      }
3191    }
3192  }
3193
3194  /**
3195   * Exposes the contents of the recovery log table as a matrix of String.<br />
3196   * <em>this method should be only used for management/debugging purpose</em>
3197   *
3198   * @param from the starting index from which the log entries are retrieved
3199   * (corresponds to the log_id column)
3200   * @param maxrows the maximum number of rows to retrieve
3201   * @return a matrix of String representing the content of the recovery log
3202   * table
3203   */

3204  String JavaDoc[][] getLogEntries(long from, int maxrows)
3205  {
3206    Statement JavaDoc stmt = null;
3207    ResultSet JavaDoc rs = null;
3208    try
3209    {
3210      stmt = getDatabaseConnection().createStatement();
3211      stmt.setMaxRows(maxrows);
3212      rs = stmt.executeQuery("select * from " + logTableName
3213          + " where log_id >= " + from);
3214      int columnCount = rs.getMetaData().getColumnCount();
3215      List JavaDoc logEntries = new ArrayList JavaDoc();
3216      while (rs.next())
3217      {
3218        String JavaDoc[] logEntry = new String JavaDoc[columnCount];
3219        for (int i = 0; i < columnCount; i++)
3220        {
3221          logEntry[i] = rs.getString(i + 1);
3222        }
3223        logEntries.add(logEntry);
3224      }
3225      String JavaDoc[][] result = new String JavaDoc[logEntries.size()][columnCount];
3226      for (int i = 0; i < logEntries.size(); i++)
3227        result[i] = (String JavaDoc[]) logEntries.get(i);
3228      return result;
3229    }
3230    catch (SQLException JavaDoc e)
3231    {
3232      return null;
3233    }
3234    finally
3235    {
3236      try
3237      {
3238        rs.close();
3239      }
3240      catch (SQLException JavaDoc ignore)
3241      {
3242      }
3243      try
3244      {
3245        stmt.close();
3246      }
3247      catch (SQLException JavaDoc ignore)
3248      {
3249      }
3250    }
3251  }
3252
3253  //
3254
// Internal utility methods
3255
//
3256

3257  /**
3258   * Posts and waits for the specified LogEvent to complete.
3259   *
3260   * @param event the LogEvent to post and wait for
3261   * @throws SQLException in case the wait was interrupted.
3262   */

3263  void postAndWaitFor(LogEvent event) throws SQLException JavaDoc
3264  {
3265    synchronized (event)
3266    {
3267      loggerThread.log(event);
3268      try
3269      {
3270        event.wait();
3271      }
3272      catch (InterruptedException JavaDoc e)
3273      {
3274        throw new SQLException JavaDoc("Interrupted while waiting for LogEvent "
3275            + event);
3276      }
3277    }
3278  }
3279
3280  /**
3281   * Gives the log as an XML String
3282   *
3283   * @return the XML representation of the log
3284   */

3285  public String JavaDoc getXml()
3286  {
3287    StringBuffer JavaDoc info = new StringBuffer JavaDoc();
3288    info.append("<" + DatabasesXmlTags.ELT_RecoveryLog + " "
3289        + DatabasesXmlTags.ATT_driver + "=\"" + driverClassName + "\" "
3290        + DatabasesXmlTags.ATT_url + "=\"" + url + "\" ");
3291    if (driverPath != null)
3292    {
3293      info.append(DatabasesXmlTags.ATT_driverPath + "=\"" + driverPath + "\" ");
3294    }
3295    info.append(DatabasesXmlTags.ATT_login + "=\"" + login + "\" "
3296        + DatabasesXmlTags.ATT_password + "=\"" + password + "\" "
3297        + DatabasesXmlTags.ATT_requestTimeout + "=\"" + (timeout / 1000)
3298        + "\" " + DatabasesXmlTags.ATT_recoveryBatchSize + "=\""
3299        + recoveryBatchSize + "\">");
3300    // Recovery Log table
3301
info.append("<" + DatabasesXmlTags.ELT_RecoveryLogTable + " "
3302        + DatabasesXmlTags.ATT_createTable + "=\"" + logTableCreateTable
3303        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + logTableName + "\" "
3304        + DatabasesXmlTags.ATT_logIdColumnType + "=\"" + logTableLogIdType
3305        + "\" " + DatabasesXmlTags.ATT_vloginColumnType + "=\""
3306        + logTableVloginType + "\" " + DatabasesXmlTags.ATT_sqlColumnType
3307        + "=\"" + logTableSqlType + "\" "
3308        + DatabasesXmlTags.ATT_autoConnTranColumnType + "=\""
3309        + logTableAutoConnTranColumnType + "\" "
3310        + DatabasesXmlTags.ATT_transactionIdColumnType + "=\""
3311        + logTableTransactionIdType + "\" "
3312        + DatabasesXmlTags.ATT_requestIdColumnType + "=\""
3313        + logTableRequestIdType + "\" "
3314        + DatabasesXmlTags.ATT_execTimeColumnType + "=\""
3315        + logTableExecTimeType + "\" "
3316        + DatabasesXmlTags.ATT_updateCountColumnType + "=\""
3317        + logTableUpdateCountType + "\" "
3318        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
3319        + logTableExtraStatement + "\"/>");
3320    // Checkpoint table
3321
info.append("<" + DatabasesXmlTags.ELT_CheckpointTable + " "
3322        + DatabasesXmlTags.ATT_createTable + "=\"" + checkpointTableCreateTable
3323        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + checkpointTableName
3324        + "\" " + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
3325        + checkpointTableNameType + "\" "
3326        + DatabasesXmlTags.ATT_logIdColumnType + "=\""
3327        + checkpointTableLogIdType + "\" "
3328        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
3329        + checkpointTableExtraStatement + "\"" + "/>");
3330    // BackendLog table
3331
info.append("<" + DatabasesXmlTags.ELT_BackendTable + " "
3332        + DatabasesXmlTags.ATT_createTable + "=\"" + backendTableCreateTable
3333        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + backendTableName
3334        + "\" " + DatabasesXmlTags.ATT_databaseNameColumnType + "=\""
3335        + backendTableDatabaseName + "\" "
3336        + DatabasesXmlTags.ATT_backendNameColumnType + "=\""
3337        + backendTableBackendName + "\" "
3338        + DatabasesXmlTags.ATT_backendStateColumnType + "=\""
3339        + backendTableBackendState + "\" "
3340        + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
3341        + backendTableCheckpointName + "\" "
3342        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
3343        + backendTableExtraStatement + "\"" + "/>");
3344    // Dump table
3345
info.append("<" + DatabasesXmlTags.ELT_DumpTable + " "
3346        + DatabasesXmlTags.ATT_createTable + "=\"" + dumpTableCreateTable
3347        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + dumpTableName
3348        + "\" " + DatabasesXmlTags.ATT_dumpNameColumnType + "=\""
3349        + dumpTableDumpNameColumnType + "\" "
3350        + DatabasesXmlTags.ATT_dumpDateColumnType + "=\""
3351        + dumpTableDumpDateColumnType + "\" "
3352        + DatabasesXmlTags.ATT_dumpPathColumnType + "=\""
3353        + dumpTableDumpPathColumnType + "\" "
3354        + DatabasesXmlTags.ATT_dumpFormatColumnType + "=\""
3355        + dumpTableDumpFormatColumnType + "\" "
3356        + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
3357        + dumpTableCheckpointNameColumnType + "\" "
3358        + DatabasesXmlTags.ATT_backendNameColumnType + "=\""
3359        + dumpTableBackendNameColumnType + "\" "
3360        + DatabasesXmlTags.ATT_tablesColumnName + "=\""
3361        + dumpTableTablesColumnName + "\" "
3362        + DatabasesXmlTags.ATT_tablesColumnType + "=\""
3363        + dumpTableTablesColumnType + "\" "
3364        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
3365        + dumpTableExtraStatementDefinition + "\"" + "/>");
3366    info.append("</" + DatabasesXmlTags.ELT_RecoveryLog + ">");
3367
3368    return info.toString();
3369  }
3370
3371}
Popular Tags