KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > controller > recoverylog > RecoveryLog


1 /**
2  * C-JDBC: Clustered JDBC.
3  * Copyright (C) 2002-2005 French National Institute For Research In Computer
4  * Science And Control (INRIA).
5  * Contact: c-jdbc@objectweb.org
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by the
9  * Free Software Foundation; either version 2.1 of the License, or any later
10  * version.
11  *
12  * This library is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
15  * for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
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.objectweb.cjdbc.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.util.ArrayList JavaDoc;
34
35 import org.objectweb.cjdbc.common.exceptions.VirtualDatabaseException;
36 import org.objectweb.cjdbc.common.i18n.Translate;
37 import org.objectweb.cjdbc.common.log.Trace;
38 import org.objectweb.cjdbc.common.shared.BackendState;
39 import org.objectweb.cjdbc.common.shared.DumpInfo;
40 import org.objectweb.cjdbc.common.sql.AbstractRequest;
41 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest;
42 import org.objectweb.cjdbc.common.sql.AlterRequest;
43 import org.objectweb.cjdbc.common.sql.CreateRequest;
44 import org.objectweb.cjdbc.common.sql.DeleteRequest;
45 import org.objectweb.cjdbc.common.sql.DropRequest;
46 import org.objectweb.cjdbc.common.sql.InsertRequest;
47 import org.objectweb.cjdbc.common.sql.StoredProcedure;
48 import org.objectweb.cjdbc.common.sql.UpdateRequest;
49 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
50 import org.objectweb.cjdbc.common.xml.XmlComponent;
51 import org.objectweb.cjdbc.controller.connection.DriverManager;
52 import org.objectweb.cjdbc.controller.loadbalancer.tasks.BeginTask;
53 import org.objectweb.cjdbc.controller.loadbalancer.tasks.CommitTask;
54 import org.objectweb.cjdbc.controller.loadbalancer.tasks.ReadStoredProcedureTask;
55 import org.objectweb.cjdbc.controller.loadbalancer.tasks.ReleaseSavepointTask;
56 import org.objectweb.cjdbc.controller.loadbalancer.tasks.RollbackTask;
57 import org.objectweb.cjdbc.controller.loadbalancer.tasks.RollbackToSavepointTask;
58 import org.objectweb.cjdbc.controller.loadbalancer.tasks.SavepointTask;
59 import org.objectweb.cjdbc.controller.loadbalancer.tasks.WriteRequestTask;
60 import org.objectweb.cjdbc.controller.loadbalancer.tasks.WriteStoredProcedureTask;
61 import org.objectweb.cjdbc.controller.recoverylog.events.LogEntry;
62 import org.objectweb.cjdbc.controller.recoverylog.events.LogRequestEvent;
63 import org.objectweb.cjdbc.controller.recoverylog.events.LogRollbackEvent;
64 import org.objectweb.cjdbc.controller.recoverylog.events.ResetLogEvent;
65 import org.objectweb.cjdbc.controller.recoverylog.events.StoreDumpCheckpointEvent;
66 import org.objectweb.cjdbc.controller.recoverylog.events.UnlogRequestEvent;
67 import org.objectweb.cjdbc.controller.requestmanager.TransactionMarkerMetaData;
68
69 /**
70  * Recovery Log using a database accessed through JDBC.
71  *
72  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
73  * @author <a HREF="mailto:Julie.Marguerite@inria.fr">Julie Marguerite </a>
74  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>*
75  * @author <a HREF="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
76  * </a>
77  * @version 1.0
78  */

79 public class RecoveryLog implements XmlComponent
80 {
81   static Trace logger = Trace
82                                                                             .getLogger("org.objectweb.cjdbc.controller.recoverylog");
83
84   /** Number of backends currently recovering */
85   private long recoveringNb = 0;
86
87   /** Size of the pendingRecoveryTasks queue used by the recover thread */
88   protected int recoveryBatchSize;
89
90   /** Driver class name. */
91   private String JavaDoc driverClassName;
92
93   /** Driver name. */
94   private String JavaDoc driverName;
95
96   /** Driver URL. */
97   private String JavaDoc url;
98
99   /** User's login. */
100   private String JavaDoc login;
101
102   /** User's password. */
103   private String JavaDoc password;
104
105   /** Database connection */
106   private Connection JavaDoc internalConnectionManagedByGetDatabaseConnection = null;
107
108   /**
109    * Recovery log table options.
110    *
111    * @see #setLogTableCreateStatement(String, String, String, String, String,
112    * String, String, String)
113    */

114   private String JavaDoc logTableCreateTable;
115   private String JavaDoc logTableName;
116   private String JavaDoc logTableCreateStatement;
117   private String JavaDoc logTableIdType;
118   private String JavaDoc logTableVloginType;
119   private String JavaDoc logTableSqlColumnName;
120   private String JavaDoc logTableSqlType;
121   private String JavaDoc logTableTransactionIdType;
122   private String JavaDoc logTableExtraStatement;
123
124   /**
125    * Checkpoint table options.
126    *
127    * @see #setCheckpointTableCreateStatement(String, String, String, String,
128    * String)
129    */

130   private String JavaDoc checkpointTableCreateTable;
131   private String JavaDoc checkpointTableName;
132   private String JavaDoc checkpointTableCreateStatement;
133   private String JavaDoc checkpointTableNameType;
134   private String JavaDoc checkpointTableRequestIdType;
135   private String JavaDoc checkpointTableExtraStatement;
136
137   /**
138    * Backend table options
139    *
140    * @see #setBackendTableCreateStatement(String, String, String, String,
141    * String, String, String)
142    */

143   private String JavaDoc backendTableCreateStatement;
144   private String JavaDoc backendTableName;
145   private String JavaDoc backendTableCreateTable;
146   private String JavaDoc backendTableDatabaseName;
147   private String JavaDoc backendTableExtraStatement;
148   private String JavaDoc backendTableCheckpointName;
149   private String JavaDoc backendTableBackendState;
150   private String JavaDoc backendTableBackendName;
151
152   /**
153    * Dump table options
154    *
155    * @see #setDumpTableCreateStatement(String, String, String, String, String,
156    * String, String, String, String, String)
157    */

158   private String JavaDoc dumpTableCreateStatement;
159   private String JavaDoc dumpTableCreateTable;
160   private String JavaDoc dumpTableName;
161   private String JavaDoc dumpTableDumpNameColumnType;
162   private String JavaDoc dumpTableDumpDateColumnType;
163   private String JavaDoc dumpTableDumpPathColumnType;
164   private String JavaDoc dumpTableDumpFormatColumnType;
165   private String JavaDoc dumpTableCheckpointNameColumnType;
166   private String JavaDoc dumpTableBackendNameColumnType;
167   private String JavaDoc dumpTableTablesColumnName;
168   private String JavaDoc dumpTableTablesColumnType;
169   private String JavaDoc dumpTableExtraStatementDefinition;
170
171   /** Current maximum value of the primary key in logTableName. */
172   private long logTableId = 0;
173
174   /** Timeout for SQL requests. */
175   private int timeout;
176
177   private LoggerThread loggerThread;
178
179   /**
180    * Creates a new <code>RecoveryLog</code> instance.
181    *
182    * @param driverName the driverClassName name.
183    * @param driverClassName the driverClassName class name.
184    * @param url the JDBC URL.
185    * @param login the login to use to connect to the database.
186    * @param password the password to connect to the database.
187    * @param requestTimeout timeout in seconds for update queries.
188    * @param recoveryBatchSize number of queries that can be accumulated into a
189    * batch when recovering
190    */

191   public RecoveryLog(String JavaDoc driverName, String JavaDoc driverClassName, String JavaDoc url,
192       String JavaDoc login, String JavaDoc password, int requestTimeout, int recoveryBatchSize)
193   {
194     this.driverName = driverName;
195     this.driverClassName = driverClassName;
196     this.url = url;
197     this.login = login;
198     this.password = password;
199     this.timeout = requestTimeout;
200     if (recoveryBatchSize < 1)
201     {
202       logger
203           .warn("RecoveryBatchSize was set to a value lesser than 1, resetting value to 1.");
204       recoveryBatchSize = 1;
205     }
206     this.recoveryBatchSize = recoveryBatchSize;
207
208     // Connect to the database
209
try
210     {
211       getDatabaseConnection();
212     }
213     catch (SQLException JavaDoc e)
214     {
215       throw new RuntimeException JavaDoc("Unable to connect to the database: " + e);
216     }
217
218     // Logger thread will be created in checkRecoveryLogTables()
219
// after database has been initialized
220
}
221
222   //
223
// Database manipulation and access
224
//
225

226   /**
227    * Gets a connection to the database.
228    *
229    * @return a connection to the database
230    * @exception SQLException if an error occurs.
231    * @see #invalidateInternalConnection()
232    */

233   protected Connection JavaDoc getDatabaseConnection() throws SQLException JavaDoc
234   {
235     try
236     {
237       if (internalConnectionManagedByGetDatabaseConnection == null)
238       {
239         if (logger.isDebugEnabled())
240           logger.debug(Translate.get("recovery.jdbc.connect", new String JavaDoc[]{url,
241               login}));
242         internalConnectionManagedByGetDatabaseConnection = DriverManager
243             .getConnection(url, login, password, driverName, driverClassName);
244       }
245       return internalConnectionManagedByGetDatabaseConnection;
246     }
247     catch (RuntimeException JavaDoc e)
248     {
249       String JavaDoc msg = Translate.get("recovery.jdbc.connect.failed", e);
250       if (logger.isDebugEnabled())
251         logger.debug(msg, e);
252       throw new SQLException JavaDoc(msg);
253     }
254     catch (SQLException JavaDoc e)
255     {
256       invalidateInternalConnection();
257       String JavaDoc msg = Translate.get("recovery.jdbc.connect.failed", e);
258       if (logger.isDebugEnabled())
259         logger.debug(msg, e);
260       throw new SQLException JavaDoc(msg);
261     }
262   }
263
264   /**
265    * Increments the value of logTableId.
266    */

267   private synchronized long incrementLogTableId()
268   {
269     logTableId++;
270     return logTableId;
271   }
272
273   /**
274    * Checks if the tables (log and checkpoint) already exist, and create them if
275    * needed.
276    */

277   private void intializeDatabase() throws SQLException JavaDoc
278   {
279     boolean createLogTable = true;
280     boolean createCheckpointTable = true;
281     boolean createBackendTable = true;
282     boolean createDumpTable = true;
283     Connection JavaDoc connection;
284     // Check if tables exist
285
try
286     {
287       connection = getDatabaseConnection();
288       connection.setAutoCommit(false);
289       // Get DatabaseMetaData
290
DatabaseMetaData JavaDoc metaData = connection.getMetaData();
291
292       // Get a description of tables matching the catalog, schema, table name
293
// and type.
294
// Sending in null for catalog and schema drop them from the selection
295
// criteria. Replace the last argument in the getTables method with
296
// types below to obtain only database tables. (Sending in null
297
// retrieves all types).
298
String JavaDoc[] types = {"TABLE", "VIEW"};
299       ResultSet JavaDoc rs = metaData.getTables(null, null, "%", types);
300
301       // Get tables metadata
302
String JavaDoc tableName;
303       while (rs.next())
304       {
305         // 1 is table catalog, 2 is table schema, 3 is table name, 4 is type
306
tableName = rs.getString(3);
307         if (logger.isDebugEnabled())
308           logger.debug(Translate.get("recovery.jdbc.table.found", tableName));
309         if (tableName.equalsIgnoreCase(logTableName))
310         {
311           if (tableName.compareTo(logTableName) != 0)
312             logger.warn(Translate.get("recovery.jdbc.logtable.case.mismatch",
313                 new String JavaDoc[]{logTableName, tableName}));
314           createLogTable = false;
315           // initialize logTableId
316
PreparedStatement JavaDoc p = null;
317           try
318           {
319             ResultSet JavaDoc result = null;
320             p = connection.prepareStatement("SELECT MAX(id) AS max_id FROM "
321                 + logTableName);
322             result = p.executeQuery();
323             if (result.next())
324               logTableId = result.getLong("max_id");
325             else
326               logTableId = 0;
327             p.close();
328           }
329           catch (SQLException JavaDoc e)
330           {
331             try
332             {
333               if (p != null)
334                 p.close();
335             }
336             catch (Exception JavaDoc ignore)
337             {
338             }
339             throw new RuntimeException JavaDoc(Translate.get(
340                 "recovery.jdbc.logtable.getvalue.failed", e));
341           }
342
343         }
344         if (tableName.equalsIgnoreCase(checkpointTableName))
345         {
346           if (tableName.compareTo(checkpointTableName) != 0)
347             logger.warn(Translate.get(
348                 "recovery.jdbc.checkpointtable.case.mismatch", new String JavaDoc[]{
349                     checkpointTableName, tableName}));
350           createCheckpointTable = false;
351         }
352         else if (tableName.equalsIgnoreCase(backendTableName))
353         {
354           if (tableName.compareTo(backendTableName) != 0)
355             logger.warn(Translate.get(
356                 "recovery.jdbc.backendtable.case.mismatch", new String JavaDoc[]{
357                     backendTableName, tableName}));
358           createBackendTable = false;
359         }
360         else if (tableName.equalsIgnoreCase(dumpTableName))
361         {
362           if (tableName.compareTo(dumpTableName) != 0)
363             logger.warn(Translate.get("recovery.jdbc.dumptable.case.mismatch",
364                 new String JavaDoc[]{backendTableName, tableName}));
365           createDumpTable = false;
366         }
367       }
368       try
369       {
370         connection.commit();
371         connection.setAutoCommit(true);
372       }
373       catch (Exception JavaDoc ignore)
374       {
375         // Read-only transaction we don't care
376
}
377     }
378     catch (SQLException JavaDoc e)
379     {
380       logger.error(Translate.get("recovery.jdbc.table.no.description"), e);
381       throw e;
382     }
383
384     // Create the missing tables
385
Statement JavaDoc stmt = null;
386     if (createLogTable)
387     {
388       if (logger.isInfoEnabled())
389         logger.info(Translate
390             .get("recovery.jdbc.logtable.create", logTableName));
391       try
392       {
393         stmt = connection.createStatement();
394         stmt.executeUpdate(logTableCreateStatement);
395         stmt.close();
396       }
397       catch (SQLException JavaDoc e)
398       {
399         throw new SQLException JavaDoc(Translate.get(
400             "recovery.jdbc.logtable.create.failed", new String JavaDoc[]{logTableName,
401                 e.getMessage()}));
402       }
403     }
404     if (createCheckpointTable)
405     {
406       if (logger.isInfoEnabled())
407         logger.info(Translate.get("recovery.jdbc.checkpointtable.create",
408             checkpointTableName));
409       try
410       {
411         stmt = connection.createStatement();
412         stmt.executeUpdate(checkpointTableCreateStatement);
413         stmt.close();
414       }
415       catch (SQLException JavaDoc e)
416       {
417         throw new SQLException JavaDoc(Translate.get(
418             "recovery.jdbc.checkpointtable.create.failed", new String JavaDoc[]{
419                 logTableName, e.getMessage()}));
420       }
421
422       // Add an initial checkpoint in the table
423
String JavaDoc checkpointName = "Initial_empty_recovery_log";
424       PreparedStatement JavaDoc pstmt = null;
425       try
426       {
427         if (logger.isDebugEnabled())
428           logger.debug("Storing checkpoint " + checkpointName
429               + " at request id " + logTableId);
430         pstmt = connection.prepareStatement("INSERT INTO "
431             + checkpointTableName + " VALUES(?,?)");
432         pstmt.setString(1, checkpointName);
433         pstmt.setLong(2, logTableId);
434         pstmt.executeUpdate();
435         pstmt.close();
436       }
437       catch (SQLException JavaDoc e)
438       {
439         try
440         {
441           if (pstmt != null)
442             pstmt.close();
443         }
444         catch (Exception JavaDoc ignore)
445         {
446         }
447         throw new SQLException JavaDoc(Translate.get(
448             "recovery.jdbc.checkpoint.store.failed", new String JavaDoc[]{
449                 checkpointName, e.getMessage()}));
450       }
451     }
452     if (createBackendTable)
453     {
454       if (logger.isInfoEnabled())
455         logger.info(Translate.get("recovery.jdbc.backendtable.create",
456             backendTableName));
457       try
458       {
459         stmt = connection.createStatement();
460         stmt.executeUpdate(backendTableCreateStatement);
461         stmt.close();
462       }
463       catch (SQLException JavaDoc e)
464       {
465         throw new SQLException JavaDoc(Translate.get(
466             "recovery.jdbc.backendtable.create.failed", new String JavaDoc[]{
467                 logTableName, e.getMessage()}));
468       }
469     }
470     if (createDumpTable)
471     {
472       if (logger.isInfoEnabled())
473         logger.info(Translate.get("recovery.jdbc.dumptable.create",
474             dumpTableName));
475       try
476       {
477         stmt = connection.createStatement();
478         stmt.executeUpdate(dumpTableCreateStatement);
479         stmt.close();
480       }
481       catch (SQLException JavaDoc e)
482       {
483         throw new SQLException JavaDoc(Translate.get(
484             "recovery.jdbc.dumptable.create.failed", new String JavaDoc[]{
485                 dumpTableName, e.getMessage()}));
486       }
487     }
488   }
489
490   /**
491    * Invalidate the connection when an error occurs so that the next call to
492    * getDatabaseConnection() re-allocates a new connection.
493    *
494    * @see #getDatabaseConnection()
495    */

496   protected void invalidateInternalConnection()
497   {
498     try
499     {
500       internalConnectionManagedByGetDatabaseConnection.close();
501     }
502     catch (Exception JavaDoc ignore)
503     {
504     }
505     internalConnectionManagedByGetDatabaseConnection = null;
506   }
507
508   //
509
//
510
// Logging related methods
511
//
512
//
513

514   /**
515    * Log a transaction abort. This is used only for transaction that were
516    * started but where no request was executed, which is in fact an empty
517    * transaction. The underlying implementation might safely discard the
518    * corresponding begin from the log as an optimization.
519    *
520    * @param tm The transaction marker metadata
521    * @return the identifier of the entry in the recovery log
522    */

523   public long logAbort(TransactionMarkerMetaData tm)
524   {
525     // We have to perform exactly the same job as a rollback
526
return logRollback(tm);
527   }
528
529   /**
530    * Log the beginning of a new transaction.
531    *
532    * @param tm The transaction marker metadata
533    * @return the identifier of the entry in the recovery log
534    */

535   public long logBegin(TransactionMarkerMetaData tm)
536   {
537     // Store the begin in the database
538
long id = incrementLogTableId();
539     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
540         "begin", tm.getTransactionId(), false)));
541     return id;
542   }
543
544   /**
545    * Log a transaction commit.
546    *
547    * @param tm The transaction marker metadata
548    * @return the identifier of the entry in the recovery log
549    */

550   public long logCommit(TransactionMarkerMetaData tm)
551   {
552     long id = incrementLogTableId();
553     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
554         "commit", tm.getTransactionId(), false)));
555     return id;
556   }
557
558   /**
559    * Log a log entry in the recovery log.
560    *
561    * @param logEntry the log entry to to be written in the recovery log.
562    */

563   public void logLogEntry(LogEntry logEntry)
564   {
565     loggerThread.log(new LogRequestEvent(logEntry));
566   }
567
568   /**
569    * Log a transaction savepoint removal.
570    *
571    * @param tm The transaction marker metadata
572    * @param name The name of the savepoint to log
573    * @return the identifier of the entry in the recovery log
574    */

575   public long logReleaseSavepoint(TransactionMarkerMetaData tm, String JavaDoc name)
576   {
577     long id = incrementLogTableId();
578     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
579         "release " + name, tm.getTransactionId(), false)));
580     return id;
581   }
582
583   /**
584    * Log a write request.
585    *
586    * @param request The write request to log
587    * @return the identifier of the entry in the recovery log
588    */

589   public long logRequest(AbstractWriteRequest request)
590   {
591     long id = incrementLogTableId();
592     loggerThread.log(new LogRequestEvent(new LogEntry(id, request.getLogin(),
593         request.getSQL(), request.getTransactionId(), request
594             .getEscapeProcessing())));
595     return id;
596   }
597
598   /**
599    * Log a call to a stored procedure.
600    *
601    * @param proc The stored procedure call to log
602    * @param isRead True if the stored procedure call returns a ResultSet
603    * @return the identifier of the entry in the recovery log
604    */

605   public long logRequest(StoredProcedure proc, boolean isRead)
606   {
607     long id = incrementLogTableId();
608     if (isRead)
609       loggerThread.log(new LogRequestEvent(new LogEntry(incrementLogTableId(),
610           proc.getLogin(), proc.getSQL(), proc.getTransactionId(), proc
611               .getEscapeProcessing())));
612     else
613     { // Reverse the first bracket so that we can identify a write call
614
StringBuffer JavaDoc writeCall = new StringBuffer JavaDoc(proc.getSQL());
615       writeCall.setCharAt(0, '}');
616       loggerThread.log(new LogRequestEvent(new LogEntry(incrementLogTableId(),
617           proc.getLogin(), writeCall.toString(), proc.getTransactionId(), proc
618               .getEscapeProcessing())));
619     }
620     return id;
621   }
622
623   /**
624    * Log a transaction rollback.
625    *
626    * @param tm The transaction marker metadata
627    * @return the identifier of the entry in the recovery log
628    */

629   public long logRollback(TransactionMarkerMetaData tm)
630   {
631     long id = incrementLogTableId();
632     // Some backends started a recovery process, log the rollback
633
loggerThread.log(new LogRollbackEvent(new LogEntry(id, tm.getLogin(),
634         "rollback", tm.getTransactionId(), false)));
635     return id;
636   }
637
638   /**
639    * Log a transaction rollback to a savepoint
640    *
641    * @param tm The transaxtion marker metadata
642    * @param savepointName The name of the savepoint
643    * @return the identifier of the entry in the recovery log
644    */

645   public long logRollback(TransactionMarkerMetaData tm, String JavaDoc savepointName)
646   {
647     long id = incrementLogTableId();
648     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
649         "rollback " + savepointName, tm.getTransactionId(), false)));
650     return id;
651   }
652
653   /**
654    * Log a transaction savepoint.
655    *
656    * @param tm The transaction marker metadata
657    * @param name The name of the savepoint to log
658    * @return the identifier of the entry in the recovery log
659    */

660   public long logSetSavepoint(TransactionMarkerMetaData tm, String JavaDoc name)
661   {
662     long id = incrementLogTableId();
663     loggerThread.log(new LogRequestEvent(new LogEntry(id, tm.getLogin(),
664         "savepoint " + name, tm.getTransactionId(), false)));
665     return id;
666   }
667
668   /**
669    * Reset the current log table id and delete the recovery log information
670    * older than the given checkpoint. This method also deletes all entries in
671    * the checkpoint table. This method is asynchronous: the delete is performed
672    * via a post to the loggger thread.
673    *
674    * @param checkpointName the checkpoint name to delete from.
675    * @param newCheckpointId the new checkpoint identifier
676    * @throws SQLException if an error occurs
677    */

678   public void resetLogTableIdAndDeleteRecoveryLog(String JavaDoc checkpointName,
679       long newCheckpointId) throws SQLException JavaDoc
680   {
681     long oldId = getCheckpointRequestId(checkpointName);
682     synchronized (this)
683     {
684       // resetLog cleans the recovery log table, resets the checkpointTable and
685
// renumber the queries since oldId (checkpoint assigned to the transfer).
686
loggerThread
687           .log(new ResetLogEvent(oldId, newCheckpointId, checkpointName));
688       logTableId = newCheckpointId + logTableId - oldId;
689     }
690   }
691
692   /**
693    * Remove a transaction commit from the recovery log. This commit was logged
694    * because no backend was available locally to execute it but that finally
695    * ended up in failing at all other controllers.
696    *
697    * @param tm the commited transaction
698    * @see org.objectweb.cjdbc.controller.requestmanager.distributed.DistributedRequestManager#removeFailedCommitFromRecoveryLog(TransactionMarkerMetaData)
699    */

700   public void unlogCommit(TransactionMarkerMetaData tm)
701   {
702     // Timeout is used here to transport the log id of the commit
703
loggerThread.log(new UnlogRequestEvent(new LogEntry(tm.getTimeout(), tm
704         .getLogin(), "commit", tm.getTransactionId(), false)));
705   }
706
707   /**
708    * Remove a request from the recovery log. This request was logged because no
709    * backend was available locally to execute it but that finally ended up in
710    * failing at all other controllers.
711    *
712    * @param request request that was logged but failed at all controllers and
713    * must be removed from recovery log
714    * @see org.objectweb.cjdbc.controller.requestmanager.distributed.DistributedRequestManager#removeFailedRequestFromRecoveryLog(AbstractWriteRequest,
715    * long)
716    */

717   public void unlogRequest(AbstractRequest request)
718   {
719     loggerThread.log(new UnlogRequestEvent(new LogEntry(request.getId(),
720         request.getLogin(), request.getSQL(), request.getTransactionId(),
721         request.getEscapeProcessing())));
722   }
723
724   /**
725    * Remove a stored procedure from the recovery log. This stored procedure was
726    * logged because no backend was available locally to execute it but that
727    * finally ended up in failing at all other controllers.
728    *
729    * @param proc stored procedure that was logged but failed at all controllers
730    * and must be removed from recovery log
731    * @see org.objectweb.cjdbc.controller.requestmanager.distributed.DistributedRequestManager#removeFailedStoredProcedureFromRecoveryLog(StoredProcedure)
732    */

733   public void unlogRequest(StoredProcedure proc)
734   {
735     // If we have to unlog this can only be a write stored procedure, a read
736
// would not have been broadcasted.
737
StringBuffer JavaDoc writeCall = new StringBuffer JavaDoc(proc.getSQL());
738     writeCall.setCharAt(0, '}');
739     loggerThread.log(new UnlogRequestEvent(new LogEntry(proc.getId(), proc
740         .getLogin(), writeCall.toString(), proc.getTransactionId(), proc
741         .getEscapeProcessing())));
742   }
743
744   /**
745    * Remove a transaction rollback from the recovery log. This rollback was
746    * logged because no backend was available locally to execute it but that
747    * finally ended up in failing at all other controllers.
748    *
749    * @param tm the commited transaction
750    * @see org.objectweb.cjdbc.controller.requestmanager.distributed.DistributedRequestManager#removeFailedRollbackFromRecoveryLog(TransactionMarkerMetaData)
751    */

752   public void unlogRollback(TransactionMarkerMetaData tm)
753   {
754     // Timeout is used here to transport the log id of the rollback
755
loggerThread.log(new UnlogRequestEvent(new LogEntry(tm.getTimeout(), tm
756         .getLogin(), "rollback", tm.getTransactionId(), false)));
757   }
758
759   /**
760    * Shutdown the recovery log and all its threads.
761    */

762   public void shutdown()
763   {
764     if (loggerThread != null)
765       loggerThread.shutdown();
766   }
767
768   //
769
// Recovery process
770
//
771

772   /**
773    * Notify the recovery log that a recovery process has started.
774    */

775   public synchronized void beginRecovery()
776   {
777     recoveringNb++;
778   }
779
780   /**
781    * Possibly clean the recovery log after all recovery process are done. This
782    * removes all rollbacked transaction from the recovery log.
783    *
784    * @exception SQLException if an error occurs.
785    */

786   public void cleanRecoveryLog() throws SQLException JavaDoc
787   {
788     PreparedStatement JavaDoc stmt = null;
789
790     // Remove the rollback statements and associated requests from the database
791
ResultSet JavaDoc rs = null;
792     try
793     {
794       // Get the list of transaction ids on which a rollback occurred
795
stmt = getDatabaseConnection().prepareStatement(
796           "SELECT transaction_id FROM " + logTableName + " WHERE "
797               + logTableSqlColumnName + " LIKE ?");
798       stmt.setString(1, "rollback");
799       rs = stmt.executeQuery();
800     }
801     catch (SQLException JavaDoc e)
802     {
803       invalidateInternalConnection();
804       try
805       {
806         if (stmt != null)
807           stmt.close();
808       }
809       catch (Exception JavaDoc ignore)
810       {
811       }
812       throw new SQLException JavaDoc("Unable get rollback statements : " + e);
813     }
814     PreparedStatement JavaDoc pstmt = null;
815     long transactionId = -1;
816     try
817     {
818       // remove the rollbacked transaction from the database
819
while (rs.next())
820       {
821         transactionId = rs.getLong("transaction_id");
822         pstmt = getDatabaseConnection().prepareStatement(
823             "DELETE FROM " + logTableName + " WHERE transaction_id=?");
824         pstmt.setLong(1, transactionId);
825         pstmt.executeUpdate();
826         pstmt.close();
827       }
828       rs.close();
829       stmt.close();
830     }
831     catch (SQLException JavaDoc e)
832     {
833       invalidateInternalConnection();
834       throw new SQLException JavaDoc(Translate.get(
835           "recovery.jdbc.transaction.remove.failed", new String JavaDoc[]{
836               String.valueOf(transactionId), e.getMessage()}));
837     }
838     finally
839     {
840       try
841       {
842         if (stmt != null)
843           stmt.close();
844       }
845       catch (Exception JavaDoc ignore)
846       {
847       }
848       try
849       {
850         if (pstmt != null)
851           pstmt.close();
852       }
853       catch (Exception JavaDoc ignore)
854       {
855       }
856
857     }
858   }
859
860   /**
861    * Notify the recovery log that a recovery process has finished. If this is
862    * the last recovery process to finish, the cleanRecoveryLog method is called
863    *
864    * @see #cleanRecoveryLog()
865    */

866   public synchronized void endRecovery()
867   {
868     recoveringNb--;
869     if (recoveringNb == 0)
870     {
871       try
872       {
873         cleanRecoveryLog();
874       }
875       catch (SQLException JavaDoc e)
876       {
877         logger.error(Translate.get("recovery.cleaning.failed"), e);
878       }
879     }
880   }
881
882   /**
883    * Retrieve recovery information on a backend. This includes, the last known
884    * state of the backend, and the last known checkpoint
885    *
886    * @param databaseName the virtual database name
887    * @param backendName the backend name
888    * @return <code>BackendRecoveryInfo<code> instance or <code>null</code> if the backend does not exist
889    */

890   public BackendRecoveryInfo getBackendRecoveryInfo(String JavaDoc databaseName,
891       String JavaDoc backendName)
892   {
893     PreparedStatement JavaDoc stmt = null;
894     String JavaDoc checkpoint = null;
895     int backendState = BackendState.UNKNOWN;
896     try
897     {
898       // 1. Get the reference point to delete
899
stmt = getDatabaseConnection().prepareStatement(
900           "SELECT * FROM " + backendTableName
901               + " WHERE backend_name LIKE ? AND database_name LIKE ?");
902       stmt.setString(1, backendName);
903       stmt.setString(2, databaseName);
904       ResultSet JavaDoc rs = stmt.executeQuery();
905
906       if (rs.next())
907       {
908         checkpoint = rs.getString("checkpoint_name");
909         backendState = rs.getInt("backend_state");
910       }
911       rs.close();
912     }
913     catch (SQLException JavaDoc e)
914     {
915       invalidateInternalConnection();
916       logger.info(
917           "An error occured while retrieving backend recovery information", e);
918     }
919     finally
920     {
921       try
922       {
923         if (stmt != null)
924           stmt.close();
925       }
926       catch (Exception JavaDoc ignore)
927       {
928       }
929     }
930     return new BackendRecoveryInfo(backendName, checkpoint, backendState,
931         databaseName);
932   }
933
934   /**
935    * Get the id of the last transaction logged in the recovery log.
936    *
937    * @return the last transaction id.
938    * @throws SQLException if an error occured while retrieving the id.
939    */

940   public long getLastTransactionId() throws SQLException JavaDoc
941   {
942     Statement JavaDoc stmt = null;
943     ResultSet JavaDoc rs = null;
944     try
945     {
946       stmt = getDatabaseConnection().createStatement();
947       rs = stmt.executeQuery("select max(transaction_id) from " + logTableName);
948       if (rs.next())
949         return rs.getLong(1);
950       else
951         // Table is empty
952
return 0;
953     }
954     catch (SQLException JavaDoc e)
955     {
956       invalidateInternalConnection();
957       throw e;
958     }
959     finally
960     {
961       try
962       {
963         if (rs != null)
964           rs.close();
965       }
966       catch (Exception JavaDoc ignore)
967       {
968       }
969       try
970       {
971         if (stmt != null)
972           stmt.close();
973       }
974       catch (Exception JavaDoc ignore)
975       {
976       }
977     }
978   }
979
980   /**
981    * Returns the recoveringNb value.
982    *
983    * @return Returns the recoveringNb.
984    */

985   public long getRecoveringNb()
986   {
987     return recoveringNb;
988   }
989
990   /**
991    * Number of queries that can be accumulated into a batch when recovering
992    *
993    * @return the recovery batch size
994    */

995   public int getRecoveryBatchSize()
996   {
997     return recoveryBatchSize;
998   }
999
1000  /**
1001   * Returns <code>true</code> if at least one backend has started a recover
1002   * process.
1003   *
1004   * @return <code>boolean</code>
1005   */

1006  public synchronized boolean isRecovering()
1007  {
1008    return recoveringNb > 0;
1009  }
1010
1011  /**
1012   * Get the next log entry from the recovery log given the id of the previous
1013   * log entry.
1014   *
1015   * @param previousLogEntryId previous log entry identifier
1016   * @return the next log entry from the recovery log or null if no further
1017   * entry can be found
1018   * @throws SQLException if an error occurs while accesing the recovery log
1019   */

1020  public LogEntry getNextLogEntry(long previousLogEntryId) throws SQLException JavaDoc
1021  {
1022    ResultSet JavaDoc rs = null;
1023    boolean emptyResult;
1024    PreparedStatement JavaDoc stmt = null;
1025    try
1026    {
1027      stmt = getDatabaseConnection().prepareStatement(
1028          "SELECT * FROM " + logTableName + " WHERE id=?");
1029      // Note that the statement is closed in the finally block
1030
do
1031      {
1032        previousLogEntryId++;
1033        stmt.setLong(1, previousLogEntryId);
1034        // Close ResultSet of previous loop step to free resources.
1035
if (rs != null)
1036          rs.close();
1037        rs = stmt.executeQuery();
1038        emptyResult = !rs.next();
1039      }
1040      while (emptyResult && (previousLogEntryId <= logTableId));
1041
1042      // No more request after this one
1043
if (emptyResult)
1044        return null;
1045
1046      // Read columns in order to prevent issues with MS SQL Server as reported
1047
// Charles Cordingley.
1048
long id = rs.getLong("id");
1049      String JavaDoc user = rs.getString("vlogin");
1050      String JavaDoc sql = rs.getString(logTableSqlColumnName);
1051      long transactionId = rs.getLong("transaction_id");
1052      // Note that booleanProcessing = true is the default value in
1053
// AbstractRequest
1054
return new LogEntry(id, user, sql, transactionId, true);
1055    }
1056    catch (SQLException JavaDoc e)
1057    {
1058      invalidateInternalConnection();
1059      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.recover.failed", e));
1060    }
1061    finally
1062    {
1063      try
1064      {
1065        if (rs != null)
1066          rs.close();
1067      }
1068      catch (Exception JavaDoc ignore)
1069      {
1070      }
1071      try
1072      {
1073        if (stmt != null)
1074          stmt.close();
1075      }
1076      catch (Exception JavaDoc ignore)
1077      {
1078      }
1079    }
1080  }
1081
1082  /**
1083   * Get the next request (begin/commit/rollback or WriteRequest) from the
1084   * recovery log given the id of the previously recovered request.
1085   * <p>
1086   * The id of the request before the first one to recover is given by
1087   * getCheckpointRequestId.
1088   *
1089   * @param previousRequestId id of the previously recovered request
1090   * @return AbstractTask task corresponding to the next request to recover or
1091   * null if no such request exists
1092   * @exception SQLException if an error occurs
1093   * @see #getCheckpointRequestId(String)
1094   */

1095  public RecoveryTask recoverNextRequest(long previousRequestId)
1096      throws SQLException JavaDoc
1097  {
1098    RecoveryTask task = null;
1099
1100    // Get the request with the id after previousRequestId.
1101
LogEntry logEntry = getNextLogEntry(previousRequestId);
1102    if (logEntry == null)
1103      return null;
1104
1105    // Construct the request object according to its type
1106
long transactionId = logEntry.getTid();
1107    long id = logEntry.getId();
1108    String JavaDoc user = logEntry.getLogin();
1109    String JavaDoc sql = logEntry.getQuery().trim();
1110
1111    boolean escapeProcessing = true;
1112    // Check that the command starts with (only 2 letters are needed)
1113
// in[sert]/up[date]/de[lete]/cr[eate]/dr[op]/be[gin]/co[mmit]/ro[llback]
1114
// sa[vepoint]/re[lease]/{c[all]/}c[all] (write stored procedure call)
1115
String JavaDoc lower = sql.substring(0, 2).toLowerCase();
1116    if (lower.equals("in"))
1117    { // insert
1118
// we don't care about auto-generated keys here
1119
AbstractWriteRequest wr = new InsertRequest(sql, escapeProcessing,
1120          timeout, "\n", false /* isRead */);
1121      wr.setLogin(user);
1122      if (logger.isDebugEnabled())
1123        logger.debug("insert request: " + sql);
1124      setDriverProcessedAndSkeleton(wr);
1125      if (transactionId != 0)
1126      {
1127        wr.setIsAutoCommit(false);
1128        wr.setTransactionId(transactionId);
1129      }
1130      else
1131        wr.setIsAutoCommit(true);
1132      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1133    }
1134    else if (lower.equals("up"))
1135    { // update
1136
AbstractWriteRequest wr = new UpdateRequest(sql, escapeProcessing,
1137          timeout, "\n");
1138      wr.setLogin(user);
1139      setDriverProcessedAndSkeleton(wr);
1140      if (logger.isDebugEnabled())
1141        logger.debug("update request: " + sql);
1142      if (transactionId != 0)
1143      {
1144        wr.setIsAutoCommit(false);
1145        wr.setTransactionId(transactionId);
1146      }
1147      else
1148        wr.setIsAutoCommit(true);
1149      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1150    }
1151    else if (lower.equals("de"))
1152    { // delete
1153
AbstractWriteRequest wr = new DeleteRequest(sql, escapeProcessing,
1154          timeout, "\n");
1155      wr.setLogin(user);
1156      setDriverProcessedAndSkeleton(wr);
1157      if (logger.isDebugEnabled())
1158        logger.debug("delete request: " + sql);
1159      if (transactionId != 0)
1160      {
1161        wr.setIsAutoCommit(false);
1162        wr.setTransactionId(transactionId);
1163      }
1164      else
1165        wr.setIsAutoCommit(true);
1166      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1167    }
1168    else if (lower.equals("cr"))
1169    { // create
1170
AbstractWriteRequest wr = new CreateRequest(sql, escapeProcessing,
1171          timeout, "\n");
1172      wr.setLogin(user);
1173      setDriverProcessedAndSkeleton(wr);
1174      if (logger.isDebugEnabled())
1175        logger.debug("create request: " + sql);
1176      if (transactionId != 0)
1177      {
1178        wr.setIsAutoCommit(false);
1179        wr.setTransactionId(transactionId);
1180      }
1181      else
1182        wr.setIsAutoCommit(true);
1183      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1184    }
1185    else if (lower.equals("al"))
1186    { // alter
1187
AbstractWriteRequest wr = new AlterRequest(sql, escapeProcessing,
1188          timeout, "\n");
1189      wr.setLogin(user);
1190      setDriverProcessedAndSkeleton(wr);
1191      if (logger.isDebugEnabled())
1192        logger.debug("alter request: " + sql);
1193      if (transactionId != 0)
1194      {
1195        wr.setIsAutoCommit(false);
1196        wr.setTransactionId(transactionId);
1197      }
1198      else
1199        wr.setIsAutoCommit(true);
1200      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1201    }
1202    else if (lower.equals("dr"))
1203    { // drop
1204
AbstractWriteRequest wr = new DropRequest(sql, escapeProcessing, timeout,
1205          "\n");
1206      wr.setLogin(user);
1207      setDriverProcessedAndSkeleton(wr);
1208      if (logger.isDebugEnabled())
1209        logger.debug("drop request: " + sql);
1210      if (transactionId != 0)
1211      {
1212        wr.setIsAutoCommit(false);
1213        wr.setTransactionId(transactionId);
1214      }
1215      else
1216        wr.setIsAutoCommit(true);
1217      task = new RecoveryTask(transactionId, id, new WriteRequestTask(1, 1, wr));
1218    }
1219    else if (lower.equals("be"))
1220    { // begin
1221
task = new RecoveryTask(transactionId, id, new BeginTask(1, 1,
1222          (long) timeout * 1000, user, transactionId));
1223      if (logger.isDebugEnabled())
1224        logger.debug("begin transaction: " + transactionId);
1225    }
1226    else if (lower.equals("co"))
1227    { // commit
1228
task = new RecoveryTask(transactionId, id, new CommitTask(1, 1,
1229          (long) timeout * 1000, user, transactionId));
1230      if (logger.isDebugEnabled())
1231        logger.debug("commit transaction: " + transactionId);
1232    }
1233    else if (lower.equals("ro"))
1234    { // rollback
1235
int index = sql.indexOf(' ');
1236      if (index == -1)
1237      {
1238        task = new RecoveryTask(transactionId, id, new RollbackTask(1, 1,
1239            (long) timeout * 1000, user, transactionId));
1240        if (logger.isDebugEnabled())
1241          logger.debug("rollback transaction: " + transactionId);
1242      }
1243      else
1244      {
1245        String JavaDoc savepointName = sql.substring(index);
1246        task = new RecoveryTask(transactionId, id, new RollbackToSavepointTask(
1247            1, 1, (long) timeout * 1000, user, transactionId, savepointName));
1248        if (logger.isDebugEnabled())
1249          logger.debug("rollback transaction to savepoint: " + transactionId);
1250      }
1251    }
1252    else if (lower.equals("sa"))
1253    { // set savepoint
1254
String JavaDoc savepointName = sql.substring(sql.indexOf(' '));
1255      task = new RecoveryTask(transactionId, id, new SavepointTask(1, 1,
1256          (long) timeout * 1000, user, transactionId, savepointName));
1257      if (logger.isDebugEnabled())
1258        logger.debug("transaction set savepoint: " + transactionId);
1259    }
1260    else if (lower.equals("re"))
1261    { // release savepoint
1262
String JavaDoc savepointName = sql.substring(sql.indexOf(' '));
1263      task = new RecoveryTask(transactionId, id, new ReleaseSavepointTask(1, 1,
1264          (long) timeout * 1000, user, transactionId, savepointName));
1265      if (logger.isDebugEnabled())
1266        logger.debug("transaction release savepoint: " + transactionId);
1267    }
1268    else if (lower.equals("{c"))
1269    { // read stored procedure call "{call ...}"
1270
StoredProcedure proc = new StoredProcedure(sql, escapeProcessing,
1271          timeout, "\n", true /* isRead */);
1272      proc.setLogin(user);
1273      setDriverProcessedAndSkeleton(proc);
1274      if (logger.isDebugEnabled())
1275        logger.debug("read stored procedure call: " + sql);
1276      if (transactionId != 0)
1277      {
1278        proc.setIsAutoCommit(false);
1279        proc.setTransactionId(transactionId);
1280      }
1281      else
1282        proc.setIsAutoCommit(true);
1283      task = new RecoveryTask(transactionId, id, new ReadStoredProcedureTask(1,
1284          1, proc, null));
1285    }
1286    else if (lower.equals("}c"))
1287    { // write stored procedure call,
1288
// we must replace "}call ...}" with "{call ...}"
1289
StringBuffer JavaDoc writeCall = new StringBuffer JavaDoc(sql);
1290      writeCall.setCharAt(0, '{');
1291      StoredProcedure proc = new StoredProcedure(writeCall.toString(),
1292          escapeProcessing, timeout, "\n", false /* isRead */);
1293      proc.setLogin(user);
1294      setDriverProcessedAndSkeleton(proc);
1295      if (logger.isDebugEnabled())
1296        logger.debug("write stored procedure call: " + sql);
1297      if (transactionId != 0)
1298      {
1299        proc.setIsAutoCommit(false);
1300        proc.setTransactionId(transactionId);
1301      }
1302      else
1303        proc.setIsAutoCommit(true);
1304      task = new RecoveryTask(transactionId, id, new WriteStoredProcedureTask(
1305          1, 1, proc));
1306    }
1307    else
1308      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.sql.unkwown", sql));
1309    return task;
1310  }
1311
1312  //
1313
//
1314
// Checkpoint Management
1315
//
1316
//
1317

1318  /**
1319   * Deletes recovery log entries that are older than specified checkpoint.
1320   * Entries are deleted directly into the recovery log database, as opposed to
1321   * via a post to the logger thread (as resetLogTableIdAndDeleteRecoveryLog).
1322   *
1323   * @param checkpointName the name of the checkpoint uptil which log entries
1324   * should be removed
1325   * @throws SQLException in case of error.
1326   */

1327  public void deleteLogEntriesBeforeCheckpoint(String JavaDoc checkpointName)
1328      throws SQLException JavaDoc
1329  {
1330    long id = getCheckpointRequestId(checkpointName);
1331    PreparedStatement JavaDoc stmt = null;
1332    try
1333    {
1334      stmt = getDatabaseConnection().prepareStatement(
1335          "DELETE FROM " + getLogTableName() + " WHERE id<=?");
1336      stmt.setLong(1, id);
1337      stmt.executeUpdate();
1338    }
1339    catch (SQLException JavaDoc e)
1340    {
1341      // TODO: Check error message below
1342
throw new SQLException JavaDoc(Translate.get(
1343          "recovery.jdbc.transaction.remove.failed", new String JavaDoc[]{
1344              String.valueOf(id), e.getMessage()}));
1345    }
1346    finally
1347    {
1348      try
1349      {
1350        if (stmt != null)
1351          stmt.close();
1352      }
1353      catch (Exception JavaDoc ignore)
1354      {
1355      }
1356    }
1357  }
1358
1359  /**
1360   * Returns an array of names of all the checkpoint available in the recovery
1361   * log
1362   *
1363   * @return <code>ArrayList</code> of <code>String</code> checkpoint names
1364   * @throws SQLException if fails
1365   */

1366  public ArrayList JavaDoc getCheckpointNames() throws SQLException JavaDoc
1367  {
1368    PreparedStatement JavaDoc stmt = null;
1369
1370    waitForLogQueueToEmpty();
1371
1372    try
1373    {
1374      if (logger.isDebugEnabled())
1375        logger.debug("Retrieving checkpoint names list");
1376      stmt = getDatabaseConnection().prepareStatement(
1377          "SELECT name from " + checkpointTableName);
1378      ResultSet JavaDoc rs = stmt.executeQuery();
1379      ArrayList JavaDoc list = new ArrayList JavaDoc();
1380      while (rs.next())
1381      {
1382        list.add(rs.getString(1));
1383      }
1384      rs.close();
1385      return list;
1386    }
1387    catch (Exception JavaDoc e)
1388    {
1389      invalidateInternalConnection();
1390      throw new SQLException JavaDoc(Translate.get(
1391          "recovery.jdbc.checkpoint.list.failed", e));
1392    }
1393    finally
1394    {
1395      try
1396      {
1397        if (stmt != null)
1398          stmt.close();
1399      }
1400      catch (SQLException JavaDoc ignore)
1401      {
1402      }
1403    }
1404  }
1405
1406  /**
1407   * Get the request id corresponding to a given checkpoint. This is the first
1408   * step in a recovery process. Following steps consist in calling
1409   * recoverNextRequest.
1410   *
1411   * @param checkpointName Name of the checkpoint
1412   * @return int the request identifier corresponding to this checkpoint.
1413   * @exception SQLException if an error occurs
1414   * @see #recoverNextRequest(long)
1415   */

1416  public long getCheckpointRequestId(String JavaDoc checkpointName) throws SQLException JavaDoc
1417  {
1418    long requestId = -1;
1419    PreparedStatement JavaDoc stmt = null;
1420    ResultSet JavaDoc rs = null;
1421    try
1422    {
1423      stmt = getDatabaseConnection().prepareStatement(
1424          "SELECT request_id FROM " + checkpointTableName
1425              + " WHERE name LIKE ?");
1426      stmt.setString(1, checkpointName);
1427      rs = stmt.executeQuery();
1428
1429      if (rs.next())
1430        requestId = rs.getLong("request_id");
1431      else
1432      {
1433        String JavaDoc msg = Translate.get("recovery.jdbc.checkpoint.not.found",
1434            checkpointName);
1435        logger.info(msg);
1436        throw new SQLException JavaDoc(msg);
1437      }
1438    }
1439    catch (SQLException JavaDoc e)
1440    {
1441      invalidateInternalConnection();
1442      throw new SQLException JavaDoc(Translate.get(
1443          "recovery.jdbc.checkpoint.not.found.error", new String JavaDoc[]{
1444              checkpointName, e.getMessage()}));
1445    }
1446    finally
1447    {
1448      try
1449      {
1450        if (rs != null)
1451          rs.close();
1452      }
1453      catch (Exception JavaDoc ignore)
1454      {
1455      }
1456      try
1457      {
1458        if (stmt != null)
1459          stmt.close();
1460      }
1461      catch (Exception JavaDoc ignore)
1462      {
1463      }
1464    }
1465    return requestId;
1466  }
1467
1468  /**
1469   * Remove a checkpoint from the recovery. This is useful for recovery
1470   * maintenant
1471   *
1472   * @param checkpointName to remove
1473   * @throws SQLException if an error occurs
1474   */

1475  public void removeCheckpoint(String JavaDoc checkpointName) throws SQLException JavaDoc
1476  {
1477    PreparedStatement JavaDoc stmt = null;
1478
1479    waitForLogQueueToEmpty();
1480
1481    try
1482    {
1483      // 1. Get the reference point to delete
1484
stmt = getDatabaseConnection().prepareStatement(
1485          "SELECT * FROM " + checkpointTableName + " WHERE name LIKE ?");
1486      stmt.setString(1, checkpointName);
1487      ResultSet JavaDoc rs = stmt.executeQuery();
1488      boolean checkpointExists = rs.next();
1489      if (!checkpointExists)
1490      {
1491        rs.close();
1492        stmt.close();
1493        throw new SQLException JavaDoc("Checkpoint " + checkpointName
1494            + " does not exist");
1495      }
1496
1497      long requestId = rs.getLong("request_id");
1498      rs.close();
1499      stmt.close();
1500
1501      // Delete all entries below
1502
stmt = getDatabaseConnection().prepareStatement(
1503          "DELETE FROM " + logTableName + " WHERE id <= ?");
1504      stmt.setLong(1, requestId);
1505      stmt.executeUpdate();
1506      stmt.close();
1507
1508      // Delete checkpoint name
1509
stmt = getDatabaseConnection().prepareStatement(
1510          "DELETE FROM " + checkpointTableName + " WHERE name like ?");
1511      stmt.setString(1, checkpointName);
1512      stmt.executeUpdate();
1513      stmt.close();
1514    }
1515    catch (SQLException JavaDoc e)
1516    {
1517      invalidateInternalConnection();
1518      throw new SQLException JavaDoc(Translate.get(
1519          "recovery.jdbc.checkpoint.remove.failed", new String JavaDoc[]{
1520              checkpointName, e.getMessage()}));
1521    }
1522    finally
1523    {
1524      try
1525      {
1526        if (stmt != null)
1527          stmt.close();
1528      }
1529      catch (Exception JavaDoc ignore)
1530      {
1531      }
1532
1533    }
1534  }
1535
1536  /**
1537   * Store the state of the backend in the recovery log
1538   *
1539   * @param databaseName the virtual database name
1540   * @param backendRecoveryInfo the backend recovery information to store
1541   * @throws SQLException if cannot proceed
1542   */

1543  public void storeBackendRecoveryInfo(String JavaDoc databaseName,
1544      BackendRecoveryInfo backendRecoveryInfo) throws SQLException JavaDoc
1545  {
1546    PreparedStatement JavaDoc stmt = null;
1547    PreparedStatement JavaDoc stmt2 = null;
1548    if ((backendRecoveryInfo.getCheckpoint() == null)
1549        || ((backendRecoveryInfo.getBackendState() != BackendState.DISABLED) && (backendRecoveryInfo
1550            .getBackendState() != BackendState.UNKNOWN)))
1551      backendRecoveryInfo.setCheckpoint(""); // No checkpoint
1552
else
1553    { // Check checkpoint name validity
1554
getCheckpointRequestId(backendRecoveryInfo.getCheckpoint());
1555    }
1556
1557    try
1558    {
1559      // 1. Get the reference point to delete
1560
stmt = getDatabaseConnection().prepareStatement(
1561          "SELECT * FROM " + backendTableName
1562              + " WHERE backend_name LIKE ? and database_name LIKE ?");
1563      stmt.setString(1, backendRecoveryInfo.getBackendName());
1564      stmt.setString(2, databaseName);
1565      ResultSet JavaDoc rs = stmt.executeQuery();
1566      boolean mustUpdate = rs.next();
1567      rs.close();
1568      if (!mustUpdate)
1569      {
1570        stmt2 = getDatabaseConnection().prepareStatement(
1571            "INSERT INTO " + backendTableName + " values(?,?,?,?)");
1572        stmt2.setString(1, databaseName);
1573        stmt2.setString(2, backendRecoveryInfo.getBackendName());
1574        stmt2.setInt(3, backendRecoveryInfo.getBackendState());
1575        stmt2.setString(4, backendRecoveryInfo.getCheckpoint());
1576        if (stmt2.executeUpdate() != 1)
1577          throw new SQLException JavaDoc(
1578              "Error while inserting new backend reference. Incorrect number of rows");
1579      }
1580      else
1581      {
1582        stmt2 = getDatabaseConnection()
1583            .prepareStatement(
1584                "UPDATE "
1585                    + backendTableName
1586                    + " set backend_state=?,checkpoint_name=? where backend_name=? and database_name=?");
1587        stmt2.setInt(1, backendRecoveryInfo.getBackendState());
1588        stmt2.setString(2, backendRecoveryInfo.getCheckpoint());
1589        stmt2.setString(3, backendRecoveryInfo.getBackendName());
1590        stmt2.setString(4, databaseName);
1591        if (stmt2.executeUpdate() != 1)
1592          throw new SQLException JavaDoc(
1593              "Error while updating backend reference. Incorrect number of rows");
1594      }
1595    }
1596    catch (SQLException JavaDoc e)
1597    {
1598      invalidateInternalConnection();
1599
1600      logger.warn("Failed to store backend recovery info", e);
1601
1602      throw new SQLException JavaDoc("Unable to update checkpoint '"
1603          + backendRecoveryInfo.getCheckpoint() + "' for backend:"
1604          + backendRecoveryInfo.getBackendName());
1605    }
1606    finally
1607    {
1608      try
1609      {
1610        if (stmt != null)
1611          stmt.close();
1612      }
1613      catch (Exception JavaDoc ignore)
1614      {
1615      }
1616      try
1617      {
1618        if (stmt2 != null)
1619          stmt2.close();
1620      }
1621      catch (Exception JavaDoc ignore)
1622      {
1623      }
1624    }
1625  }
1626
1627  /**
1628   * Store a Checkpoint using the current log state.
1629   *
1630   * @param checkpointName Name of the checkpoint
1631   * @exception SQLException if an error occurs
1632   */

1633  public void storeCheckpoint(String JavaDoc checkpointName) throws SQLException JavaDoc
1634  {
1635    storeCheckpoint(checkpointName, logTableId);
1636  }
1637
1638  /**
1639   * Store a Checkpoint using the given request id.
1640   *
1641   * @param checkpointName Name of the checkpoint
1642   * @param requestId request identifier
1643   * @exception SQLException if an error occurs
1644   */

1645  public void storeCheckpoint(String JavaDoc checkpointName, long requestId)
1646      throws SQLException JavaDoc
1647  {
1648    PreparedStatement JavaDoc stmt = null;
1649
1650    // Check if a checkpoint with the name checkpointName already exists
1651
if (!validCheckpointName(checkpointName))
1652    {
1653      throw new SQLException JavaDoc(Translate.get(
1654          "recovery.jdbc.checkpoint.duplicate", checkpointName));
1655    }
1656
1657    waitForLogQueueToEmpty();
1658
1659    try
1660    {
1661      if (logger.isDebugEnabled())
1662        logger.debug("Storing checkpoint " + checkpointName + " at request id "
1663            + requestId);
1664      stmt = getDatabaseConnection().prepareStatement(
1665          "INSERT INTO " + checkpointTableName + " VALUES(?,?)");
1666      stmt.setString(1, checkpointName);
1667      stmt.setLong(2, requestId);
1668      stmt.executeUpdate();
1669    }
1670    catch (SQLException JavaDoc e)
1671    {
1672      invalidateInternalConnection();
1673      throw new SQLException JavaDoc(Translate.get(
1674          "recovery.jdbc.checkpoint.store.failed", new String JavaDoc[]{checkpointName,
1675              e.getMessage()}));
1676    }
1677    finally
1678    {
1679      try
1680      {
1681        if (stmt != null)
1682          stmt.close();
1683      }
1684      catch (Exception JavaDoc ignore)
1685      {
1686      }
1687    }
1688  }
1689
1690  //
1691
//
1692
// Dump management
1693
//
1694
//
1695

1696  /**
1697   * Get the DumpInfo element corresponding to the given dump name. Returns null
1698   * if no information is found for this dump name.
1699   *
1700   * @param dumpName the name of the dump to look for
1701   * @return a <code>DumpInfo</code> object or null if not found in the table
1702   * @throws SQLException if a recovery log database access error occurs
1703   */

1704  public DumpInfo getDumpInfo(String JavaDoc dumpName) throws SQLException JavaDoc
1705  {
1706    PreparedStatement JavaDoc stmt = null;
1707
1708    try
1709    {
1710      if (logger.isDebugEnabled())
1711        logger.debug("Retrieving dump " + dumpName + " information");
1712      stmt = getDatabaseConnection().prepareStatement(
1713          "SELECT * from " + dumpTableName + " WHERE dump_name LIKE ?");
1714      stmt.setString(1, dumpName);
1715
1716      ResultSet JavaDoc rs = stmt.executeQuery();
1717      DumpInfo dumpInfo = null;
1718      if (rs.next())
1719      {
1720        dumpInfo = new DumpInfo(rs.getString("dump_name"), rs
1721            .getString("dump_date"), rs.getString("dump_path"), rs
1722            .getString("dump_format"), rs.getString("checkpoint_name"), rs
1723            .getString("backend_name"), rs.getString(dumpTableTablesColumnName));
1724      }
1725      // else not found, return dumpInfo=null;
1726

1727      rs.close();
1728      return dumpInfo;
1729    }
1730    catch (Exception JavaDoc e)
1731    {
1732      invalidateInternalConnection();
1733      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.info.failed",
1734          new String JavaDoc[]{dumpName, e.getMessage()}));
1735    }
1736    finally
1737    {
1738      try
1739      {
1740        if (stmt != null)
1741          stmt.close();
1742      }
1743      catch (SQLException JavaDoc ignore)
1744      {
1745      }
1746    }
1747  }
1748
1749  /**
1750   * Retrieve the list of available dumps.
1751   *
1752   * @return an <code>ArrayList</code> of <code>DumpInfo</code> objects
1753   * @throws SQLException if a recovery log database access error occurs
1754   */

1755  public ArrayList JavaDoc getDumpList() throws SQLException JavaDoc
1756  {
1757    PreparedStatement JavaDoc stmt = null;
1758
1759    try
1760    {
1761      if (logger.isDebugEnabled())
1762        logger.debug("Retrieving dump list");
1763      stmt = getDatabaseConnection().prepareStatement(
1764          "SELECT * FROM " + dumpTableName + " ORDER BY dump_date DESC");
1765      ResultSet JavaDoc rs = stmt.executeQuery();
1766      ArrayList JavaDoc list = new ArrayList JavaDoc();
1767      while (rs.next())
1768      {
1769        list
1770            .add(new DumpInfo(rs.getString("dump_name"), rs
1771                .getString("dump_date"), rs.getString("dump_path"), rs
1772                .getString("dump_format"), rs.getString("checkpoint_name"), rs
1773                .getString("backend_name"), rs
1774                .getString(dumpTableTablesColumnName)));
1775      }
1776      rs.close();
1777      return list;
1778    }
1779    catch (Exception JavaDoc e)
1780    {
1781      invalidateInternalConnection();
1782      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.list.failed", e));
1783    }
1784    finally
1785    {
1786      try
1787      {
1788        if (stmt != null)
1789          stmt.close();
1790      }
1791      catch (SQLException JavaDoc ignore)
1792      {
1793      }
1794    }
1795  }
1796
1797  /**
1798   * Remove a dump information from the dump table base.
1799   *
1800   * @param dumpInfo the <code>DumpInfo</code> to remove
1801   * @throws SQLException if the dump has has not been removed from the dump
1802   * table
1803   */

1804  public void removeDump(DumpInfo dumpInfo) throws SQLException JavaDoc
1805  {
1806    PreparedStatement JavaDoc stmt = null;
1807
1808    try
1809    {
1810      if (logger.isDebugEnabled())
1811      {
1812        logger.debug("removing dump " + dumpInfo.getDumpName());
1813      }
1814      stmt = getDatabaseConnection().prepareStatement(
1815          "DELETE FROM " + dumpTableName + " WHERE dump_name=?");
1816      stmt.setString(1, dumpInfo.getDumpName());
1817
1818      stmt.executeUpdate();
1819    }
1820    catch (Exception JavaDoc e)
1821    {
1822      invalidateInternalConnection();
1823      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.remove.failed",
1824          new String JavaDoc[]{dumpInfo.getDumpName(), e.getMessage()}));
1825    }
1826    finally
1827    {
1828      try
1829      {
1830        if (stmt != null)
1831          stmt.close();
1832      }
1833      catch (SQLException JavaDoc ignore)
1834      {
1835      }
1836    }
1837  }
1838
1839  /**
1840   * Set DumpInfo, thereby making a new dump available for restore.
1841   *
1842   * @param dumpInfo the dump info to create.
1843   * @throws VirtualDatabaseException if an error occurs
1844   */

1845  public void setDumpInfo(DumpInfo dumpInfo) throws VirtualDatabaseException
1846  {
1847    try
1848    {
1849      storeDump(dumpInfo);
1850    }
1851    catch (SQLException JavaDoc e)
1852    {
1853      throw new VirtualDatabaseException(e);
1854    }
1855  }
1856
1857  /**
1858   * Store the given dump information in the dump table
1859   *
1860   * @param dump the <code>DumpInfo</code> to store
1861   * @throws SQLException if a recovery log database access error occurs
1862   */

1863  public void storeDump(DumpInfo dump) throws SQLException JavaDoc
1864  {
1865    PreparedStatement JavaDoc stmt = null;
1866
1867    if (dump == null)
1868      throw new NullPointerException JavaDoc(
1869          "Invalid null dump in JDBCRecoverylog.storeDump");
1870
1871    try
1872    {
1873      if (logger.isDebugEnabled())
1874        logger.debug("Storing dump " + dump.getDumpName());
1875      stmt = getDatabaseConnection().prepareStatement(
1876          "INSERT INTO " + dumpTableName + " VALUES (?,?,?,?,?,?,?)");
1877      stmt.setString(1, dump.getDumpName());
1878      stmt.setString(2, dump.getDumpDate());
1879      stmt.setString(3, dump.getDumpPath());
1880      stmt.setString(4, dump.getDumpFormat());
1881      stmt.setString(5, dump.getCheckpointName());
1882      stmt.setString(6, dump.getBackendName());
1883      stmt.setString(7, dump.getTables());
1884
1885      stmt.executeUpdate();
1886    }
1887    catch (Exception JavaDoc e)
1888    {
1889      invalidateInternalConnection();
1890      throw new SQLException JavaDoc(Translate.get("recovery.jdbc.dump.store.failed",
1891          new String JavaDoc[]{dump.getDumpName(), e.getMessage()}));
1892    }
1893    finally
1894    {
1895      try
1896      {
1897        if (stmt != null)
1898          stmt.close();
1899      }
1900      catch (SQLException JavaDoc ignore)
1901      {
1902      }
1903    }
1904  }
1905
1906  /**
1907   * Update the path name for a given checkpoint.
1908   *
1909   * @param dumpName the dump name
1910   * @param newPath the new path to set
1911   * @return if the dump path was successfully updated, false if it failed
1912   * (usually because the dump does not exist)
1913   * @throws SQLException if a recovery log database access error occurs
1914   */

1915  public boolean updateDumpPath(String JavaDoc dumpName, String JavaDoc newPath)
1916      throws SQLException JavaDoc
1917  {
1918    DumpInfo dumpInfo = getDumpInfo(dumpName);
1919    if (dumpInfo == null)
1920      return false;
1921
1922    PreparedStatement JavaDoc stmt = null;
1923
1924    try
1925    {
1926      if (logger.isDebugEnabled())
1927        logger.debug("Changing old path " + dumpInfo.getDumpPath()
1928            + " for dump " + dumpInfo.getDumpName() + " to " + newPath);
1929      stmt = getDatabaseConnection().prepareStatement(
1930          "UPDATE " + dumpTableName + " SET dump_path=? WHERE dump_name=?");
1931      stmt.setString(1, newPath);
1932      stmt.setString(2, dumpName);
1933
1934      return stmt.executeUpdate() == 1;
1935    }
1936    catch (Exception JavaDoc e)
1937    {
1938      invalidateInternalConnection();
1939      throw new SQLException JavaDoc(Translate.get(
1940          "recovery.jdbc.dump.update.path.failed", new String JavaDoc[]{dumpName,
1941              e.getMessage()}));
1942    }
1943    finally
1944    {
1945      try
1946      {
1947        if (stmt != null)
1948          stmt.close();
1949      }
1950      catch (SQLException JavaDoc ignore)
1951      {
1952      }
1953    }
1954  }
1955
1956  //
1957
//
1958
// Recovery log database tables management
1959
//
1960
//
1961

1962  /**
1963   * Checks if the recovery log and checkpoint tables exist, and create them if
1964   * they do not exist. This method also starts the logger thread.
1965   */

1966  public void checkRecoveryLogTables()
1967  {
1968    try
1969    {
1970      intializeDatabase();
1971    }
1972    catch (SQLException JavaDoc e)
1973    {
1974      throw new RuntimeException JavaDoc("Unable to initialize the database: " + e);
1975    }
1976
1977    // Start the logger thread
1978
loggerThread = new LoggerThread(this);
1979    loggerThread.start();
1980  }
1981
1982  /**
1983   * Returns the backendTableName value.
1984   *
1985   * @return Returns the backendTableName.
1986   */

1987  public String JavaDoc getBackendTableName()
1988  {
1989    return backendTableName;
1990  }
1991
1992  /**
1993   * Returns the checkpointTableName value.
1994   *
1995   * @return Returns the checkpointTableName.
1996   */

1997  public String JavaDoc getCheckpointTableName()
1998  {
1999    return checkpointTableName;
2000  }
2001
2002  /**
2003   * Returns the logTableName value.
2004   *
2005   * @return Returns the logTableName.
2006   */

2007  public String JavaDoc getLogTableName()
2008  {
2009    return logTableName;
2010  }
2011
2012  /**
2013   * Returns the logTableSqlColumnName value.
2014   *
2015   * @return Returns the logTableSqlColumnName.
2016   */

2017  public String JavaDoc getLogTableSqlColumnName()
2018  {
2019    return logTableSqlColumnName;
2020  }
2021
2022  /**
2023   * Sets the backend table create statement
2024   *
2025   * @param createTable statement to create the table
2026   * @param tableName the backend table name
2027   * @param checkpointNameType type for the checkpointName column
2028   * @param backendNameType type for the backendName column
2029   * @param backendStateType type for the backendState column
2030   * @param databaseNameType type for the databaseName column
2031   * @param extraStatement like primary keys
2032   */

2033  public void setBackendTableCreateStatement(String JavaDoc createTable,
2034      String JavaDoc tableName, String JavaDoc checkpointNameType, String JavaDoc backendNameType,
2035      String JavaDoc backendStateType, String JavaDoc databaseNameType, String JavaDoc extraStatement)
2036  {
2037    this.backendTableCreateTable = createTable;
2038    this.backendTableName = tableName;
2039    this.backendTableDatabaseName = databaseNameType;
2040    this.backendTableBackendName = backendNameType;
2041    this.backendTableBackendState = backendStateType;
2042    this.backendTableCheckpointName = checkpointNameType;
2043    this.backendTableExtraStatement = extraStatement;
2044    this.backendTableCreateStatement = createTable + " " + backendTableName
2045        + " (database_name " + databaseNameType + ", backend_name "
2046        + backendNameType + ",backend_state " + backendStateType
2047        + ", checkpoint_name " + checkpointNameType + " " + extraStatement
2048        + ")";
2049
2050    if (logger.isDebugEnabled())
2051      logger.debug(Translate.get("recovery.jdbc.backendtable.statement",
2052          backendTableCreateStatement));
2053  }
2054
2055  /**
2056   * Sets the checkpoint table name and create statement.
2057   *
2058   * @param createTable statement to create the table
2059   * @param tableName name of the checkpoint table.
2060   * @param nameType type for the name column
2061   * @param requestIdType type for the requestId column
2062   * @param extraStatement like primary keys
2063   */

2064  public void setCheckpointTableCreateStatement(String JavaDoc createTable,
2065      String JavaDoc tableName, String JavaDoc nameType, String JavaDoc requestIdType,
2066      String JavaDoc extraStatement)
2067  {
2068    this.checkpointTableCreateTable = createTable;
2069    this.checkpointTableName = tableName;
2070    this.checkpointTableNameType = nameType;
2071    this.checkpointTableRequestIdType = requestIdType;
2072    this.checkpointTableExtraStatement = extraStatement;
2073    // CREATE TABLE tableName (
2074
// name checkpointNameColumnType,
2075
// request_id requestIdColumnType,
2076
// extraStatement)
2077

2078    checkpointTableCreateStatement = createTable + " " + tableName + " (name "
2079        + nameType + ",request_id " + requestIdType + extraStatement + ")";
2080    if (logger.isDebugEnabled())
2081      logger.debug(Translate.get("recovery.jdbc.checkpointtable.statement",
2082          checkpointTableCreateStatement));
2083  }
2084
2085  /**
2086   * Sets the dump table name and create statement.
2087   *
2088   * @param createTable statement to create the table
2089   * @param tableName name of the checkpoint table.
2090   * @param dumpNameColumnType the dump name column type
2091   * @param dumpDateColumnType the dump data column type
2092   * @param dumpPathColumnType the dump path column type
2093   * @param dumpFormatColumnType the dump tpe column type
2094   * @param checkpointNameColumnType the checkpoint name column type
2095   * @param backendNameColumnType the backend name column type
2096   * @param tablesColumnName the database tables column name
2097   * @param tablesColumnType the database tables column type
2098   * @param extraStatement like primary keys
2099   */

2100  public void setDumpTableCreateStatement(String JavaDoc createTable, String JavaDoc tableName,
2101      String JavaDoc dumpNameColumnType, String JavaDoc dumpDateColumnType,
2102      String JavaDoc dumpPathColumnType, String JavaDoc dumpFormatColumnType,
2103      String JavaDoc checkpointNameColumnType, String JavaDoc backendNameColumnType,
2104      String JavaDoc tablesColumnName, String JavaDoc tablesColumnType, String JavaDoc extraStatement)
2105  {
2106    this.dumpTableCreateTable = createTable;
2107    this.dumpTableName = tableName;
2108    this.dumpTableDumpNameColumnType = dumpNameColumnType;
2109    this.dumpTableDumpDateColumnType = dumpDateColumnType;
2110    this.dumpTableDumpPathColumnType = dumpPathColumnType;
2111    this.dumpTableDumpFormatColumnType = dumpFormatColumnType;
2112    this.dumpTableCheckpointNameColumnType = checkpointNameColumnType;
2113    this.dumpTableBackendNameColumnType = backendNameColumnType;
2114    this.dumpTableTablesColumnName = tablesColumnName;
2115    this.dumpTableTablesColumnType = tablesColumnType;
2116    this.dumpTableExtraStatementDefinition = extraStatement;
2117
2118    // CREATE TABLE DumpTable (
2119
// dump_name TEXT NOT NULL,
2120
// dump_date DATE,
2121
// dump_path TEXT NOT NULL,
2122
// dump_type TEXT NOT NULL,
2123
// checkpoint_name TEXT NOT NULL,
2124
// backend_name TEXT NOT NULL,
2125
// tables TEXT NOT NULL
2126
// )
2127

2128    dumpTableCreateStatement = dumpTableCreateTable + " " + dumpTableName
2129        + " (dump_name " + dumpTableDumpNameColumnType + ",dump_date "
2130        + dumpDateColumnType + ",dump_path " + dumpPathColumnType
2131        + ",dump_format " + dumpFormatColumnType + ",checkpoint_name "
2132        + checkpointNameColumnType + ",backend_name " + backendNameColumnType
2133        + "," + dumpTableTablesColumnName + " " + tablesColumnType
2134        + extraStatement + ")";
2135    if (logger.isDebugEnabled())
2136      logger.debug(Translate.get("recovery.jdbc.dumptable.statement",
2137          dumpTableCreateStatement));
2138  }
2139
2140  /**
2141   * Sets the log table name and create statement.
2142   *
2143   * @param createTable statement to create the table
2144   * @param tableName name of the log table
2145   * @param idType type of the id column
2146   * @param vloginType type of the login column
2147   * @param sqlName name of the sql statement column
2148   * @param sqlType type of the sql column
2149   * @param transactionIdType type of the transaction column
2150   * @param extraStatement extra statement like primary keys ...
2151   */

2152  public void setLogTableCreateStatement(String JavaDoc createTable, String JavaDoc tableName,
2153      String JavaDoc idType, String JavaDoc vloginType, String JavaDoc sqlName, String JavaDoc sqlType,
2154      String JavaDoc transactionIdType, String JavaDoc extraStatement)
2155  {
2156    this.logTableCreateTable = createTable;
2157    this.logTableName = tableName;
2158    this.logTableIdType = idType;
2159    this.logTableVloginType = vloginType;
2160    this.logTableSqlColumnName = sqlName;
2161    this.logTableSqlType = sqlType;
2162    this.logTableTransactionIdType = transactionIdType;
2163    this.logTableExtraStatement = extraStatement;
2164    logTableCreateStatement = createTable + " " + tableName + " (id " + idType
2165        + ",vlogin " + vloginType + "," + logTableSqlColumnName + " " + sqlType
2166        + ",transaction_id " + transactionIdType + extraStatement + ")";
2167    if (logger.isDebugEnabled())
2168      logger.debug(Translate.get("recovery.jdbc.logtable.statement",
2169          logTableCreateStatement));
2170  }
2171
2172  //
2173
//
2174
// Log utility functions
2175
//
2176
//
2177

2178  /**
2179   * Recreate the skeleton of the query from the query itself with the param tag
2180   *
2181   * @param sql the sql query as it is recorded in the recovery log
2182   * @return the sql skelton with '?' instead of the <?...|...?>tags
2183   * @see org.objectweb.cjdbc.driver.PreparedStatement#setWithTag(int, String,
2184   * String)
2185   */

2186  private String JavaDoc recreateSkeleton(String JavaDoc sql)
2187  {
2188    // Here we have to rebuild the query skeleton to be able to call setXXX on
2189
// the PreparedStatement
2190
StringBuffer JavaDoc skeleton = new StringBuffer JavaDoc();
2191    int start = 0;
2192    int end;
2193    while ((end = sql.indexOf(
2194        org.objectweb.cjdbc.driver.PreparedStatement.START_PARAM_TAG, start)) != -1)
2195    {
2196      skeleton.append(sql.substring(start, end)).append('?');
2197      start = sql.indexOf(
2198          org.objectweb.cjdbc.driver.PreparedStatement.END_PARAM_TAG, end);
2199      if (start == -1)
2200        throw new RuntimeException JavaDoc("Malformed query in recovery log: " + sql);
2201      else
2202        start += org.objectweb.cjdbc.driver.PreparedStatement.END_PARAM_TAG
2203            .length();
2204    }
2205    if (start < sql.length())
2206      skeleton.append(sql.substring(start));
2207    return skeleton.toString();
2208  }
2209
2210  /**
2211   * Set the driverProcessed flag of the given request according to its SQL
2212   * content and rebuild the SQL skeleton if necessary.
2213   *
2214   * @param request Request to process
2215   */

2216  private void setDriverProcessedAndSkeleton(AbstractRequest request)
2217  {
2218    String JavaDoc sql = request.getSQL();
2219    boolean isDriverProcessed = sql
2220        .indexOf(org.objectweb.cjdbc.driver.PreparedStatement.END_PARAM_TAG) == -1;
2221    request.setDriverProcessed(isDriverProcessed);
2222    if (isDriverProcessed)
2223      return; // No need to set the skeleton
2224

2225    request.setSqlSkeleton(recreateSkeleton(sql));
2226  }
2227
2228  /**
2229   * Checks if a checkpoint with the name checkpointName is already stored in
2230   * the database.
2231   *
2232   * @param checkpointName name of the checkpoint.
2233   * @return ResultSet empty if no checkpoint was found.
2234   */

2235  private boolean validCheckpointName(String JavaDoc checkpointName)
2236      throws SQLException JavaDoc
2237  {
2238    PreparedStatement JavaDoc stmt = null;
2239    ResultSet JavaDoc rs = null;
2240    try
2241    {
2242      stmt = getDatabaseConnection().prepareStatement(
2243          "SELECT * FROM " + checkpointTableName + " WHERE name LIKE ?");
2244      stmt.setString(1, checkpointName);
2245      rs = stmt.executeQuery();
2246
2247      // If the query returned any rows, the checkpoint name is already
2248
// in use and therefore invalid.
2249
boolean checkpointExists = rs.next();
2250      rs.close();
2251      return !checkpointExists;
2252    }
2253    catch (SQLException JavaDoc e)
2254    {
2255      invalidateInternalConnection();
2256      throw new SQLException JavaDoc(Translate.get(
2257          "recovery.jdbc.checkpoint.check.failed", e));
2258    }
2259    finally
2260    {
2261      try
2262      {
2263        if (stmt != null)
2264          stmt.close();
2265      }
2266      catch (SQLException JavaDoc ignore)
2267      {
2268      }
2269    }
2270  }
2271
2272  /**
2273   * Synchronizes with the loggerThread, waiting for log requests to be flushed
2274   * to the recovery log.
2275   */

2276  private void waitForLogQueueToEmpty()
2277  {
2278    synchronized (loggerThread)
2279    {
2280      while (!loggerThread.getLogQueueIsEmpty())
2281      {
2282        try
2283        {
2284          loggerThread.wait();
2285        }
2286        catch (Exception JavaDoc e)
2287        {
2288          logger.warn("Exception " + e
2289              + " while waiting for end of transactions");
2290        }
2291      }
2292    }
2293  }
2294
2295  //
2296
//
2297
// Info/Monitoring/Debug related functions
2298
//
2299
//
2300

2301  /**
2302   * @see org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean#getAssociatedString()
2303   */

2304  public String JavaDoc getAssociatedString()
2305  {
2306    return "jdbcrecoverylog";
2307  }
2308
2309  /**
2310   * Allow to get the content of the recovery log for viewing
2311   *
2312   * @return <code>String[][]</code>
2313   * @see org.objectweb.cjdbc.console.views.RecoveryLogViewer
2314   */

2315  public String JavaDoc[][] getData()
2316  {
2317    Statement JavaDoc stmt = null;
2318    ResultSet JavaDoc rs = null;
2319    try
2320    {
2321      stmt = getDatabaseConnection().createStatement();
2322      rs = stmt.executeQuery("select * from " + logTableName);
2323      ArrayList JavaDoc list = new ArrayList JavaDoc();
2324      while (rs.next())
2325      {
2326        // 3: Query 2: User 1: ID 4: TID
2327
list.add(new String JavaDoc[]{rs.getString(3), rs.getString(2),
2328            rs.getString(1), rs.getString(4)});
2329      }
2330      String JavaDoc[][] result = new String JavaDoc[list.size()][4];
2331      for (int i = 0; i < list.size(); i++)
2332        result[i] = (String JavaDoc[]) list.get(i);
2333      return result;
2334    }
2335    catch (SQLException JavaDoc e)
2336    {
2337      return null;
2338    }
2339    finally
2340    {
2341      try
2342      {
2343        rs.close();
2344      }
2345      catch (SQLException JavaDoc ignore)
2346      {
2347      }
2348      try
2349      {
2350        stmt.close();
2351      }
2352      catch (SQLException JavaDoc ignore)
2353      {
2354      }
2355    }
2356  }
2357
2358  /**
2359   * @see org.objectweb.cjdbc.controller.recoverylog.AbstractRecoveryLog#getXmlImpl()
2360   */

2361  public String JavaDoc getXml()
2362  {
2363    StringBuffer JavaDoc info = new StringBuffer JavaDoc();
2364    info.append("<" + DatabasesXmlTags.ELT_RecoveryLog + " "
2365        + DatabasesXmlTags.ATT_driver + "=\"" + driverClassName + "\" "
2366        + DatabasesXmlTags.ATT_url + "=\"" + url + "\" ");
2367    if (driverName != null)
2368    {
2369      info.append(DatabasesXmlTags.ATT_driverPath + "=\"" + driverName + "\" ");
2370    }
2371    info.append(DatabasesXmlTags.ATT_login + "=\"" + login + "\" "
2372        + DatabasesXmlTags.ATT_password + "=\"" + password + "\" "
2373        + DatabasesXmlTags.ATT_requestTimeout + "=\"" + (timeout / 1000)
2374        + "\" " + DatabasesXmlTags.ATT_recoveryBatchSize + "=\""
2375        + recoveryBatchSize + "\">");
2376    // Recovery Log table
2377
info.append("<" + DatabasesXmlTags.ELT_RecoveryLogTable + " "
2378        + DatabasesXmlTags.ATT_createTable + "=\"" + logTableCreateTable
2379        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + logTableName + "\" "
2380        + DatabasesXmlTags.ATT_idColumnType + "=\"" + logTableIdType + "\" "
2381        + DatabasesXmlTags.ATT_vloginColumnType + "=\"" + logTableVloginType
2382        + "\" " + DatabasesXmlTags.ATT_sqlColumnType + "=\"" + logTableSqlType
2383        + "\" " + DatabasesXmlTags.ATT_transactionIdColumnType + "=\""
2384        + logTableTransactionIdType + "\" "
2385        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
2386        + logTableExtraStatement + "\"/>");
2387    // Checkpoint table
2388
info.append("<" + DatabasesXmlTags.ELT_CheckpointTable + " "
2389        + DatabasesXmlTags.ATT_createTable + "=\"" + checkpointTableCreateTable
2390        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + checkpointTableName
2391        + "\" " + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
2392        + checkpointTableNameType + "\" "
2393        + DatabasesXmlTags.ATT_requestIdColumnType + "=\""
2394        + checkpointTableRequestIdType + "\" "
2395        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
2396        + checkpointTableExtraStatement + "\"" + "/>");
2397    // BackendLog table
2398
info.append("<" + DatabasesXmlTags.ELT_BackendTable + " "
2399        + DatabasesXmlTags.ATT_createTable + "=\"" + backendTableCreateTable
2400        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + backendTableName
2401        + "\" " + DatabasesXmlTags.ATT_databaseNameColumnType + "=\""
2402        + backendTableDatabaseName + "\" "
2403        + DatabasesXmlTags.ATT_backendNameColumnType + "=\""
2404        + backendTableBackendName + "\" "
2405        + DatabasesXmlTags.ATT_backendStateColumnType + "=\""
2406        + backendTableBackendState + "\" "
2407        + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
2408        + backendTableCheckpointName + "\" "
2409        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
2410        + backendTableExtraStatement + "\"" + "/>");
2411    // Dump table
2412
info.append("<" + DatabasesXmlTags.ELT_DumpTable + " "
2413        + DatabasesXmlTags.ATT_createTable + "=\"" + dumpTableCreateTable
2414        + "\" " + DatabasesXmlTags.ATT_tableName + "=\"" + dumpTableName
2415        + "\" " + DatabasesXmlTags.ATT_dumpNameColumnType + "=\""
2416        + dumpTableDumpNameColumnType + "\" "
2417        + DatabasesXmlTags.ATT_dumpDateColumnType + "=\""
2418        + dumpTableDumpDateColumnType + "\" "
2419        + DatabasesXmlTags.ATT_dumpPathColumnType + "=\""
2420        + dumpTableDumpPathColumnType + "\" "
2421        + DatabasesXmlTags.ATT_dumpFormatColumnType + "=\""
2422        + dumpTableDumpFormatColumnType + "\" "
2423        + DatabasesXmlTags.ATT_checkpointNameColumnType + "=\""
2424        + dumpTableCheckpointNameColumnType + "\" "
2425        + DatabasesXmlTags.ATT_backendNameColumnType + "=\""
2426        + dumpTableBackendNameColumnType + "\" "
2427        + DatabasesXmlTags.ATT_tablesColumnName + "=\""
2428        + dumpTableTablesColumnName + "\" "
2429        + DatabasesXmlTags.ATT_tablesColumnType + "=\""
2430        + dumpTableTablesColumnType + "\" "
2431        + DatabasesXmlTags.ATT_extraStatementDefinition + "=\""
2432        + dumpTableExtraStatementDefinition + "\"" + "/>");
2433    info.append("</" + DatabasesXmlTags.ELT_RecoveryLog + ">");
2434
2435    return info.toString();
2436  }
2437
2438  /**
2439   * @see StoreDumpCheckpointEvent
2440   * @param dumpCheckpointName name of the checkpoint to store
2441   * @param checkpointId id of the checkpoint
2442   */

2443  public void storeDumpCheckpointName(String JavaDoc dumpCheckpointName,
2444      long checkpointId)
2445  {
2446    loggerThread.log(new StoreDumpCheckpointEvent(dumpCheckpointName,
2447        checkpointId));
2448  }
2449
2450}
Popular Tags