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_ENABLED;
865   }
866
867   /**
868    * Returns the isRecovering value.
869    *
870    * @return Returns the isRecovering.
871    */

872   public boolean isRecovering()
873   {
874     return state == BackendState.RECOVERING;
875   }
876
877   /**
878    * Returns the isDisabling value.
879    *
880    * @return Returns the isDisabling.
881    */

882   public boolean isDisabling()
883   {
884     return state == BackendState.DISABLING;
885   }
886
887   /**
888    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#isDisabled()
889    */

890   public boolean isDisabled()
891   {
892     return state == BackendState.DISABLED;
893   }
894
895   /**
896    * Returns true if the backend cannot be used anymore
897    *
898    * @return Returns true if the backend was removed from activity by the load
899    * balancer
900    */

901   public boolean isKilled()
902   {
903     return state == BackendState.UNKNOWN;
904   }
905
906   /**
907    * Retrieve the state of the backend.
908    *
909    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLED
910    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_RECOVERING
911    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_BACKINGUP
912    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLING
913    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_ENABLED
914    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLED
915    * @return one of the above
916    */

917   public String JavaDoc getState()
918   {
919     switch (state)
920     {
921       case BackendState.READ_ENABLED_WRITE_DISABLED :
922         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED;
923       case BackendState.READ_ENABLED_WRITE_ENABLED :
924         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED_WRITE;
925       case BackendState.READ_DISABLED_WRITE_ENABLED :
926         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED_WRITE;
927       case BackendState.DISABLING :
928         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_DISABLING;
929       case BackendState.BACKUPING :
930         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_BACKINGUP;
931       case BackendState.RECOVERING :
932         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_RECOVERING;
933       case BackendState.REPLAYING :
934         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_REPLAYING;
935       case BackendState.DISABLED :
936         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_DISABLED;
937       case BackendState.UNKNOWN :
938         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_UNKNOWN;
939       default :
940         throw new IllegalArgumentException JavaDoc("Unknown backend state:" + state);
941     }
942   }
943
944   /**
945    * Return the integer value corresponding to the state of the backend. The
946    * values are defined in <code>BackendState</code>
947    *
948    * @return <tt>int</tt> value
949    * @see BackendState
950    */

951   public int getStateValue()
952   {
953     return state;
954   }
955
956   /**
957    * Enables the database backend for reads. This method should only be called
958    * when the backend is synchronized with the others.
959    */

960   public synchronized void enableRead()
961   {
962     if (isWriteEnabled())
963       setState(BackendState.READ_ENABLED_WRITE_ENABLED);
964     else
965       setState(BackendState.READ_ENABLED_WRITE_DISABLED);
966   }
967
968   /**
969    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#disableRead()
970    */

971   public synchronized void disableRead()
972   {
973     if (isWriteEnabled())
974       setState(BackendState.READ_DISABLED_WRITE_ENABLED);
975     else
976       setState(BackendState.DISABLED);
977   }
978
979   /**
980    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#disableWrite()
981    */

982   public synchronized void disableWrite()
983   {
984     if (isReadEnabled())
985       setState(BackendState.READ_ENABLED_WRITE_DISABLED);
986     else
987       setState(BackendState.DISABLED);
988   }
989
990   /**
991    * Enables the database backend for writes. This method should only be called
992    * when the backend is synchronized with the others.
993    */

994   public synchronized void enableWrite()
995   {
996     // Remove last known checkpoint since backend will now be modified and no
997
// more synchronized with the checkpoint.
998
setLastKnownCheckpoint(null);
999     if (isReadEnabled())
1000      setState(BackendState.READ_ENABLED_WRITE_ENABLED);
1001    else
1002      setState(BackendState.READ_DISABLED_WRITE_ENABLED);
1003  }
1004
1005  /**
1006   * This is used when the backend must be disabled but currently open
1007   * transactions must terminate. This is a transitional state. When disabling
1008   * is complete the caller must set the backend state to disabled.
1009   * <p>
1010   * Reads are no more allowed on the backend and the state is updated so that
1011   * isReadEnabled() returns false.
1012   *
1013   * @see #disable()
1014   * @see #isReadEnabled()
1015   * @deprecated not used anymore. Please use the setState method instead
1016   */

1017  public void setDisabling()
1018  {
1019    setState(BackendState.DISABLING);
1020  }
1021
1022  /**
1023   * Sets the database backend state to disable. This state is just an
1024   * indication and it has no semantic effect. It is up to the request manager
1025   * (especially the load balancer) to ensure that no more requests are sent to
1026   * this backend.
1027   */

1028  public synchronized void disable()
1029  {
1030    setState(BackendState.DISABLED);
1031  }
1032
1033  /* Getter/setter methods */
1034
1035  /**
1036   * Returns the <code>ConnectionManager</code> associated to this backend for
1037   * a given virtual login.
1038   *
1039   * @param vLogin the virtual login
1040   * @return an <code>AbstractConnectionManager</code> instance
1041   */

1042  public AbstractConnectionManager getConnectionManager(String JavaDoc vLogin)
1043  {
1044    return (AbstractConnectionManager) connectionManagers.get(vLogin);
1045  }
1046
1047  /**
1048   * Returns a <code>HashMap</code> of all the <code>ConnectionManager</code>
1049   * associated with this <code>DatabaseBackend</code>
1050   *
1051   * @return the hashmap of connection managers
1052   */

1053  public HashMap JavaDoc getConnectionManagers()
1054  {
1055    return this.connectionManagers;
1056  }
1057
1058  /**
1059   * Returns the SQL statement to use to check the connection validity.
1060   *
1061   * @return a <code>String</code> containing a SQL statement
1062   */

1063  public String JavaDoc getConnectionTestStatement()
1064  {
1065    return connectionTestStatement;
1066  }
1067
1068  /**
1069   * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getDriverPath()
1070   */

1071  public String JavaDoc getDriverPath()
1072  {
1073    return driverPath;
1074  }
1075
1076  /**
1077   * @see org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean#getAssociatedString()
1078   */

1079  public String JavaDoc getAssociatedString()
1080  {
1081    return "backend";
1082  }
1083
1084  /**
1085   * Returns the database native JDBC driver class name.
1086   *
1087   * @return the driver class name
1088   */

1089  public String JavaDoc getDriverClassName()
1090  {
1091    return driverClassName;
1092  }
1093
1094  /**
1095   * Returns the backend logical name.
1096   *
1097   * @return the backend logical name
1098   */

1099  public String JavaDoc getName()
1100  {
1101    return name;
1102  }
1103
1104  /**
1105   * Returns the virtual database name this backend belongs to.
1106   *
1107   * @return Returns the virtual database name.
1108   */

1109  public String JavaDoc getVirtualDatabaseName()
1110  {
1111    return virtualDatabaseName;
1112  }
1113
1114  /**
1115   * Returns the list of pending requests for this backend.
1116   *
1117   * @return <code>Vector</code> of <code>AbstractRequests</code> or
1118   * <code>AbstractTask</code> objects
1119   */

1120  public Vector JavaDoc getPendingRequests()
1121  {
1122    return pendingRequests;
1123  }
1124
1125  /**
1126   * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getPendingRequestsDescription(int,
1127   * boolean, boolean)
1128   */

1129  public ArrayList JavaDoc getPendingRequestsDescription(int count, boolean fromFirst,
1130      boolean clone)
1131  {
1132    int size = pendingRequests.size();
1133    int limit = (count == 0 || count > size) ? size : Math.min(size, count);
1134    ArrayList JavaDoc list = new ArrayList JavaDoc(limit);
1135    int start = (fromFirst) ? 0 : Math.min(limit - count, 0);
1136    if (!clone)
1137    {
1138      synchronized (pendingRequests)
1139      {
1140        for (int i = start; i < limit; i++)
1141          list.add(pendingRequests.get(i).toString());
1142      }
1143      return list;
1144    }
1145    else
1146    {
1147      Vector JavaDoc cloneVector = (Vector JavaDoc) pendingRequests.clone();
1148      for (int i = start; i < limit; i++)
1149        list.add(cloneVector.get(i).toString());
1150      return list;
1151    }
1152  }
1153
1154  /**
1155   * Adds a pending request (or task) to this backend. Note that the underlying
1156   * vector is synchronized.
1157   *
1158   * @param request the request to add
1159   */

1160  public void addPendingReadRequest(Object JavaDoc request)
1161  {
1162    synchronized (this)
1163    {
1164      totalRequest++;
1165      totalReadRequest++;
1166    }
1167    pendingRequests.add(request);
1168  }
1169
1170  /**
1171   * Adds a pending request (or task) to this backend. Note that the underlying
1172   * vector is synchronized.
1173   *
1174   * @param request the request to add
1175   */

1176  public void addPendingWriteRequest(Object JavaDoc request)
1177  {
1178    synchronized (this)
1179    {
1180      totalRequest++;
1181      totalWriteRequest++;
1182    }
1183    pendingRequests.add(request);
1184  }
1185
1186  /**
1187   * Removes a pending request from this backend. Note that the underlying
1188   * vector is synchronized.
1189   *
1190   * @param request the request to remove
1191   * @return <code>true</code> if the request has been found and removed
1192   */

1193  public boolean removePendingRequest(Object JavaDoc request)
1194  {
1195    return pendingRequests.remove(request);
1196  }
1197
1198  //
1199
// Schema manipulation
1200
//
1201

1202  /**
1203   * @see DatabaseBackendMBean#checkDatabaseSchema()
1204   */

1205  public synchronized boolean checkDatabaseSchema()
1206  {
1207    if (logger.isDebugEnabled())
1208      logger.debug(Translate.get("backend.dynamic.schema",
1209          DatabaseBackendSchemaConstants
1210              .getDynamicSchemaLevel(dynamicPrecision)));
1211
1212    boolean checked = true;
1213    AbstractConnectionManager connectionMananger;
1214    Iterator JavaDoc iter = connectionManagers.values().iterator();
1215    while (iter.hasNext())
1216    {
1217      connectionMananger = (AbstractConnectionManager) iter.next();
1218
1219      // Gather the database schema from this connection manager
1220
DatabaseBackendMetaData meta = new DatabaseBackendMetaData(
1221          connectionMananger, logger, dynamicPrecision, gatherSystemTables,
1222          schemaName);
1223
1224      DatabaseSchema metaSchema;
1225      try
1226      {
1227        if (logger.isInfoEnabled())
1228          logger.info(Translate.get("backend.gathering.database.schema"));
1229        metaSchema = meta.getDatabaseSchema();
1230      }
1231      catch (SQLException JavaDoc e)
1232      {
1233        if (logger.isWarnEnabled())
1234          logger.warn(Translate.get("backend.gather.schema.failed", e));
1235        return false;
1236      }
1237      if (schema == null)
1238      {
1239        if (logger.isDebugEnabled())
1240          logger.debug(Translate.get("backend.use.gathered.schema.as.new"));
1241        schema = metaSchema;
1242      }
1243      else
1244      {
1245        if (dynamicPrecision == DatabaseBackendSchemaConstants.DynamicPrecisionStatic)
1246        {
1247          if (logger.isInfoEnabled())
1248            logger.info(Translate.get("backend.schema.static.no.check", name));
1249        }
1250        else
1251        {
1252          if (logger.isInfoEnabled())
1253            logger.info(Translate.get("backend.check.schema.compatibility"));
1254          if (schema.isCompatibleSubset(metaSchema))
1255            logger.info(Translate.get("backend.schema.compatible.for.login",
1256                connectionMananger.getLogin()));
1257          else
1258          {
1259            checked = false;
1260            logger.warn(Translate.get(
1261                "backend.schema.not.compatible.for.login", connectionMananger
1262                    .getLogin()));
1263          }
1264        }
1265      }
1266    }
1267    setSchemaIsDirty(false);
1268    return checked;
1269  }
1270
1271  /**
1272   * Returns the schema of this database.
1273   *
1274   * @return the schema of this database. Returns <code>null</code> if the
1275   * schema has not been set.
1276   * @see #setDatabaseSchema(DatabaseSchema, boolean)
1277   */

1278  public synchronized DatabaseSchema getDatabaseSchema()
1279  {
1280    if (schemaIsNeededByVdb && schemaIsDirty && !schemaIsStatic)
1281      refreshSchema();
1282    return schema;
1283  }
1284
1285  /**
1286   * Get the Database static metadata from this backend using a connection from
1287   * the first available connection manager.
1288   *
1289   * @return Static metadata information
1290   */

1291  public MetadataContainer getDatabaseStaticMetadata()
1292  {
1293    AbstractConnectionManager connectionMananger;
1294    Iterator JavaDoc iter = connectionManagers.values().iterator();
1295    if (iter.hasNext())
1296    {
1297      connectionMananger = (AbstractConnectionManager) iter.next();
1298      // Gather the static metadata from the first connection manager
1299
DatabaseBackendMetaData meta = new DatabaseBackendMetaData(
1300          connectionMananger, logger, dynamicPrecision, gatherSystemTables,
1301          schemaName);
1302      try
1303      {
1304        return meta.retrieveDatabaseMetadata();
1305      }
1306      catch (SQLException JavaDoc e)
1307      {
1308        return null;
1309      }
1310    }
1311    else
1312      return null;
1313  }
1314
1315  /**
1316   * @return Returns the dynamicPrecision.
1317   */

1318  public int getDynamicPrecision()
1319  {
1320    return dynamicPrecision;
1321  }
1322
1323  /**
1324   * Returns the schemaName value.
1325   *
1326   * @return Returns the schemaName.
1327   */

1328  public String JavaDoc getSchemaName()
1329  {
1330    return schemaName;
1331  }
1332
1333  /**
1334   * Returns the gatherSystemTables value.
1335   *
1336   * @return Returns the gatherSystemTables.
1337   */

1338  public boolean isGatherSystemTables()
1339  {
1340    return gatherSystemTables;
1341  }
1342
1343  /**
1344   * Returns the schemaIsDirty value.
1345   *
1346   * @return Returns true if the backend database schema is dirty and needs a
1347   * refresh.
1348   */

1349  public boolean isSchemaDirty()
1350  {
1351    return schemaIsDirty;
1352  }
1353
1354  /**
1355   * @return Returns the schemaIsStatic.
1356   */

1357  public boolean isSchemaStatic()
1358  {
1359    return schemaIsStatic;
1360  }
1361
1362  /**
1363   * Returns true if an up-to-date schema is needed by the virtual database.
1364   *
1365   * @return Returns the schemaIsNeededByVdb.
1366   */

1367  public boolean isSchemaNeededByVdb()
1368  {
1369    return schemaIsNeededByVdb;
1370  }
1371
1372  /**
1373   * Erase the current schema and force a re-fetch of all the meta data
1374   */

1375  private synchronized void refreshSchema()
1376  {
1377    setDatabaseSchema(null, isSchemaStatic());
1378    checkDatabaseSchema(); // set dirty to false as well
1379
}
1380
1381  /**
1382   * Sets the database schema.
1383   *
1384   * @param databaseSchema the schema to set
1385   * @param isStatic <code>true</code> if the schema should be static
1386   * @see #getDatabaseSchema()
1387   */

1388  public synchronized void setDatabaseSchema(DatabaseSchema databaseSchema,
1389      boolean isStatic)
1390  {
1391    if (schema == null)
1392    {
1393      schemaIsStatic = isStatic;
1394      schema = databaseSchema;
1395    }
1396    else
1397    {
1398      if (!isStatic)
1399        schema = databaseSchema;
1400    }
1401  }
1402
1403  /**
1404   * Set the amount of information that must be gathered when fetching database
1405   * schema information.
1406   *
1407   * @param dynamicPrecision The dynamicPrecision to set.
1408   * @param gatherSystemTables True if we must gather system tables
1409   * @param schemaName Schema name to use to gather tables
1410   */

1411  public void setDynamicPrecision(int dynamicPrecision,
1412      boolean gatherSystemTables, String JavaDoc schemaName)
1413  {
1414    this.dynamicPrecision = dynamicPrecision;
1415    this.gatherSystemTables = gatherSystemTables;
1416    this.schemaName = schemaName;
1417  }
1418
1419  /**
1420   * Sets the schemaIsDirty value if the backend schema needs to be refreshed.
1421   *
1422   * @param schemaIsDirty The schemaIsDirty to set.
1423   */

1424  public void setSchemaIsDirty(boolean schemaIsDirty)
1425  {
1426    this.schemaIsDirty = schemaIsDirty;
1427  }
1428
1429  /**
1430   * Sets the schemaIsNeededByVdb value.
1431   *
1432   * @param schemaIsNeededByVdb The schemaIsNeededByVdb to set.
1433   */

1434  public void setSchemaIsNeededByVdb(boolean schemaIsNeededByVdb)
1435  {
1436    this.schemaIsNeededByVdb = schemaIsNeededByVdb;
1437  }
1438
1439  /**
1440   * Update the DatabaseBackend schema definition according to the successful
1441   * execution of the provided request. Note that the schema is only updated it
1442   * the provided request is a DDL statement.
1443   *
1444   * @param request the request that possibly updates the schema
1445   */

1446  public void updateDatabaseBackendSchema(AbstractWriteRequest request)
1447  {
1448    if (!request.isDDL())
1449      return;
1450
1451    // Update schema
1452
if (isSchemaNeededByVdb())
1453    {
1454      if (request.isCreate())
1455      { // Add the table to the schema
1456
DatabaseSchema dbs = getDatabaseSchema();
1457        if (dbs != null)
1458        {
1459          CreateRequest createRequest = (CreateRequest) request;
1460          if (createRequest.altersDatabaseSchema())
1461          {
1462            DatabaseTable t = createRequest.getDatabaseTable();
1463            if (t != null)
1464            {
1465              dbs.addTable(t);
1466              if (logger.isDebugEnabled())
1467                logger.debug("Added table '" + request.getTableName()
1468                    + "' to backend database schema");
1469            }
1470            else
1471              // Unsupported force re-fetch from db
1472
setSchemaIsDirty(true);
1473          }
1474          else
1475            // Unsupported force re-fetch from db
1476
setSchemaIsDirty(true);
1477        }
1478      }
1479      else if (request.isDrop())
1480      { // Delete the table from the schema
1481
DatabaseSchema dbs = getDatabaseSchema();
1482        if (dbs != null)
1483        {
1484          DatabaseTable t = dbs.getTable(request.getTableName());
1485          if (t != null)
1486          {
1487            dbs.removeTable(t);
1488            if (logger.isDebugEnabled())
1489              logger.debug("Removed table '" + request.getTableName()
1490                  + "' from backend database schema");
1491          }
1492          else
1493            // Unsupported force re-fetch from db
1494
setSchemaIsDirty(true);
1495        }
1496        else
1497          // Unsupported force re-fetch from db
1498
setSchemaIsDirty(true);
1499      }
1500      else
1501        // Unsupported force re-fetch from db
1502
setSchemaIsDirty(true);
1503    }
1504    else
1505      // Unsupported force re-fetch from db
1506
setSchemaIsDirty(true);
1507  }
1508
1509  //
1510
// Driver compliance
1511
//
1512

1513  /**
1514   * @return the driver compliance to C-JDBC requirements.
1515   */

1516  public DriverCompliance getDriverCompliance()
1517  {
1518    return driverCompliance;
1519  }
1520
1521  /**
1522   * Check if the driver used by this backend is compliant with C-JDBC needs.
1523   *
1524   * @throws SQLException if the driver is not compliant
1525   */

1526  public void checkDriverCompliance() throws SQLException JavaDoc
1527  {
1528    if (connectionManagers.isEmpty())
1529      throw new SQLException JavaDoc(Translate.get("backend.null.connection.manager",
1530          new String JavaDoc[]{name, url}));
1531
1532    AbstractConnectionManager connectionManager;
1533    Iterator JavaDoc iter = connectionManagers.values().iterator();
1534    connectionManager = (AbstractConnectionManager) iter.next();
1535
1536    try
1537    {
1538      if (!driverCompliance.complianceTest(url, connectionManager.getLogin(),
1539          connectionManager.getPassword(), connectionManager.getDriverPath(),
1540          connectionManager.getDriverClassName(), connectionTestStatement))
1541        throw new SQLException JavaDoc(Translate.get("backend.driver.not.compliant",
1542            driverClassName));
1543    }
1544    catch (ConnectException JavaDoc e)
1545    {
1546      throw new SQLException JavaDoc(Translate.get("backend.cannot.connect.to", e));
1547    }
1548  }
1549
1550  /**
1551   * Returns the JDBC URL used to access the database.
1552   *
1553   * @return a JDBC URL
1554   */

1555  public String JavaDoc getURL()
1556  {
1557    return url;
1558  }
1559
1560  /*
1561   * Rewriting Rule management
1562   */

1563
1564  /**
1565   * Add a <code>AbstractRewritingRule</code> at the end of the rule list.
1566   *
1567   * @param rule a AbstractRewritingRule
1568   */

1569  public void addRewritingRule(AbstractRewritingRule rule)
1570  {
1571    if (rewritingRules == null)
1572      rewritingRules = new ArrayList JavaDoc();
1573    if (logger.isDebugEnabled())
1574      logger.debug(Translate.get("backend.rewriting.rule.add", new String JavaDoc[]{
1575          rule.getQueryPattern(), rule.getRewrite()}));
1576    rewritingRules.add(rule);
1577  }
1578
1579  /**
1580   * Rewrite the current query according to the rewriting rules.
1581   *
1582   * @param sqlQuery request to rewrite
1583   * @return the rewritten SQL query according to rewriting rules.
1584   */

1585  public String JavaDoc rewriteQuery(String JavaDoc sqlQuery)
1586  {
1587    if (rewritingRules == null)
1588      return sqlQuery;
1589    int size = rewritingRules.size();
1590    for (int i = 0; i < size; i++)
1591    {
1592      AbstractRewritingRule rule = (AbstractRewritingRule) rewritingRules
1593          .get(i);
1594      sqlQuery = rule.rewrite(sqlQuery);
1595      if (rule.hasMatched())
1596      { // Rule matched, query rewriten
1597
if (logger.isDebugEnabled())
1598          logger.debug(Translate.get("backend.rewriting.query", sqlQuery));
1599        if (rule.isStopOnMatch())
1600          break; // Ok, stop here.
1601
}
1602    }
1603    return sqlQuery;
1604  }
1605
1606  /*
1607   * Debug/Monitoring
1608   */

1609
1610  /**
1611   * Get xml information about this backend.
1612   *
1613   * @return xml formatted information on this database backend.
1614   */

1615  public synchronized String JavaDoc getXml()
1616  {
1617    StringBuffer JavaDoc info = new StringBuffer JavaDoc();
1618    info.append("<" + DatabasesXmlTags.ELT_DatabaseBackend + " "
1619        + DatabasesXmlTags.ATT_name + "=\"" + name + "\" "
1620        + DatabasesXmlTags.ATT_driver + "=\"" + driverClassName + "\" "
1621        + DatabasesXmlTags.ATT_url + "=\"" + url + "\" "
1622        + DatabasesXmlTags.ATT_connectionTestStatement + "=\""
1623        + connectionTestStatement + "\">");
1624
1625    boolean expandSchema = this.schema != null
1626        && dynamicPrecision == DatabaseBackendSchemaConstants.DynamicPrecisionStatic;
1627
1628    info.append(getSchemaXml(expandSchema));
1629
1630    if (rewritingRules != null)
1631    {
1632      int size = rewritingRules.size();
1633      for (int i = 0; i < size; i++)
1634        info.append(((AbstractRewritingRule) rewritingRules.get(i)).getXml());
1635    }
1636    if (connectionManagers != null)
1637    {
1638      if (connectionManagers.isEmpty() == false)
1639      {
1640        AbstractConnectionManager connectionManager;
1641        Iterator JavaDoc iter = connectionManagers.values().iterator();
1642        while (iter.hasNext())
1643        {
1644          connectionManager = (AbstractConnectionManager) iter.next();
1645          info.append(connectionManager.getXml());
1646        }
1647      }
1648    }
1649    info.append("</" + DatabasesXmlTags.ELT_DatabaseBackend + ">");
1650    return info.toString();
1651  }
1652
1653  /**
1654   * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getSchemaXml(boolean)
1655   */

1656  public String JavaDoc getSchemaXml(boolean expandSchema)
1657  {
1658    StringBuffer JavaDoc info = new StringBuffer JavaDoc();
1659    info.append("<"
1660        + DatabasesXmlTags.ELT_DatabaseSchema
1661        + " "
1662        + DatabasesXmlTags.ATT_dynamicPrecision
1663        + "=\""
1664        + DatabaseBackendSchemaConstants
1665            .getDynamicSchemaLevel(dynamicPrecision) + "\" "
1666        + DatabasesXmlTags.ATT_gatherSystemTables + "=\""
1667        + (gatherSystemTables ? "true" : "false") + "\">");
1668    synchronized (this)
1669    {
1670      if (expandSchema && (schema != null))
1671        info.append(schema.getXml());
1672    }
1673    info.append("</" + DatabasesXmlTags.ELT_DatabaseSchema + ">");
1674    return info.toString();
1675  }
1676
1677  /**
1678   * @return Returns the activeTransactions.
1679   */

1680  public ArrayList JavaDoc getActiveTransactions()
1681  {
1682    return activeTransactions;
1683  }
1684
1685  /**
1686   * Get data about this backend. Format is:
1687   *
1688   * <pre>
1689   * data[0] = this.name;
1690   * data[1] = this.driverClassName;
1691   * data[2] = this.url;
1692   * data[3] = String.valueOf(this.activeTransactions.size());
1693   * data[4] = String.valueOf(this.pendingRequests.size());
1694   * data[5] = String.valueOf(this.isReadEnabled());
1695   * data[6] = String.valueOf(this.isWriteEnabled());
1696   * data[7] = String.valueOf(this.isInitialized());
1697   * data[8] = String.valueOf(this.schemaIsStatic);
1698   * data[9] = String.valueOf(this.connectionManagers.size());
1699   * data[10] = String.valueOf(getTotalActiveConnections());
1700   * data[11] = String.valueOf(totalRequest);
1701   * data[12] = String.valueOf(totalTransactions);
1702   * data[13] = lastKnownCheckpoint;
1703   *</pre>
1704   *
1705   * @return an array of strings
1706   */

1707  public String JavaDoc[] getBackendData()
1708  {
1709    String JavaDoc[] data = new String JavaDoc[14];
1710    data[0] = this.name;
1711    data[1] = this.driverClassName;
1712    data[2] = this.url;
1713    data[3] = String.valueOf(this.activeTransactions.size());
1714    data[4] = String.valueOf(this.pendingRequests.size());
1715    data[5] = String.valueOf(this.isReadEnabled());
1716    data[6] = String.valueOf(this.isWriteEnabled());
1717    try
1718    {
1719      data[7] = String.valueOf(this.isInitialized());
1720    }
1721    catch (Exception JavaDoc e)
1722    {
1723      data[7] = "unknown";
1724    }
1725    data[8] = String.valueOf(this.schemaIsStatic);
1726
1727    data[9] = String.valueOf(this.connectionManagers.size());
1728    data[10] = String.valueOf(getTotalActiveConnections());
1729    data[11] = String.valueOf(totalRequest);
1730    data[12] = String.valueOf(totalTransactions);
1731    if (lastKnownCheckpoint == null || lastKnownCheckpoint.equalsIgnoreCase(""))
1732      data[13] = "<unknown>";
1733    else
1734      data[13] = lastKnownCheckpoint;
1735    return data;
1736  }
1737
1738  /**
1739   * Get the statistics of the backend.
1740   *
1741   * @return a BackendStatistics
1742   */

1743  public BackendStatistics getBackendStats()
1744  {
1745    BackendStatistics stats = new BackendStatistics();
1746    stats.setBackendName(name);
1747    stats.setDriverClassName(driverClassName);
1748    stats.setUrl(url);
1749    stats.setNumberOfActiveTransactions(activeTransactions.size());
1750    stats.setNumberOfPendingRequests(pendingRequests.size());
1751    stats.setReadEnabled(isReadEnabled());
1752    stats.setWriteEnabled(isWriteEnabled());
1753    String JavaDoc initializationStatus = "<unknown>";
1754    try
1755    {
1756      initializationStatus = String.valueOf(this.isInitialized());
1757    }
1758    catch (Exception JavaDoc e)
1759    {
1760    }
1761    stats.setInitializationStatus(initializationStatus);
1762    stats.setSchemaStatic(schemaIsStatic);
1763    stats.setNumberOfConnectionManagers(connectionManagers.size());
1764    stats.setNumberOfTotalActiveConnections(getTotalActiveConnections());
1765    stats.setNumberOfTotalRequests(totalRequest);
1766    stats.setNumberOfTotalTransactions(totalTransactions);
1767    if (lastKnownCheckpoint == null || lastKnownCheckpoint.equalsIgnoreCase(""))
1768      stats.setLastKnownCheckpoint("<unknown>");
1769    else
1770      stats.setLastKnownCheckpoint(lastKnownCheckpoint);
1771    return stats;
1772  }
1773
1774  /**
1775   * Get the total number of active connections for this backend
1776   *
1777   * @return number of active connections for all
1778   * <code>AbstractConnectionManager</code> connected to this backend
1779   */

1780  public long getTotalActiveConnections()
1781  {
1782    int activeConnections = 0;
1783    Iterator JavaDoc iter = connectionManagers.keySet().iterator();
1784    while (iter.hasNext())
1785      activeConnections += ((AbstractConnectionManager) connectionManagers
1786          .get(iter.next())).getCurrentNumberOfConnections();
1787    return activeConnections;
1788  }
1789
1790  /**
1791   * Returns the total number of transactions executed by this backend.
1792   *
1793   * @return Total number of transactions.
1794   */

1795  public int getTotalTransactions()
1796  {
1797    return totalTransactions;
1798  }
1799
1800  /**
1801   * Returns the total number of read requests executed by this backend.
1802   *
1803   * @return Returns the totalReadRequest.
1804   */

1805  public int getTotalReadRequest()
1806  {
1807    return totalReadRequest;
1808  }
1809
1810  /**
1811   * Returns the total number of write requests executed by this backend.
1812   *
1813   * @return Returns the totalWriteRequest.
1814   */

1815  public int getTotalWriteRequest()
1816  {
1817    return totalWriteRequest;
1818  }
1819
1820  /**
1821   * Returns the total number of requests executed by this backend.
1822   *
1823   * @return Returns the totalRequest.
1824   */

1825  public int getTotalRequest()
1826  {
1827    return totalRequest;
1828  }
1829
1830  /**
1831   * setLastKnownCheckpoint for this backend
1832   *
1833   * @param checkpoint the checkpoint
1834   */

1835  public void setLastKnownCheckpoint(String JavaDoc checkpoint)
1836  {
1837    this.lastKnownCheckpoint = checkpoint;
1838  }
1839
1840  /**
1841   * Returns the lastKnownCheckpoint value.
1842   *
1843   * @return Returns the lastKnownCheckpoint.
1844   */

1845  public String JavaDoc getLastKnownCheckpoint()
1846  {
1847    return lastKnownCheckpoint;
1848  }
1849
1850  /**
1851   * Returns the databaseProductName value.
1852   *
1853   * @return Returns the databaseProductName.
1854   */

1855  public String JavaDoc getDatabaseProductName()
1856  {
1857    return driverCompliance.getDatabaseProductName();
1858  }
1859
1860  /**
1861   * Returns the rewritingRules value.
1862   *
1863   * @return Returns the rewritingRules.
1864   */

1865  public ArrayList JavaDoc getRewritingRules()
1866  {
1867    return rewritingRules;
1868  }
1869
1870  /**
1871   * Sets the rewritingRules value.
1872   *
1873   * @param rewritingRules The rewritingRules to set.
1874   */

1875  public void setRewritingRules(ArrayList JavaDoc rewritingRules)
1876  {
1877    this.rewritingRules = rewritingRules;
1878  }
1879
1880  /**
1881   * Returns a deeply copied clone of this backend Will use the same rewriting
1882   * rules and will get new instance of connection managers with the same
1883   * configuration
1884   *
1885   * @param newName the new name for this new backend
1886   * @param parameters a set of parameters to use to replace values from the
1887   * copied backend. <br>
1888   * The different parameters are: <br>
1889   * <ul>
1890   * <li><tt>driverPath</tt>: the path to the driver</li>
1891   * <li><tt>driver</tt>: the driver class name</li>
1892   * <li><tt>url</tt>: the url to connect to the database</li>
1893   * <li><tt>connectionTestStatement</tt>: the query to test the
1894   * connection</li>
1895   * </ul>
1896   * <br>
1897   * @return <code>DatabaseBackend</code> instance
1898   * @throws Exception if cannot proceed the copy
1899   */

1900  public DatabaseBackend copy(String JavaDoc newName, Map JavaDoc parameters) throws Exception JavaDoc
1901  {
1902    // Get the parameters from the backend if they are not specified, or take
1903
// them from the map of parameters otherwise.
1904
String JavaDoc fromDriverPath = parameters
1905        .containsKey(DatabasesXmlTags.ATT_driverPath) ? (String JavaDoc) parameters
1906        .get(DatabasesXmlTags.ATT_driverPath) : this.getDriverPath();
1907
1908    String JavaDoc fromDriverClassName = parameters
1909        .containsKey(DatabasesXmlTags.ATT_driver) ? (String JavaDoc) parameters
1910        .get(DatabasesXmlTags.ATT_driver) : this.getDriverClassName();
1911
1912    String JavaDoc fromUrl = parameters.containsKey(DatabasesXmlTags.ATT_url)
1913        ? (String JavaDoc) parameters.get(DatabasesXmlTags.ATT_url)
1914        : this.getURL();
1915
1916    String JavaDoc fromConnectionTestStatement = parameters
1917        .containsKey(DatabasesXmlTags.ATT_connectionTestStatement)
1918        ? (String JavaDoc) parameters.get(DatabasesXmlTags.ATT_connectionTestStatement)
1919        : this.getConnectionTestStatement();
1920
1921    // Create the new backend object
1922
DatabaseBackend newBackend = new DatabaseBackend(newName, fromDriverPath,
1923        fromDriverClassName, fromUrl, virtualDatabaseName, writeCanBeEnabled,
1924        fromConnectionTestStatement);
1925
1926    // Clone dynamic precision
1927
newBackend.setDynamicPrecision(this.dynamicPrecision,
1928        this.gatherSystemTables, this.schemaName);
1929
1930    // Set the rewriting rules and the connection managers as the backend we
1931
// are copying
1932
newBackend.setRewritingRules(this.getRewritingRules());
1933
1934    // Set Connection managers
1935
HashMap JavaDoc fromConnectionManagers = this.getConnectionManagers();
1936    Iterator JavaDoc iter = fromConnectionManagers.keySet().iterator();
1937
1938    String JavaDoc vlogin = null;
1939    AbstractConnectionManager connectionManager;
1940    while (iter.hasNext())
1941    {
1942      vlogin = (String JavaDoc) iter.next();
1943      connectionManager = (AbstractConnectionManager) fromConnectionManagers
1944          .get(vlogin);
1945      newBackend.addConnectionManager(vlogin, connectionManager.copy(fromUrl,
1946          newName));
1947    }
1948
1949    return newBackend;
1950  }
1951
1952  /**
1953   * Returns the isBackuping value.
1954   *
1955   * @return Returns the isBackuping.
1956   */

1957  public boolean isBackuping()
1958  {
1959    return state == BackendState.BACKUPING;
1960  }
1961
1962  /**
1963   * Set the state of a backend
1964   *
1965   * @param state see BackendState for a possible list of the different state
1966   * @see org.objectweb.cjdbc.common.shared.BackendState
1967   */

1968  public synchronized void setState(int state)
1969  {
1970    switch (state)
1971    {
1972      case BackendState.UNKNOWN :
1973        lastKnownCheckpoint = null;
1974        break;
1975      case BackendState.READ_ENABLED_WRITE_DISABLED :
1976      case BackendState.READ_ENABLED_WRITE_ENABLED :
1977      case BackendState.READ_DISABLED_WRITE_ENABLED :
1978      case BackendState.DISABLING :
1979      case BackendState.BACKUPING :
1980      case BackendState.RECOVERING :
1981      case BackendState.REPLAYING :
1982      case BackendState.DISABLED :
1983        break;
1984      default :
1985        throw new IllegalArgumentException JavaDoc("Unknown backend state:" + state);
1986    }
1987    this.state = state;
1988    if (logger.isDebugEnabled())
1989      logger.debug(Translate.get("backend.state.changed", new String JavaDoc[]{name,
1990          getState()}));
1991    notifyStateChange();
1992  }
1993
1994  /**
1995   * Notify the state of the backend has changed. This does two things: 1.
1996   * Change the state of the backend stored in the recovery log 2. Sends a jmx
1997   * notification. This method has all the data prefilled because we know all
1998   * the parameters in advance, except the type of the notification.
1999   *
2000   * @see CjdbcNotificationList
2001   */

2002  public void notifyStateChange()
2003  {
2004    if (stateListener != null)
2005      stateListener.changeState(this);
2006    notifyJmx(getState());
2007  }
2008
2009  /**
2010   * Sends JMX notification
2011   *
2012   * @param type notification type
2013   * @see CjdbcNotificationList
2014   */

2015  public void notifyJmx(String JavaDoc type)
2016  {
2017    notifyJmx(type, CjdbcNotificationList.NOTIFICATION_LEVEL_INFO, Translate
2018        .get(type, getName()));
2019  }
2020
2021  /**
2022   * Sends JMX error notification
2023   *
2024   * @param e <tt>Exception</tt> object. Only the message will be used
2025   * @param type notification type
2026   * @see CjdbcNotificationList
2027   */

2028  public void notifyJmxError(String JavaDoc type, Exception JavaDoc e)
2029  {
2030    notifyJmx(type, CjdbcNotificationList.NOTIFICATION_LEVEL_ERROR, Translate
2031        .get(type, new String JavaDoc[]{getName(), e.getMessage()}));
2032
2033  }
2034
2035  private void notifyJmx(String JavaDoc type, String JavaDoc level, String JavaDoc message)
2036  {
2037    if (MBeanServerManager.isJmxEnabled())
2038    {
2039      // Send notification
2040
Hashtable JavaDoc data = new Hashtable JavaDoc();
2041      data.put(CjdbcNotificationList.DATA_DATABASE, getVirtualDatabaseName());
2042      data.put(CjdbcNotificationList.DATA_DRIVER, getDriverClassName());
2043      String JavaDoc checkpoint = getLastKnownCheckpoint();
2044      checkpoint = (checkpoint == null) ? "" : checkpoint;
2045      data.put(CjdbcNotificationList.DATA_CHECKPOINT, checkpoint);
2046      data.put(CjdbcNotificationList.DATA_NAME, getName());
2047      data.put(CjdbcNotificationList.DATA_URL, getURL());
2048      RmiConnector.broadcastNotification(this, type, level, message, data);
2049    }
2050  }
2051
2052  /**
2053   * Returns the writeCanBeEnabled value.
2054   *
2055   * @return Returns the writeCanBeEnabled.
2056   */

2057  public boolean isWriteCanBeEnabled()
2058  {
2059    return writeCanBeEnabled;
2060  }
2061
2062  /**
2063   * Sets the stateListener value.
2064   *
2065   * @param stateListener The stateListener to set.
2066   */

2067  public void setStateListener(BackendStateListener stateListener)
2068  {
2069    this.stateListener = stateListener;
2070  }
2071
2072  /**
2073   * String description
2074   *
2075   * @return a string description of the backend.
2076   */

2077  public String JavaDoc toString()
2078  {
2079    return "Backend: Name[" + this.name + "] State[" + this.state
2080        + "] JDBCConnected[" + isJDBCConnected() + "] ActiveTransactions["
2081        + activeTransactions.size() + "] PendingRequests["
2082        + pendingRequests.size() + "]";
2083  }
2084
2085}
Popular Tags