KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > cjdbc > controller > backend > DatabaseBackend


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, Sara Bouchenak, Jean-Bernard van Zuylen.
23  */

24
25 package org.objectweb.cjdbc.controller.backend;
26
27 import java.io.StringReader JavaDoc;
28 import java.net.ConnectException JavaDoc;
29 import java.sql.Connection JavaDoc;
30 import java.sql.SQLException JavaDoc;
31 import java.sql.Savepoint JavaDoc;
32 import java.sql.Statement JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.HashMap JavaDoc;
35 import java.util.Hashtable JavaDoc;
36 import java.util.Iterator JavaDoc;
37 import java.util.List JavaDoc;
38 import java.util.Map JavaDoc;
39 import java.util.Vector JavaDoc;
40
41 import javax.management.NotCompliantMBeanException JavaDoc;
42
43 import org.dom4j.Document;
44 import org.dom4j.Element;
45 import org.dom4j.io.SAXReader;
46 import org.objectweb.cjdbc.common.exceptions.NoTransactionStartWhenDisablingException;
47 import org.objectweb.cjdbc.common.exceptions.UnreachableBackendException;
48 import org.objectweb.cjdbc.common.i18n.Translate;
49 import org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean;
50 import org.objectweb.cjdbc.common.jmx.notifications.CjdbcNotificationList;
51 import org.objectweb.cjdbc.common.log.Trace;
52 import org.objectweb.cjdbc.common.monitor.backend.BackendStatistics;
53 import org.objectweb.cjdbc.common.shared.BackendInfo;
54 import org.objectweb.cjdbc.common.shared.BackendState;
55 import org.objectweb.cjdbc.common.sql.AbstractWriteRequest;
56 import org.objectweb.cjdbc.common.sql.CreateRequest;
57 import org.objectweb.cjdbc.common.sql.metadata.MetadataContainer;
58 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
59 import org.objectweb.cjdbc.common.sql.schema.DatabaseTable;
60 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
61 import org.objectweb.cjdbc.common.xml.XmlComponent;
62 import org.objectweb.cjdbc.controller.backend.rewriting.AbstractRewritingRule;
63 import org.objectweb.cjdbc.controller.connection.AbstractConnectionManager;
64 import org.objectweb.cjdbc.controller.connection.FailFastPoolConnectionManager;
65 import org.objectweb.cjdbc.controller.connection.RandomWaitPoolConnectionManager;
66 import org.objectweb.cjdbc.controller.connection.SimpleConnectionManager;
67 import org.objectweb.cjdbc.controller.connection.VariablePoolConnectionManager;
68 import org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean;
69 import org.objectweb.cjdbc.controller.jmx.MBeanServerManager;
70 import org.objectweb.cjdbc.controller.jmx.RmiConnector;
71 import org.objectweb.cjdbc.controller.loadbalancer.AbstractLoadBalancer;
72
73 /**
74  * A <code>DatabaseBackend</code> represents a real database backend that will
75  * have to be bound to a virtual C-JDBC database. All connections opened will
76  * use the same url but possibly different login/password.
77  *
78  * @author <a HREF="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
79  * @author <a HREF="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
80  * @author <a HREF="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
81  * @author <a HREF="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
82  * @author <a HREF="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
83  * </a>
84  * @version 1.0
85  */

86 public final class DatabaseBackend extends AbstractStandardMBean
87     implements
88       XmlComponent,
89       DatabaseBackendMBean
90 {
91   //
92
// How the code is organized?
93
// 1. Member variables
94
// 2. Constructor(s)
95
// 3. Lookup functions
96
// 4. Connections management
97
// 5. State management
98
// 6. Getter/Setter (possibly in alphabetical order)
99
// 7. Debug/Monitoring
100
//
101

102   /** Logical name assigned to this backend. */
103   private String JavaDoc name;
104
105   /** Path for driver */
106   private String JavaDoc driverPath;
107
108   /** Database native JDBC driver class name. */
109   private String JavaDoc driverClassName;
110
111   /** Driver compliance to C-JDBC requirements */
112   private transient DriverCompliance driverCompliance;
113
114   /** Real URL to access the database (JDBC URL). */
115   private String JavaDoc url;
116
117   /** Name of the virtual database this backend is attached to */
118   private String JavaDoc virtualDatabaseName;
119
120   /** A boolean to know if we should allow this backend to be enabled for write */
121   private boolean writeCanBeEnabled;
122
123   /** SQL statement used to check if a connection is still valid */
124   private String JavaDoc connectionTestStatement;
125
126   /**
127    * The schema of the database. This should be accessed in a synchronized(this)
128    * block since it can be updated dynamically.
129    */

130   private transient DatabaseSchema schema;
131
132   /** <code>true</code> if schema is static. */
133   private boolean schemaIsStatic = false;
134
135   /**
136    * <code>true</code> if the backend must maintain its schema dynamically for
137    * the virtual database needs
138    */

139   private boolean schemaIsNeededByVdb = true;
140
141   /** <code>true</code> if schema is no more up-to-date and needs a refresh */
142   private boolean schemaIsDirty = true;
143
144   /** Connection managers for this backend. */
145   private transient HashMap JavaDoc connectionManagers;
146
147   /** Logger instance. */
148   protected transient Trace logger;
149
150   /** List of started transactions. */
151   private transient ArrayList JavaDoc activeTransactions = new ArrayList JavaDoc();
152
153   /** List of savepoints for each transaction */
154   private transient Map JavaDoc savepoints = new HashMap JavaDoc();
155
156   /** List of pending requests. */
157   private transient Vector JavaDoc pendingRequests = new Vector JavaDoc();
158
159   /** Monitoring Values */
160   private int totalRequest;
161   private int totalWriteRequest;
162   private int totalReadRequest;
163   private int totalTransactions;
164
165   /** List of <code>AbstractRewritingRule</code> objects. */
166   private ArrayList JavaDoc rewritingRules;
167
168   /** For metadata information generation */
169   private int dynamicPrecision;
170   private boolean gatherSystemTables = false;
171   private String JavaDoc schemaName = null;
172
173   /** Short form of SQL statements to include in traces and exceptions */
174   private int sqlShortFormLength = 40;
175
176   private String JavaDoc lastKnownCheckpoint;
177
178   /**
179    * The current state of the backend
180    *
181    * @see org.objectweb.cjdbc.common.shared.BackendState
182    */

183   private int state = BackendState.DISABLED;
184
185   private transient BackendStateListener stateListener;
186
187   /**
188    * Creates a new <code>DatabaseBackend</code> instance.
189    *
190    * @param name logical name assigned to this backend
191    * @param driverPath path for driver
192    * @param driverClassName class name of the database native JDBC driver to
193    * load
194    * @param url URL to access the database
195    * @param vdbName Name of the virtual database this backend is attached to
196    * @param writeCanBeEnabled if writes can be enabled on this backend
197    * @param connectionTestStatement SQL statement used to check if a connection
198    * is still valid
199    * @throws NotCompliantMBeanException if the mbean can not be created (unless
200    * we refactor the code this cannot happend)
201    */

202   public DatabaseBackend(String JavaDoc name, String JavaDoc driverPath,
203       String JavaDoc driverClassName, String JavaDoc url, String JavaDoc vdbName,
204       boolean writeCanBeEnabled, String JavaDoc connectionTestStatement)
205       throws NotCompliantMBeanException JavaDoc
206   {
207     super(DatabaseBackendMBean.class);
208     if (name == null)
209       throw new IllegalArgumentException JavaDoc(Translate
210           .get("backend.null.backend.name"));
211
212     if (driverClassName == null)
213       throw new IllegalArgumentException JavaDoc(Translate.get("backend.null.driver"));
214
215     if (url == null)
216       throw new IllegalArgumentException JavaDoc(Translate.get("backend.null.url"));
217
218     if (vdbName == null)
219       throw new IllegalArgumentException JavaDoc(Translate
220           .get("backend.null.virtualdatabase.name"));
221
222     if (connectionTestStatement == null)
223       throw new IllegalArgumentException JavaDoc(Translate
224           .get("backend.null.connection.test"));
225
226     this.name = name;
227     this.writeCanBeEnabled = writeCanBeEnabled;
228     this.driverPath = driverPath;
229     this.driverClassName = driverClassName;
230     this.url = url;
231     this.virtualDatabaseName = vdbName;
232     this.connectionTestStatement = connectionTestStatement;
233     this.connectionManagers = new HashMap JavaDoc();
234     logger = Trace
235         .getLogger("org.objectweb.cjdbc.controller.backend.DatabaseBackend."
236             + name);
237     this.driverCompliance = new DriverCompliance(logger);
238     totalRequest = 0;
239     dynamicPrecision = DatabaseBackendSchemaConstants.DynamicPrecisionAll;
240   }
241
242   /**
243    * Creates a new <code>DatabaseBackend</code> object
244    *
245    * @param info a backend info object to create a database backend object from
246    * @throws NotCompliantMBeanException if mbean is not compliant (unless we
247    * refactor the code this cannot happend)
248    */

249   public DatabaseBackend(BackendInfo info) throws NotCompliantMBeanException JavaDoc
250   {
251     this(info.getName(), info.getDriverPath(), info.getDriverClassName(), info
252         .getUrl(), info.getVirtualDatabaseName(), true, info
253         .getConnectionTestStatement());
254     setDynamicPrecision(info.getDynamicPrecision(),
255         info.isGatherSystemTables(), info.getSchemaName());
256     try
257     {
258       String JavaDoc xml = info.getXml();
259       StringReader JavaDoc sreader = new StringReader JavaDoc(xml);
260       SAXReader reader = new SAXReader();
261       Document document = reader.read(sreader);
262       Element root = document.getRootElement();
263       Iterator JavaDoc iter1 = root.elementIterator();
264       while (iter1.hasNext())
265       {
266         Element elem = (Element) iter1.next();
267         if (elem.getName().equals(DatabasesXmlTags.ELT_ConnectionManager))
268         {
269           String JavaDoc vuser = elem.valueOf("@" + DatabasesXmlTags.ATT_vLogin);
270           String JavaDoc rlogin = elem.valueOf("@" + DatabasesXmlTags.ATT_rLogin);
271           String JavaDoc rpassword = elem.valueOf("@" + DatabasesXmlTags.ATT_rPassword);
272           Iterator JavaDoc iter2 = elem.elementIterator();
273           while (iter2.hasNext())
274           {
275             Element connectionManager = (Element) iter2.next();
276             String JavaDoc cname = connectionManager.getName();
277             if (cname
278                 .equals(DatabasesXmlTags.ELT_VariablePoolConnectionManager))
279             {
280               int minPoolSize = Integer.parseInt(connectionManager.valueOf("@"
281                   + DatabasesXmlTags.ATT_minPoolSize));
282               int maxPoolSize = Integer.parseInt(connectionManager.valueOf("@"
283                   + DatabasesXmlTags.ATT_maxPoolSize));
284               int idleTimeout = Integer.parseInt(connectionManager.valueOf("@"
285                   + DatabasesXmlTags.ATT_idleTimeout));
286               int waitTimeout = Integer.parseInt(connectionManager.valueOf("@"
287                   + DatabasesXmlTags.ATT_waitTimeout));
288               this.addConnectionManager(vuser,
289                   new VariablePoolConnectionManager(url, name, rlogin,
290                       rpassword, driverPath, driverClassName, minPoolSize,
291                       maxPoolSize, idleTimeout, waitTimeout));
292             }
293             else if (cname.equals(DatabasesXmlTags.ELT_SimpleConnectionManager))
294             {
295               this.addConnectionManager(vuser, new SimpleConnectionManager(url,
296                   name, rlogin, rpassword, driverPath, driverClassName));
297             }
298             else if (cname
299                 .equals(DatabasesXmlTags.ELT_RandomWaitPoolConnectionManager))
300             {
301               int poolSize = Integer.parseInt(connectionManager.valueOf("@"
302                   + DatabasesXmlTags.ATT_poolSize));
303               int timeout = Integer.parseInt(connectionManager.valueOf("@"
304                   + DatabasesXmlTags.ATT_timeout));
305               this
306                   .addConnectionManager(vuser,
307                       new RandomWaitPoolConnectionManager(url, name, rlogin,
308                           rpassword, driverPath, driverClassName, poolSize,
309                           timeout));
310             }
311             else if (cname
312                 .equals(DatabasesXmlTags.ELT_FailFastPoolConnectionManager))
313             {
314               int poolSize = Integer.parseInt(connectionManager.valueOf("@"
315                   + DatabasesXmlTags.ATT_poolSize));
316               this.addConnectionManager(vuser,
317                   new FailFastPoolConnectionManager(url, name, rlogin,
318                       rpassword, driverPath, driverClassName, poolSize));
319             }
320           }
321         }
322       }
323
324     }
325     catch (Exception JavaDoc e)
326     {
327       logger
328           .error(Translate.get("backend.add.connection.manager.failed", e), e);
329     }
330   }
331
332   /**
333    * Additionnal constructor for setting a different dynamic schema level.
334    * Default was to gather all information Creates a new
335    * <code>DatabaseBackend</code> instance.
336    *
337    * @param name logical name assigned to this backend
338    * @param driverPath path for driver
339    * @param driverClassName class name of the database native JDBC driver to
340    * load
341    * @param url URL to access the database
342    * @param vdbName Name of the virtual database this backend is attached to
343    * @param connectionTestStatement SQL statement used to check if a connection
344    * is still valid
345    * @param dynamicSchemaLevel for dynamically gathering schema from backend
346    * @throws NotCompliantMBeanException (unless we refactor the code this cannot
347    * happend)
348    */

349   public DatabaseBackend(String JavaDoc name, String JavaDoc driverPath,
350       String JavaDoc driverClassName, String JavaDoc url, String JavaDoc vdbName,
351       String JavaDoc connectionTestStatement, String JavaDoc dynamicSchemaLevel)
352       throws NotCompliantMBeanException JavaDoc
353   {
354     this(name, driverPath, driverClassName, url, vdbName, true,
355         connectionTestStatement);
356     this.dynamicPrecision = DatabaseBackendSchemaConstants
357         .getDynamicSchemaLevel(dynamicSchemaLevel);
358   }
359
360   /**
361    * Sets the sqlShortFormLength value.
362    *
363    * @param sqlShortFormLength The sqlShortFormLength to set.
364    */

365   public void setSqlShortFormLength(int sqlShortFormLength)
366   {
367     this.sqlShortFormLength = sqlShortFormLength;
368   }
369
370   /**
371    * Return the sql short form length to use when reporting an error.
372    *
373    * @return sql short form length
374    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#getSQLShortForm(int)
375    */

376   public int getSQLShortFormLength()
377   {
378     return sqlShortFormLength;
379   }
380
381   /* Lookup functions */
382
383   /**
384    * Two database backends are considered equal if they have the same name, URL
385    * and driver class name.
386    *
387    * @param other an object
388    * @return a <code>boolean</code> value
389    */

390   public boolean equals(Object JavaDoc other)
391   {
392     if ((other == null) || (!(other instanceof DatabaseBackend)))
393       return false;
394     else
395     {
396       DatabaseBackend b = (DatabaseBackend) other;
397       return name.equals(b.getName())
398           && driverClassName.equals(b.getDriverClassName())
399           && url.equals(b.getURL());
400     }
401   }
402
403   /**
404    * Returns <code>true</code> if this backend has the given list of tables in
405    * its schema. The caller must ensure that the database schema has been
406    * defined, using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
407    * {@link #checkDatabaseSchema()}methods.
408    *
409    * @param tables the list of table names (<code>ArrayList</code> of
410    * <code>String</code>) to look for
411    * @return <code>true</code> if all the tables are found
412    */

413   public boolean hasTables(ArrayList JavaDoc tables)
414   {
415     DatabaseSchema schemaPtr = getDatabaseSchema();
416     if (schemaPtr == null)
417       throw new NullPointerException JavaDoc(Translate.get("backend.schema.not.set"));
418
419     if (tables == null)
420       throw new IllegalArgumentException JavaDoc(Translate.get("backend.null.tables"));
421
422     int size = tables.size();
423     for (int i = 0; i < size; i++)
424     {
425       if (!schemaPtr.hasTable((String JavaDoc) tables.get(i)))
426         return false;
427     }
428     return true;
429   }
430
431   /**
432    * Returns <code>true</code> if this backend has the given table in its
433    * schema. The caller must ensure that the database schema has been defined,
434    * using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
435    * {@link #checkDatabaseSchema()}
436    *
437    * @param table The table name to look for
438    * @return <code>true</code> if tables is found in the schema
439    */

440   public boolean hasTable(String JavaDoc table)
441   {
442     DatabaseSchema schemaPtr = getDatabaseSchema();
443     if (schemaPtr == null)
444       throw new NullPointerException JavaDoc(Translate.get("backend.schema.not.set"));
445
446     return schemaPtr.hasTable(table);
447   }
448
449   /**
450    * Get all the names of tables of this database
451    *
452    * @return <code>ArrayList</code> of <code>DatabaseTable</code>
453    */

454   public ArrayList JavaDoc getTables()
455   {
456     DatabaseSchema schemaPtr = getDatabaseSchema();
457     if (schemaPtr == null)
458       throw new NullPointerException JavaDoc(Translate.get("backend.schema.not.set"));
459     return schemaPtr.getTables();
460   }
461
462   /**
463    * Returns <code>true</code> if this backend has the given stored procedure
464    * in its schema. The caller must ensure that the database schema has been
465    * defined, using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
466    * {@link #checkDatabaseSchema()}
467    *
468    * @param procedureName The stored procedure name to look for
469    * @return <code>true</code> if procedure name is found in the schema
470    */

471   public boolean hasStoredProcedure(String JavaDoc procedureName)
472   {
473     DatabaseSchema schemaPtr = getDatabaseSchema();
474     if (schemaPtr == null)
475       throw new NullPointerException JavaDoc(Translate.get("backend.schema.not.set"));
476
477     return schemaPtr.hasProcedure(procedureName);
478   }
479
480   /* Connection management */
481
482   /**
483    * Initializes the connection managers' connections. The caller must ensure
484    * that the driver has already been loaded else an exception will be thrown.
485    *
486    * @exception SQLException if an error occurs
487    */

488   public synchronized void initializeConnections() throws SQLException JavaDoc
489   {
490     if (connectionManagers.isEmpty())
491       throw new SQLException JavaDoc(Translate.get("backend.not.defined", new String JavaDoc[]{
492           name, url}));
493
494     AbstractConnectionManager connectionManager;
495     Iterator JavaDoc iter = connectionManagers.values().iterator();
496     while (iter.hasNext())
497     {
498       connectionManager = (AbstractConnectionManager) iter.next();
499       if (!connectionManager.isInitialized())
500         connectionManager.initializeConnections();
501     }
502   }
503
504   /**
505    * Releases all the connections to the database held by the connection
506    * managers.
507    *
508    * @throws SQLException if an error occurs
509    */

510   public synchronized void finalizeConnections() throws SQLException JavaDoc
511   {
512     if (connectionManagers.isEmpty())
513       throw new SQLException JavaDoc(Translate.get("backend.not.defined", new String JavaDoc[]{
514           name, url}));
515
516     AbstractConnectionManager connectionManager;
517     Iterator JavaDoc iter = connectionManagers.values().iterator();
518     while (iter.hasNext())
519     {
520       connectionManager = (AbstractConnectionManager) iter.next();
521       if (connectionManager.isInitialized())
522         connectionManager.finalizeConnections();
523     }
524   }
525
526   /**
527    * Check if the given connection is valid or not. This function issues the
528    * connectionTestStatement query on the connection and if it succeeds then the
529    * connection is declared valid. If an exception occurs, the connection is
530    * declared invalid.
531    *
532    * @param connection the connection to test
533    * @return true if the connection is valid
534    */

535   public boolean isValidConnection(Connection JavaDoc connection)
536   {
537     try
538     {
539       Statement JavaDoc s = connection.createStatement();
540       s.executeQuery(connectionTestStatement);
541     }
542     catch (SQLException JavaDoc e)
543     {
544       if ("25P02".equals(e.getSQLState())
545           || (e.getMessage() != null && e
546               .getMessage()
547               .indexOf(
548                   "current transaction is aborted, queries ignored until end of transaction block") > 0))
549       {
550         // see bug item #300873 on the forge for details
551
// postgres throws an exception if a query is issued after a request has
552
// failed within a transaction, we now have to check for this exception
553
// as it is means the connection is valid
554
//
555
// postgres versions after 7.4 will return the SQLState, whereas
556
// postgres versions prior to 7.4 will have to be checked for the
557
// message text
558
return true;
559       }
560       return false;
561     }
562     return true;
563   }
564
565   /**
566    * Adds a <code>ConnectionManager</code> to this backend. Note that the
567    * <code>ConnectionManager</code> is not initialized in this method.
568    *
569    * @param vLogin the virtual login corresponding to this connection manager
570    * @param connectionManager the <code>ConnectionManager</code> to add
571    */

572   public void addConnectionManager(String JavaDoc vLogin,
573       AbstractConnectionManager connectionManager)
574   {
575     if (connectionManager == null)
576       throw new IllegalArgumentException JavaDoc(Translate.get(
577           "backend.null.connection.manager", new String JavaDoc[]{name, url}));
578     if (logger.isInfoEnabled())
579       logger.info(Translate.get("backend.add.connection.manager.for.user",
580           vLogin));
581     connectionManager.setVLogin(vLogin);
582     connectionManagers.put(vLogin, connectionManager);
583   }
584
585   /**
586    * Retrieve a connection for a given transaction or create a new connection
587    * and start a new transaction. <br>
588    * This method is synchronized so that concurrent writes within the same
589    * transaction that are allowed to execute out of order will not open separate
590    * connection if they race on transaction begin.
591    *
592    * @param tid transaction identifier
593    * @param cm connection manager to get the connection from
594    * @param transactionIsolationLevel transaction isolation level to use for a
595    * new transaction (does nothing if equals to
596    * Connection.DEFAULT_TRANSACTION_ISOLATION_LEVEL)
597    * @return the connection for the given transaction id
598    * @throws UnreachableBackendException if the backend is no more reachable
599    * @throws NoTransactionStartWhenDisablingException if a new transaction
600    * needed to be started but the backend is in the disabling state
601    * @throws SQLException if another error occurs
602    */

603   public synchronized Connection JavaDoc getConnectionForTransactionAndLazyBeginIfNeeded(
604       Long JavaDoc tid, AbstractConnectionManager cm, int transactionIsolationLevel)
605       throws UnreachableBackendException,
606       NoTransactionStartWhenDisablingException, SQLException JavaDoc
607   {
608     if (isStartedTransaction(tid))
609     { // Transaction has already been started, retrieve connection
610
return cm.retrieveConnection(tid.longValue());
611     }
612     else
613     {
614       if (isDisabling())
615         throw new NoTransactionStartWhenDisablingException();
616
617       // begin transaction
618
startTransaction(tid);
619
620       // Transaction has not been started yet, this is a lazy begin
621
return AbstractLoadBalancer.getConnectionAndBeginTransaction(this, cm,
622           tid.longValue(), transactionIsolationLevel);
623     }
624   }
625
626   /* State management */
627
628   /**
629    * Signals that a transaction has been started on this backend. It means that
630    * a connection has been allocated for this transaction.
631    *
632    * @param tid transaction identifier
633    */

634   public void startTransaction(Long JavaDoc tid)
635   {
636     synchronized (activeTransactions)
637     {
638       totalTransactions++;
639       activeTransactions.add(tid);
640     }
641   }
642
643   /**
644    * Signals that a transaction has been stopped on this backend. It means that
645    * the connection has been released for this transaction.
646    *
647    * @param tid transaction identifier
648    */

649   public void stopTransaction(Long JavaDoc tid)
650   {
651     synchronized (activeTransactions)
652     {
653       if (!activeTransactions.remove(tid))
654         throw new IllegalArgumentException JavaDoc(Translate.get(
655             "backend.transaction.not.started", new String JavaDoc[]{"" + tid, name}));
656       // If this was the last open transaction, we notify people possibly
657
// waiting on waitForAllTransactionsToComplete()
658
if (activeTransactions.isEmpty())
659       {
660         activeTransactions.notifyAll();
661       }
662     }
663
664     synchronized (savepoints)
665     {
666       savepoints.remove(tid);
667     }
668   }
669
670   /**
671    * This method waits until all currently open transactions on this backend
672    * complete. If no transaction are currently running on this backend, this
673    * method immediately returns.
674    */

675   public void waitForAllTransactionsToComplete()
676   {
677     synchronized (activeTransactions)
678     {
679       if (activeTransactions.isEmpty())
680         return;
681       else
682         try
683         {
684           activeTransactions.wait();
685         }
686         catch (InterruptedException JavaDoc ignore)
687         {
688         }
689     }
690   }
691
692   /**
693    * Returns <code>true</code> if the specified transaction has been started
694    * on this backend (a connection has been allocated for this transaction).
695    *
696    * @param tid transaction identifier
697    * @return <code>true</code> if the transaction has been started
698    */

699   public boolean isStartedTransaction(Long JavaDoc tid)
700   {
701     synchronized (activeTransactions)
702     {
703       return activeTransactions.contains(tid);
704     }
705   }
706
707   /**
708    * Adds a savepoint to a given transaction
709    *
710    * @param tid transaction identifier
711    * @param savepoint savepoint to add
712    */

713   public void addSavepoint(Long JavaDoc tid, Savepoint JavaDoc savepoint)
714   {
715     synchronized (savepoints)
716     {
717       List JavaDoc savepointList = (List JavaDoc) savepoints.get(tid);
718       if (savepointList == null)
719       { // Lazy list creation
720
savepointList = new ArrayList JavaDoc();
721         savepoints.put(tid, savepointList);
722       }
723       savepointList.add(savepoint);
724     }
725   }
726
727   /**
728    * Removes a savepoint for a given transaction
729    *
730    * @param tid transaction identifier
731    * @param savepoint savepoint to remove
732    */

733   public void removeSavepoint(Long JavaDoc tid, Savepoint JavaDoc savepoint)
734   {
735     synchronized (savepoints)
736     {
737       List JavaDoc savepointList = (List JavaDoc) savepoints.get(tid);
738       if (savepointList == null)
739         logger.error("No savepoints found for transaction " + tid);
740       else
741         savepointList.remove(savepoint);
742     }
743   }
744
745   /**
746    * Retrieves a savepoint object by its name for a given transaction
747    *
748    * @param tid transaction identifier
749    * @param savepointName name of the savepoint
750    * @return a savepoint
751    */

752   public Savepoint JavaDoc getSavepoint(Long JavaDoc tid, String JavaDoc savepointName)
753   {
754     synchronized (savepoints)
755     {
756       List JavaDoc savepointList = (List JavaDoc) savepoints.get(tid);
757       if (savepointList == null)
758         return null; // No checkpoint for that transaction
759

760       Iterator JavaDoc i = savepointList.iterator();
761       while (i.hasNext())
762       {
763         try
764         {
765           Savepoint JavaDoc savepoint = (Savepoint JavaDoc) i.next();
766           if (savepointName.equals(savepoint.getSavepointName()))
767             return savepoint;
768         }
769         catch (SQLException JavaDoc ignore)
770         {
771           // We should never get here because we always use named savepoints
772
// on backends
773
}
774       }
775     }
776
777     // No savepoint has been found for given savepoint name
778
return null;
779   }
780
781   /**
782    * Checks if this backend has a savepoint with given name for a given
783    * transaction
784    *
785    * @param tid transaction identifier
786    * @param savepointName name of the savepoint
787    * @return <code>true</code> if this backend has a savepoint with a given
788    * name for a given transaction
789    */

790   public boolean hasSavepointForTransaction(Long JavaDoc tid, String JavaDoc savepointName)
791   {
792     return (this.getSavepoint(tid, savepointName) != null);
793   }
794
795   /**
796    * Tests if this backend is enabled (active and synchronized).
797    *
798    * @return <code>true</code> if this backend is enabled
799    * @throws SQLException if an error occurs
800    */

801   public synchronized boolean isInitialized() throws SQLException JavaDoc
802   {
803     if (connectionManagers.isEmpty())
804       throw new SQLException JavaDoc(Translate.get("backend.null.connection.manager",
805           new String JavaDoc[]{name, url}));
806     Iterator JavaDoc iter = connectionManagers.values().iterator();
807     while (iter.hasNext())
808     {
809       if (!((AbstractConnectionManager) iter.next()).isInitialized())
810         return false;
811     }
812     return true;
813   }
814
815   /**
816    * Is the backend accessible ?
817    *
818    * @return <tt>true</tt> if a jdbc connection is still possible from the
819    * controller
820    */

821   public synchronized boolean isJDBCConnected()
822   {
823     try
824     {
825       if (connectionManagers.isEmpty())
826         throw new SQLException JavaDoc(Translate.get("backend.null.connection.manager",
827             new String JavaDoc[]{name, url}));
828
829       AbstractConnectionManager connectionManager;
830       Iterator JavaDoc iter = connectionManagers.values().iterator();
831       connectionManager = (AbstractConnectionManager) iter.next();
832
833       Connection JavaDoc con = connectionManager.getConnectionFromDriver();
834       con.createStatement().execute(this.connectionTestStatement);
835       return true;
836     }
837     catch (Exception JavaDoc e)
838     {
839       String JavaDoc msg = Translate.get("loadbalancer.backend.unreacheable", name);
840       logger.warn(msg, e);
841       return false;
842     }
843   }
844
845   /**
846    * Tests if this backend is read enabled (active and synchronized).
847    *
848    * @return <code>true</code> if this backend is enabled.
849    */

850   public synchronized boolean isReadEnabled()
851   {
852     return state == BackendState.READ_ENABLED_WRITE_DISABLED
853         || state == BackendState.READ_ENABLED_WRITE_ENABLED;
854   }
855
856   /**
857    * Tests if this backend is write enabled (active and synchronized).
858    *
859    * @return <code>true</code> if this backend is enabled.
860    */

861   public synchronized boolean isWriteEnabled()
862   {
863     return state == BackendState.READ_ENABLED_WRITE_ENABLED
864         || state == BackendState.READ_DISABLED_WRITE_ENABL