KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > controller > virtualdatabase > VirtualDatabase


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): Mathieu Peltier, Nicolas Modrzyk, Vadim Kassin, Olivier Fambon, Jean-Bernard van Zuylen.
23  */

24
25 package org.objectweb.cjdbc.controller.virtualdatabase;
26
27 import java.sql.SQLException JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Date JavaDoc;
30 import java.util.Hashtable JavaDoc;
31 import java.util.Iterator JavaDoc;
32 import java.util.LinkedList JavaDoc;
33 import java.util.Map JavaDoc;
34
35 import javax.management.NotCompliantMBeanException JavaDoc;
36 import javax.management.ObjectName JavaDoc;
37
38 import org.objectweb.cjdbc.common.exceptions.BackupException;
39 import org.objectweb.cjdbc.common.exceptions.VirtualDatabaseException;
40 import org.objectweb.cjdbc.common.i18n.Translate;
41 import org.objectweb.cjdbc.common.jmx.JmxConstants;
42 import org.objectweb.cjdbc.common.jmx.JmxException;
43 import org.objectweb.cjdbc.common.jmx.mbeans.VirtualDatabaseMBean;
44 import org.objectweb.cjdbc.common.jmx.notifications.CjdbcNotificationList;
45 import org.objectweb.cjdbc.common.log.Trace;
46 import org.objectweb.cjdbc.common.monitor.backend.BackendStatistics;
47 import org.objectweb.cjdbc.common.shared.BackendInfo;
48 import org.objectweb.cjdbc.common.shared.BackendState;
49 import org.objectweb.cjdbc.common.shared.DumpInfo;
50 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest;
51 import org.objectweb.cjdbc.common.sql.SelectRequest;
52 import org.objectweb.cjdbc.common.sql.StoredProcedure;
53 import org.objectweb.cjdbc.common.sql.filters.AbstractBlobFilter;
54 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
55 import org.objectweb.cjdbc.common.users.AdminUser;
56 import org.objectweb.cjdbc.common.users.VirtualDatabaseUser;
57 import org.objectweb.cjdbc.common.util.Constants;
58 import org.objectweb.cjdbc.common.util.ReadPrioritaryFIFOWriteLock;
59 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
60 import org.objectweb.cjdbc.common.xml.XmlComponent;
61 import org.objectweb.cjdbc.common.xml.XmlTools;
62 import org.objectweb.cjdbc.controller.authentication.AuthenticationManager;
63 import org.objectweb.cjdbc.controller.backend.DatabaseBackend;
64 import org.objectweb.cjdbc.controller.backup.Backuper;
65 import org.objectweb.cjdbc.controller.cache.result.AbstractResultCache;
66 import org.objectweb.cjdbc.controller.core.Controller;
67 import org.objectweb.cjdbc.controller.core.shutdown.VirtualDatabaseForceShutdownThread;
68 import org.objectweb.cjdbc.controller.core.shutdown.VirtualDatabaseSafeShutdownThread;
69 import org.objectweb.cjdbc.controller.core.shutdown.VirtualDatabaseShutdownThread;
70 import org.objectweb.cjdbc.controller.core.shutdown.VirtualDatabaseWaitShutdownThread;
71 import org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean;
72 import org.objectweb.cjdbc.controller.jmx.MBeanServerManager;
73 import org.objectweb.cjdbc.controller.jmx.RmiConnector;
74 import org.objectweb.cjdbc.controller.loadbalancer.AllBackendsFailedException;
75 import org.objectweb.cjdbc.controller.monitoring.SQLMonitoring;
76 import org.objectweb.cjdbc.controller.recoverylog.BackendRecoveryInfo;
77 import org.objectweb.cjdbc.controller.recoverylog.RecoverThread;
78 import org.objectweb.cjdbc.controller.recoverylog.RecoveryLog;
79 import org.objectweb.cjdbc.controller.requestmanager.RAIDbLevels;
80 import org.objectweb.cjdbc.controller.requestmanager.RequestManager;
81
82 /**
83  * A <code>VirtualDatabase</code> represents a database from client point of
84  * view and hide the complexity of the cluster distribution to the client. The
85  * client always uses the virtual database name and the C-JDBC Controller will
86  * use the real connections when an SQL request comes in.
87  *
88  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
89  * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
90  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
91  * @author <a HREF="mailto:vadim@kase.kz">Vadim Kassin </a>
92  * @author <a HREF="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
93  * </a>
94  * @version 1.0
95  */

96 public class VirtualDatabase extends AbstractStandardMBean
97     implements
98       VirtualDatabaseMBean,
99       XmlComponent
100 {
101   private static final long serialVersionUID = 1399418136380336827L;
102
103   //
104
// How the code is organized ?
105
//
106
// 1. Member variables
107
// 2. Constructor(s)
108
// 3. Request handling
109
// 4. Transaction handling
110
// 5. Database backend management
111
// 6. Checkpoint management
112
// 7. Getter/Setter (possibly in alphabetical order)
113
// 8. Shutdown
114
//
115

116   /** Virtual database name */
117   protected String JavaDoc name;
118
119   /**
120    * Authentification manager matching virtual database login/password to
121    * backends login/password
122    */

123   protected AuthenticationManager authenticationManager;
124
125   /** <code>ArrayList</code> of <code>DatabaseBackend</code> objects */
126   protected ArrayList JavaDoc backends;
127
128   /** Read/Write lock for backend list */
129   protected ReadPrioritaryFIFOWriteLock rwLock;
130
131   /** The request manager to use for this database */
132   protected RequestManager requestManager;
133
134   /** ArrayList to store the order of requests */
135   protected LinkedList JavaDoc totalOrderQueue = null;
136
137   /** Virtual database logger */
138   public Trace logger = null;
139   protected Trace requestLogger = null;
140
141   // List of current active Worker Threads
142
private ArrayList JavaDoc activeThreads = new ArrayList JavaDoc();
143   // List of current idle Worker Threads
144
private int idleThreads = 0;
145   // List of current pending connections (Socket objects)
146
private ArrayList JavaDoc pendingConnections = new ArrayList JavaDoc();
147
148   /** Maximum number of concurrent accepted for this virtual database */
149   protected int maxNbOfConnections;
150
151   /** If false one worker thread is forked per connection else */
152   protected boolean poolConnectionThreads;
153
154   /** Maximum time a worker thread can remain idle before dying */
155   protected long maxThreadIdleTime;
156
157   /**
158    * Minimum number of worker threads to keep in the pool if
159    * poolConnectionThreads is true
160    */

161   protected int minNbOfThreads;
162
163   /** Maximum number of worker threads to fork */
164   protected int maxNbOfThreads;
165
166   /** Current number of worker threads */
167   protected int currentNbOfThreads;
168
169   /** Virtual Database MetaData */
170   protected VirtualDatabaseDynamicMetaData metadata;
171   private VirtualDatabaseStaticMetaData staticMetadata;
172
173   private SQLMonitoring sqlMonitor = null;
174
175   /** Use for method getAndCheck */
176   public static final int CHECK_BACKEND_ENABLE = 1;
177   /** Use for method getAndCheck */
178   public static final int CHECK_BACKEND_DISABLE = 0;
179   /** Use for method getAndCheck */
180   public static final int NO_CHECK_BACKEND = -1;
181
182   /** Short form of SQL statements to include in traces and exceptions */
183   private int sqlShortFormLength;
184
185   /** The filter used to store blobs in the database */
186   private AbstractBlobFilter blobFilter;
187
188   /** The controller we belong to */
189   Controller controller;
190
191   /** Comma separated list of database product names (one instance per name) */
192   private String JavaDoc databaseProductNames = "C-JDBC";
193
194   /** Marker to see if the database is shutting down */
195   private boolean shuttingDown = false;
196
197   /* Constructors */
198
199   /**
200    * Creates a new <code>VirtualDatabase</code> instance.
201    *
202    * @param name the virtual database name.
203    * @param maxConnections maximum number of concurrent connections.
204    * @param pool should we use a pool of threads for handling connections?
205    * @param minThreads minimum number of threads in the pool
206    * @param maxThreads maximum number of threads in the pool
207    * @param maxThreadIdleTime maximum time a thread can remain idle before being
208    * removed from the pool.
209    * @param sqlShortFormLength maximum number of characters of an SQL statement
210    * to diplay in traces or exceptions
211    * @param blobFilter encoding method for blobs
212    * @param controller the controller we belong to
213    * @exception NotCompliantMBeanException in case the bean does not comply with
214    * jmx
215    * @exception JmxException could not register mbean
216    */

217   public VirtualDatabase(Controller controller, String JavaDoc name,
218       int maxConnections, boolean pool, int minThreads, int maxThreads,
219       long maxThreadIdleTime, int sqlShortFormLength,
220       AbstractBlobFilter blobFilter) throws NotCompliantMBeanException JavaDoc,
221       JmxException
222   {
223     super(VirtualDatabaseMBean.class);
224     this.controller = controller;
225     this.name = name;
226     this.maxNbOfConnections = maxConnections;
227     this.poolConnectionThreads = pool;
228     this.minNbOfThreads = minThreads;
229     this.maxNbOfThreads = maxThreads;
230     this.maxThreadIdleTime = maxThreadIdleTime;
231     this.sqlShortFormLength = sqlShortFormLength;
232     this.blobFilter = blobFilter;
233     backends = new ArrayList JavaDoc();
234
235     ObjectName JavaDoc objectName = JmxConstants.getVirtualDbObjectName(name);
236     MBeanServerManager.registerMBean(this, objectName);
237
238     rwLock = new ReadPrioritaryFIFOWriteLock();
239     logger = Trace.getLogger("org.objectweb.cjdbc.controller.virtualdatabase."
240         + name);
241     requestLogger = Trace
242         .getLogger("org.objectweb.cjdbc.controller.virtualdatabase.request."
243             + name);
244   }
245
246   /**
247    * Acquires a read lock on the backend lists (both enabled and disabled
248    * backends). This should be called prior traversing the backend
249    * <code>ArrayList</code>.
250    *
251    * @throws InterruptedException if an error occurs
252    */

253   public final void acquireReadLockBackendLists() throws InterruptedException JavaDoc
254   {
255     rwLock.acquireRead();
256   }
257
258   /**
259    * Releases the read lock on the backend lists (both enabled and disabled
260    * backends). This should be called after traversing the backend
261    * <code>ArrayList</code>.
262    */

263   public final void releaseReadLockBackendLists()
264   {
265     rwLock.releaseRead();
266   }
267
268   /**
269    * Is this virtual database distributed ?
270    *
271    * @return false
272    */

273   public boolean isDistributed()
274   {
275     return false;
276   }
277
278   /* Request Handling */
279
280   /**
281    * Checks if a given virtual login/password is ok.
282    *
283    * @param virtualLogin the virtual user login
284    * @param virtualPassword the virtual user password
285    * @return <code>true</code> if the login/password is known from the
286    * <code>AuthenticationManager</code>. Returns <code>false</code>
287    * if no <code>AuthenticationManager</code> is defined.
288    */

289   public boolean checkUserAuthentication(String JavaDoc virtualLogin,
290       String JavaDoc virtualPassword)
291   {
292     if (authenticationManager == null)
293     {
294       logger.error("No authentification manager defined to check login '"
295           + virtualLogin + "'");
296       return false;
297     }
298     else
299       return authenticationManager.isValidVirtualUser(new VirtualDatabaseUser(
300           virtualLogin, virtualPassword));
301   }
302
303   /**
304    * Checks if a given admin login/password is ok.
305    *
306    * @param adminLogin admin user login
307    * @param adminPassword admin user password
308    * @return <code>true</code> if the login/password is known from the
309    * <code>AuthenticationManager</code>. Returns <code>false</code>
310    * if no <code>AuthenticationManager</code> is defined.
311    */

312   public boolean checkAdminAuthentication(String JavaDoc adminLogin,
313       String JavaDoc adminPassword)
314   {
315     if (authenticationManager == null)
316     {
317       logger.error("No authentification manager defined to check admin login '"
318           + adminLogin + "'");
319       return false;
320     }
321     else
322       return authenticationManager.isValidAdminUser(new AdminUser(adminLogin,
323           adminPassword));
324   }
325
326   /**
327    * Performs a read request and returns the reply.
328    *
329    * @param request the request to execute
330    * @return a <code>ControllerResultSet</code> value
331    * @exception SQLException if the request fails
332    */

333   public ControllerResultSet execReadRequest(SelectRequest request)
334       throws SQLException JavaDoc
335   {
336     if (request == null)
337     {
338       String JavaDoc msg = "Request failed (null read request received)";
339       logger.warn(msg);
340       throw new SQLException JavaDoc(msg);
341     }
342
343     try
344     {
345       if (requestLogger.isInfoEnabled())
346         requestLogger.info("S " + request.getTransactionId() + " "
347             + request.getSQL());
348
349       long start = 0;
350       if (sqlMonitor != null && sqlMonitor.isActive())
351         start = System.currentTimeMillis();
352
353       ControllerResultSet rs = requestManager.execReadRequest(request);
354
355       if (sqlMonitor != null && sqlMonitor.isActive())
356         sqlMonitor.logRequestTime(request, System.currentTimeMillis() - start);
357
358       return rs;
359     }
360     catch (SQLException JavaDoc e)
361     {
362       String JavaDoc msg = "Request '" + request.getId() + "' failed ("
363           + e.getMessage() + ")";
364       logger.warn(msg);
365       if (sqlMonitor != null && sqlMonitor.isActive())
366         sqlMonitor.logError(request);
367       throw e;
368     }
369   }
370
371   /**
372    * Performs a write request and returns the number of rows affected.
373    *
374    * @param request the request to execute
375    * @return number of rows affected
376    * @exception SQLException if the request fails
377    */

378   public int execWriteRequest(AbstractWriteRequest request) throws SQLException JavaDoc
379   {
380     if (request == null)
381     {
382       String JavaDoc msg = "Request failed (null write request received)";
383       logger.warn(msg);
384       throw new SQLException JavaDoc(msg);
385     }
386
387     try
388     {
389       if (requestLogger.isInfoEnabled())
390         requestLogger.info("W " + request.getTransactionId() + " "
391             + request.getSQL());
392
393       long start = 0;
394       if (sqlMonitor != null && sqlMonitor.isActive())
395         start = System.currentTimeMillis();
396
397       int result = requestManager.execWriteRequest(request);
398
399       if (sqlMonitor != null && sqlMonitor.isActive())
400         sqlMonitor.logRequestTime(request, System.currentTimeMillis() - start);
401
402       return result;
403     }
404     catch (SQLException JavaDoc e)
405     {
406       String JavaDoc msg = "Request '" + request.getId() + "' failed ("
407           + e.getMessage() + ")";
408       logger.warn(msg);
409       if (sqlMonitor != null && sqlMonitor.isActive())
410         sqlMonitor.logError(request);
411       throw e;
412     }
413   }
414
415   /**
416    * Performs a write request and returns the auto generated keys.
417    *
418    * @param request the request to execute
419    * @return auto generated keys
420    * @exception SQLException if the request fails
421    */

422   public ControllerResultSet execWriteRequestWithKeys(
423       AbstractWriteRequest request) throws SQLException JavaDoc
424   {
425     if (request == null)
426     {
427       String JavaDoc msg = "Request failed (null write request received)";
428       logger.warn(msg);
429       throw new SQLException JavaDoc(msg);
430     }
431
432     try
433     {
434       if (requestLogger.isInfoEnabled())
435         requestLogger.info("W " + request.getTransactionId() + " "
436             + request.getSQL());
437
438       long start = 0;
439       if (sqlMonitor != null && sqlMonitor.isActive())
440         start = System.currentTimeMillis();
441
442       ControllerResultSet result = requestManager
443           .execWriteRequestWithKeys(request);
444
445       if (sqlMonitor != null && sqlMonitor.isActive())
446         sqlMonitor.logRequestTime(request, System.currentTimeMillis() - start);
447
448       return result;
449     }
450     catch (SQLException JavaDoc e)
451     {
452       String JavaDoc msg = "Request '" + request.getId() + "' failed ("
453           + e.getMessage() + ")";
454       logger.warn(msg);
455       if (sqlMonitor != null && sqlMonitor.isActive())
456         sqlMonitor.logError(request);
457       throw e;
458     }
459   }
460
461   /**
462    * Call a stored procedure that returns a ResultSet.
463    *
464    * @param proc the stored procedure call
465    * @return a <code>java.sql.ResultSet</code> value
466    * @exception SQLException if an error occurs
467    */

468   public ControllerResultSet execReadStoredProcedure(StoredProcedure proc)
469       throws SQLException JavaDoc
470   {
471     if (proc == null)
472     {
473       String JavaDoc msg = "Request failed (null stored procedure received)";
474       logger.warn(msg);
475       throw new SQLException JavaDoc(msg);
476     }
477
478     try
479     {
480       if (requestLogger.isInfoEnabled())
481         requestLogger
482             .info("S " + proc.getTransactionId() + " " + proc.getSQL());
483
484       long start = 0;
485       if (sqlMonitor != null && sqlMonitor.isActive())
486         start = System.currentTimeMillis();
487
488       ControllerResultSet rs = requestManager.execReadStoredProcedure(proc);
489
490       if (sqlMonitor != null && sqlMonitor.isActive())
491         sqlMonitor.logRequestTime(proc, System.currentTimeMillis() - start);
492
493       return rs;
494     }
495     catch (AllBackendsFailedException e)
496     {
497       String JavaDoc msg = Translate.get(
498           "loadbalancer.storedprocedure.failed.on.all.backends", new String JavaDoc[]{
499               String.valueOf(proc.getId()), e.getMessage()});
500       logger.warn(msg);
501       if (sqlMonitor != null && sqlMonitor.isActive())
502         sqlMonitor.logError(proc);
503       throw new SQLException JavaDoc(msg);
504     }
505     catch (SQLException JavaDoc e)
506     {
507       String JavaDoc msg = Translate.get("loadbalancer.storedprocedure.failed",
508           new String JavaDoc[]{String.valueOf(proc.getId()), e.getMessage()});
509       logger.warn(msg);
510       if (sqlMonitor != null && sqlMonitor.isActive())
511         sqlMonitor.logError(proc);
512       throw e;
513     }
514   }
515
516   /**
517    * Call a stored procedure that performs an update.
518    *
519    * @param proc the stored procedure call
520    * @return number of rows affected
521    * @exception SQLException if an error occurs
522    */

523   protected int execWriteStoredProcedure(StoredProcedure proc)
524       throws SQLException JavaDoc
525   {
526     if (proc == null)
527     {
528       String JavaDoc msg = "Request failed (null stored procedure received)";
529       logger.warn(msg);
530       throw new SQLException JavaDoc(msg);
531     }
532
533     try
534     {
535       if (requestLogger.isInfoEnabled())
536         requestLogger
537             .info("W " + proc.getTransactionId() + " " + proc.getSQL());
538
539       long start = 0;
540       if (sqlMonitor != null && sqlMonitor.isActive())
541         start = System.currentTimeMillis();
542
543       int result = requestManager.execWriteStoredProcedure(proc);
544
545       if (sqlMonitor != null && sqlMonitor.isActive())
546         sqlMonitor.logRequestTime(proc, System.currentTimeMillis() - start);
547
548       return result;
549     }
550     catch (AllBackendsFailedException e)
551     {
552       String JavaDoc msg = Translate.get(
553           "loadbalancer.storedprocedure.failed.on.all.backends", new String JavaDoc[]{
554               String.valueOf(proc.getId()), e.getMessage()});
555       logger.warn(msg);
556       if (sqlMonitor != null && sqlMonitor.isActive())
557         sqlMonitor.logError(proc);
558       throw new SQLException JavaDoc(msg);
559     }
560     catch (SQLException JavaDoc e)
561     {
562       String JavaDoc msg = Translate.get("loadbalancer.storedprocedure.failed",
563           new String JavaDoc[]{String.valueOf(proc.getId()), e.getMessage()});
564       logger.warn(msg);
565       if (sqlMonitor != null && sqlMonitor.isActive())
566         sqlMonitor.logError(proc);
567       throw e;
568     }
569   }
570
571   /* Transaction management */
572
573   /**
574    * Begins a new transaction and returns the corresponding transaction
575    * identifier. This method is called from the driver when
576    * {@link org.objectweb.cjdbc.driver.Connection#setAutoCommit(boolean)}is
577    * called with <code>false</code> argument.
578    * <p>
579    * Note that the transaction begin is not logged in the recovery log by this
580    * method, you will have to call logLazyTransactionBegin.
581    *
582    * @param login the login used by the connection
583    * @return an unique transaction identifier
584    * @exception SQLException if an error occurs
585    * @see RequestManager#logLazyTransactionBegin(long)
586    */

587   public long begin(String JavaDoc login) throws SQLException JavaDoc
588   {
589     try
590     {
591       long tid = requestManager.begin(login);
592       if (requestLogger.isInfoEnabled())
593         requestLogger.info("B " + tid);
594       return tid;
595     }
596     catch (SQLException JavaDoc e)
597     {
598       String JavaDoc msg = "Begin failed (" + e.getMessage() + ")";
599       logger.warn(msg);
600       throw e;
601     }
602   }
603
604   /**
605    * Abort a transaction that has been started but in which no query was
606    * executed. As we use lazy transaction begin, there is no need to rollback
607    * such transaction but just to cleanup the metadata associated with this not
608    * effectively started transaction.
609    *
610    * @param transactionId id of the transaction to abort
611    * @param logAbort true if the abort (in fact rollback) should be logged in
612    * the recovery log
613    * @throws SQLException if an error occurs
614    */

615   public void abort(long transactionId, boolean logAbort) throws SQLException JavaDoc
616   {
617     requestManager.abort(transactionId, logAbort);
618     // Emulate this as a rollback for the RequestPlayer
619
if (requestLogger.isInfoEnabled())
620       requestLogger.info("R " + transactionId);
621   }
622
623   /**
624    * Commits a transaction given its id.
625    *
626    * @param transactionId the transaction id
627    * @param logCommit true if the commit should be logged in the recovery log
628    * @exception SQLException if an error occurs
629    */

630   public void commit(long transactionId, boolean logCommit) throws SQLException JavaDoc
631   {
632     try
633     {
634       if (requestLogger.isInfoEnabled())
635         requestLogger.info("C " + transactionId);
636       requestManager.commit(transactionId, logCommit);
637     }
638     catch (SQLException JavaDoc e)
639     {
640       String JavaDoc msg = "Commit of transaction '" + transactionId + "' failed ("
641           + e.getMessage() + ")";
642       logger.warn(msg);
643       throw e;
644     }
645   }
646
647   /**
648    * Rollbacks a transaction given its id.
649    *
650    * @param transactionId the transaction id
651    * @param logRollback true if the rollback should be logged in the recovery
652    * log
653    * @exception SQLException if an error occurs
654    */

655   public void rollback(long transactionId, boolean logRollback)
656       throws SQLException JavaDoc
657   {
658     try
659     {
660       if (requestLogger.isInfoEnabled())
661         requestLogger.info("R " + transactionId);
662       requestManager.rollback(transactionId, logRollback);
663     }
664     catch (SQLException JavaDoc e)
665     {
666       String JavaDoc msg = "Rollback of transaction '" + transactionId + "' failed ("
667           + e.getMessage() + ")";
668       logger.warn(msg);
669       throw e;
670     }
671   }
672
673   /**
674    * Rollbacks a transaction given its id to a savepoint given its name
675    *
676    * @param transactionId the transaction id
677    * @param savepointName the name of the savepoint
678    * @exception SQLException if an error occurs
679    */

680   public void rollback(long transactionId, String JavaDoc savepointName)
681       throws SQLException JavaDoc
682   {
683     try
684     {
685       if (requestLogger.isInfoEnabled())
686         requestLogger.info("R " + transactionId + " " + savepointName);
687       requestManager.rollback(transactionId, savepointName);
688     }
689     catch (SQLException JavaDoc e)
690     {
691       String JavaDoc msg = "Rollback to savepoint '" + savepointName + "' for "
692           + "transaction '" + transactionId + "' failed (" + e.getMessage()
693           + ")";
694       logger.warn(msg);
695       throw e;
696     }
697   }
698
699   /**
700    * Sets a unnamed savepoint to a transaction given its id.
701    *
702    * @param transactionId the transaction id
703    * @return the savepoint id
704    * @exception SQLException if an error occurs
705    */

706   public int setSavepoint(long transactionId) throws SQLException JavaDoc
707   {
708     try
709     {
710       int savepointId = requestManager.setSavepoint(transactionId);
711       if (requestLogger.isInfoEnabled())
712         requestLogger.info("P " + transactionId + " " + savepointId);
713       return savepointId;
714     }
715     catch (SQLException JavaDoc e)
716     {
717       String JavaDoc msg = "Setting unnamed savepoint to transaction '" + transactionId
718           + "' failed (" + e.getMessage() + ")";
719       logger.warn(msg);
720       throw e;
721     }
722   }
723
724   /**
725    * Sets a savepoint given its desired name to a transaction given its id.
726    *
727    * @param transactionId the transaction id
728    * @param name the desired name of the savepoint
729    * @exception SQLException if an error occurs
730    */

731   public void setSavepoint(long transactionId, String JavaDoc name) throws SQLException JavaDoc
732   {
733     try
734     {
735       if (requestLogger.isInfoEnabled())
736         requestLogger.info("P " + transactionId + " " + name);
737       requestManager.setSavepoint(transactionId, name);
738     }
739     catch (SQLException JavaDoc e)
740     {
741       String JavaDoc msg = "Setting savepoint with name '" + name + "' to transaction "
742           + "'" + transactionId + "' failed (" + e.getMessage() + ")";
743       logger.warn(msg);
744       throw e;
745     }
746   }
747
748   /**
749    * Releases a savepoint given its name from a transaction given its id.
750    *
751    * @param transactionId the transaction id
752    * @param name the name of the savepoint
753    * @exception SQLException if an error occurs
754    */

755   public void releaseSavepoint(long transactionId, String JavaDoc name)
756       throws SQLException JavaDoc
757   {
758     try
759     {
760       if (requestLogger.isInfoEnabled())
761         requestLogger.info("F " + transactionId + " " + name);
762       requestManager.releaseSavepoint(transactionId, name);
763     }
764     catch (SQLException JavaDoc e)
765     {
766       String JavaDoc msg = "Releasing savepoint with name '" + name + "' from "
767           + "transaction '" + transactionId + "' failed (" + e.getMessage()
768           + ")";
769       logger.warn(msg);
770       throw e;
771     }
772   }
773
774   //
775
// Database backends management
776
//
777

778   /**
779    * Add a backend to this virtual database.
780    *
781    * @param db the database backend to add
782    * @throws VirtualDatabaseException if an error occurs
783    */

784   public void addBackend(DatabaseBackend db) throws VirtualDatabaseException
785   {
786     this.addBackend(db, true);
787   }
788
789   /**
790    * Add a backend to this virtual database.
791    *
792    * @param db the database backend to add
793    * @param checkForCompliance should load the driver ?
794    * @throws VirtualDatabaseException if an error occurs
795    */

796   public void addBackend(DatabaseBackend db, boolean checkForCompliance)
797       throws VirtualDatabaseException
798   {
799     if (db == null)
800     {
801       String JavaDoc msg = "Illegal null database backend in addBackend(DatabaseBackend) method";
802       logger.error(msg);
803       throw new VirtualDatabaseException(msg);
804     }
805
806     if (db.isReadEnabled())
807     {
808       String JavaDoc msg = "It is not allowed to add an enabled database.";
809       logger.error(msg);
810       throw new VirtualDatabaseException(msg);
811     }
812
813     // Get the lock on the list of backends
814
try
815     {
816       rwLock.acquireWrite();
817     }
818     catch (InterruptedException JavaDoc e)
819     {
820       String JavaDoc msg = Translate.get(
821           "loadbalancer.backendlist.acquire.writelock.failed", e);
822       logger.error(msg);
823       throw new VirtualDatabaseException(msg);
824     }
825
826     // Check that the backend is not already up
827
if (backends.indexOf(db) != -1)
828     {
829       rwLock.releaseWrite();
830       String JavaDoc msg = "Duplicate backend " + db.getURL();
831       logger.warn(msg);
832       throw new VirtualDatabaseException(msg);
833     }
834
835     // Check the authentication manager has all virtual logins defined
836
ArrayList JavaDoc logins = authenticationManager.getVirtualLogins();
837     VirtualDatabaseUser vdu;
838     String JavaDoc login;
839     for (int i = 0; i < logins.size(); i++)
840     {
841       vdu = (VirtualDatabaseUser) logins.get(i);
842       login = vdu.getLogin();
843       if (db.getConnectionManager(login) == null)
844       {
845         rwLock.releaseWrite();
846         throw new VirtualDatabaseException(Translate.get(
847             "backend.missing.connection.manager", login));
848       }
849     }
850
851     // Initialize the driver and check the compliance
852
try
853     {
854       if (logger.isDebugEnabled())
855         logger.debug("Checking driver compliance");
856       if (checkForCompliance)
857         db.checkDriverCompliance(); // Also loads the driver
858
}
859     catch (Exception JavaDoc e)
860     {
861       rwLock.releaseWrite();
862       String JavaDoc msg = "Error while adding database backend " + db.getName() + " ("
863           + e + ")";
864       logger.warn(msg);
865       throw new VirtualDatabaseException(msg);
866     }
867
868     db.setSqlShortFormLength(getSQLShortFormLength());
869
870     // Add the backend to the list
871
backends.add(db);
872     if (logger.isDebugEnabled())
873       logger.debug("Backend " + db.getName() + " added successfully");
874
875     // Set the backend state listener so that the state is logged into the
876
// recovery log - if any. When there is no recovery log,
877
// getBackendStateListener returns null, and stateListener is consequently
878
// set to null. Looks like the only state listner ever is the recoveryLog.
879
/*
880      * Note: getRequestManager() is null, at load time, thus the test. At load
881      * time, if there is a recovery log, the state listner eventually gets set,
882      * but in a different fashion: it is set by RequestManager c'tor, when
883      * calling setRecoveryLog().
884      */

885     if (getRequestManager() != null)
886     {
887       db.setStateListener(getRequestManager().getBackendStateListener());
888     }
889
890     // Release the lock
891
rwLock.releaseWrite();
892
893     // Notify Jmx listeners of the backend addition
894
if (MBeanServerManager.isJmxEnabled())
895     {
896       // Send notification
897
Hashtable JavaDoc data = new Hashtable JavaDoc();
898       data.put(CjdbcNotificationList.DATA_DATABASE, this.name);
899       data.put(CjdbcNotificationList.DATA_DRIVER, db.getDriverClassName());
900       String JavaDoc checkpoint = db.getLastKnownCheckpoint();
901       checkpoint = (checkpoint == null) ? "" : checkpoint;
902       data.put(CjdbcNotificationList.DATA_CHECKPOINT, checkpoint);
903       data.put(CjdbcNotificationList.DATA_NAME, db.getName());
904       data.put(CjdbcNotificationList.DATA_URL, db.getURL());
905       RmiConnector.broadcastNotification(this,
906           CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ADDED,
907