Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

DatabaseBackend.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2005 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Emmanuel Cecchet.
00022  * Contributor(s): Mathieu Peltier, Sara Bouchenak.
00023  */
00024 
00025 package org.objectweb.cjdbc.controller.backend;
00026 
00027 import java.io.IOException;
00028 import java.io.Serializable;
00029 import java.io.StringReader;
00030 import java.net.ConnectException;
00031 import java.sql.Connection;
00032 import java.sql.SQLException;
00033 import java.sql.Statement;
00034 import java.util.ArrayList;
00035 import java.util.HashMap;
00036 import java.util.Hashtable;
00037 import java.util.Iterator;
00038 import java.util.Map;
00039 import java.util.Vector;
00040 
00041 import javax.management.NotCompliantMBeanException;
00042 
00043 import org.dom4j.Document;
00044 import org.dom4j.Element;
00045 import org.dom4j.io.SAXReader;
00046 import org.objectweb.cjdbc.common.exceptions.NoTransactionStartWhenDisablingException;
00047 import org.objectweb.cjdbc.common.exceptions.UnreachableBackendException;
00048 import org.objectweb.cjdbc.common.i18n.Translate;
00049 import org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean;
00050 import org.objectweb.cjdbc.common.jmx.notifications.CjdbcNotificationList;
00051 import org.objectweb.cjdbc.common.log.Trace;
00052 import org.objectweb.cjdbc.common.shared.BackendInfo;
00053 import org.objectweb.cjdbc.common.shared.BackendState;
00054 import org.objectweb.cjdbc.common.sql.metadata.MetadataContainer;
00055 import org.objectweb.cjdbc.common.sql.schema.DatabaseSchema;
00056 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
00057 import org.objectweb.cjdbc.common.xml.XmlComponent;
00058 import org.objectweb.cjdbc.controller.backend.rewriting.AbstractRewritingRule;
00059 import org.objectweb.cjdbc.controller.connection.AbstractConnectionManager;
00060 import org.objectweb.cjdbc.controller.connection.FailFastPoolConnectionManager;
00061 import org.objectweb.cjdbc.controller.connection.RandomWaitPoolConnectionManager;
00062 import org.objectweb.cjdbc.controller.connection.SimpleConnectionManager;
00063 import org.objectweb.cjdbc.controller.connection.VariablePoolConnectionManager;
00064 import org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean;
00065 import org.objectweb.cjdbc.controller.jmx.MBeanServerManager;
00066 import org.objectweb.cjdbc.controller.jmx.RmiConnector;
00067 import org.objectweb.cjdbc.controller.loadbalancer.AbstractLoadBalancer;
00068 
00069 /**
00070  * A <code>DatabaseBackend</code> represents a real database backend that will
00071  * have to be bound to a virtual C-JDBC database. All connections opened will
00072  * use the same url but possibly different login/password.
00073  * 
00074  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00075  * @author <a href="mailto:Mathieu.Peltier@inrialpes.fr">Mathieu Peltier </a>
00076  * @author <a href="mailto:Sara.Bouchenak@epfl.ch">Sara Bouchenak </a>
00077  * @author <a href="mailto:Nicolas.Modrzyk@inrialpes.fr">Nicolas Modrzyk </a>
00078  * @version 1.0
00079  */
00080 public final class DatabaseBackend extends AbstractStandardMBean
00081     implements
00082       Serializable,
00083       XmlComponent,
00084       DatabaseBackendMBean
00085 {
00086   //
00087   // How the code is organized?
00088   // 1. Member variables
00089   // 2. Constructor(s)
00090   // 3. Lookup functions
00091   // 4. Connections management
00092   // 5. State management
00093   // 6. Getter/Setter (possibly in alphabetical order)
00094   // 7. Debug/Monitoring
00095   //
00096 
00097   /** Logical name assigned to this backend. */
00098   private String                         name;
00099 
00100   /** Path for driver */
00101   private String                         driverPath;
00102 
00103   /** Database native JDBC driver class name. */
00104   private String                         driverClassName;
00105 
00106   /** Driver compliance to C-JDBC requirements */
00107   private transient DriverCompliance     driverCompliance;
00108 
00109   /** Real URL to access the database (JDBC URL). */
00110   private String                         url;
00111 
00112   /** Name of the virtual database this backend is attached to */
00113   private String                         virtualDatabaseName;
00114 
00115   /** A boolean to know if we should allow this backend to be enabled for write */
00116   private boolean                        writeCanBeEnabled;
00117 
00118   /** SQL statement used to check if a connection is still valid */
00119   private String                         connectionTestStatement;
00120 
00121   /** The schema of the database. */
00122   private transient DatabaseSchema       schema;
00123 
00124   /** <code>true</code> if schema is static. */
00125   private boolean                        schemaIsStatic     = false;
00126 
00127   /** Connection managers for this backend. */
00128   private transient HashMap              connectionManagers;
00129 
00130   /** Logger instance. */
00131   protected transient Trace              logger;
00132 
00133   /** List of started transactions. */
00134   private transient ArrayList            activeTransactions = new ArrayList();
00135 
00136   /** List of pending requests. */
00137   private transient Vector               pendingRequests    = new Vector();
00138 
00139   /** Monitoring Values */
00140   private int                            totalRequest;
00141   private int                            totalWriteRequest;
00142   private int                            totalReadRequest;
00143   private int                            totalTransactions;
00144 
00145   /** List of <code>AbstractRewritingRule</code> objects. */
00146   private ArrayList                      rewritingRules;
00147 
00148   /** For metadata information generation */
00149   private int                            dynamicPrecision;
00150   private boolean                        gatherSystemTables = false;
00151   private String                         schemaName         = null;
00152 
00153   /** Short form of SQL statements to include in traces and exceptions */
00154   private int                            sqlShortFormLength = 40;
00155 
00156   private String                         lastKnownCheckpoint;
00157 
00158   /**
00159    * The current state of the backend
00160    * 
00161    * @see org.objectweb.cjdbc.common.shared.BackendState
00162    */
00163   private int                            state              = BackendState.DISABLED;
00164 
00165   private transient BackendStateListener stateListener;
00166 
00167   /**
00168    * Creates a new <code>DatabaseBackend</code> instance.
00169    * 
00170    * @param name logical name assigned to this backend
00171    * @param driverPath path for driver
00172    * @param driverClassName class name of the database native JDBC driver to
00173    *          load
00174    * @param url URL to access the database
00175    * @param vdbName Name of the virtual database this backend is attached to
00176    * @param writeCanBeEnabled if writes can be enabled on this backend
00177    * @param connectionTestStatement SQL statement used to check if a connection
00178    *          is still valid
00179    * @throws NotCompliantMBeanException if the mbean can not be created (unless
00180    *           we refactor the code this cannot happend)
00181    */
00182   public DatabaseBackend(String name, String driverPath,
00183       String driverClassName, String url, String vdbName,
00184       boolean writeCanBeEnabled, String connectionTestStatement)
00185       throws NotCompliantMBeanException
00186   {
00187     super(DatabaseBackendMBean.class);
00188     if (name == null)
00189       throw new IllegalArgumentException(Translate
00190           .get("backend.null.backend.name"));
00191 
00192     if (driverClassName == null)
00193       throw new IllegalArgumentException(Translate.get("backend.null.driver"));
00194 
00195     if (url == null)
00196       throw new IllegalArgumentException(Translate.get("backend.null.url"));
00197 
00198     if (vdbName == null)
00199       throw new IllegalArgumentException(Translate
00200           .get("backend.null.virtualdatabase.name"));
00201 
00202     if (connectionTestStatement == null)
00203       throw new IllegalArgumentException(Translate
00204           .get("backend.null.connection.test"));
00205 
00206     this.name = name;
00207     this.writeCanBeEnabled = writeCanBeEnabled;
00208     this.driverPath = driverPath;
00209     this.driverClassName = driverClassName;
00210     this.url = url;
00211     this.virtualDatabaseName = vdbName;
00212     this.connectionTestStatement = connectionTestStatement;
00213     this.connectionManagers = new HashMap();
00214     logger = Trace
00215         .getLogger("org.objectweb.cjdbc.controller.backend.DatabaseBackend."
00216             + name);
00217     this.driverCompliance = new DriverCompliance(logger);
00218     totalRequest = 0;
00219     dynamicPrecision = DatabaseBackendSchemaConstants.DynamicPrecisionAll;
00220   }
00221 
00222   /**
00223    * Creates a new <code>DatabaseBackend</code> object
00224    * 
00225    * @param info a backend info object to create a database backend object from
00226    * @throws NotCompliantMBeanException if mbean is not compliant (unless we
00227    *           refactor the code this cannot happend)
00228    */
00229   public DatabaseBackend(BackendInfo info) throws NotCompliantMBeanException
00230   {
00231     this(info.getName(), info.getDriverPath(), info.getDriverClassName(), info
00232         .getUrl(), info.getVirtualDatabaseName(), true, info
00233         .getConnectionTestStatement());
00234     try
00235     {
00236       String xml = info.getXml();
00237       StringReader sreader = new StringReader(xml);
00238       SAXReader reader = new SAXReader();
00239       Document document = reader.read(sreader);
00240       Element root = document.getRootElement();
00241       Iterator iter1 = root.elementIterator();
00242       while (iter1.hasNext())
00243       {
00244         Element elem = (Element) iter1.next();
00245         if (elem.getName().equals(DatabasesXmlTags.ELT_ConnectionManager))
00246         {
00247           String vuser = elem.valueOf("@" + DatabasesXmlTags.ATT_vLogin);
00248           String rlogin = elem.valueOf("@" + DatabasesXmlTags.ATT_rLogin);
00249           String rpassword = elem.valueOf("@" + DatabasesXmlTags.ATT_rPassword);
00250           Iterator iter2 = elem.elementIterator();
00251           while (iter2.hasNext())
00252           {
00253             Element connectionManager = (Element) iter2.next();
00254             String cname = connectionManager.getName();
00255             if (cname
00256                 .equals(DatabasesXmlTags.ELT_VariablePoolConnectionManager))
00257             {
00258               int minPoolSize = Integer.parseInt(connectionManager.valueOf("@"
00259                   + DatabasesXmlTags.ATT_minPoolSize));
00260               int maxPoolSize = Integer.parseInt(connectionManager.valueOf("@"
00261                   + DatabasesXmlTags.ATT_maxPoolSize));
00262               int idleTimeout = Integer.parseInt(connectionManager.valueOf("@"
00263                   + DatabasesXmlTags.ATT_idleTimeout));
00264               int waitTimeout = Integer.parseInt(connectionManager.valueOf("@"
00265                   + DatabasesXmlTags.ATT_waitTimeout));
00266               this.addConnectionManager(vuser,
00267                   new VariablePoolConnectionManager(url, name, rlogin,
00268                       rpassword, driverPath, driverClassName, minPoolSize,
00269                       maxPoolSize, idleTimeout, waitTimeout));
00270             }
00271             else if (cname.equals(DatabasesXmlTags.ELT_SimpleConnectionManager))
00272             {
00273               this.addConnectionManager(vuser, new SimpleConnectionManager(url,
00274                   name, rlogin, rpassword, driverPath, driverClassName));
00275             }
00276             else if (cname
00277                 .equals(DatabasesXmlTags.ELT_RandomWaitPoolConnectionManager))
00278             {
00279               int poolSize = Integer.parseInt(connectionManager.valueOf("@"
00280                   + DatabasesXmlTags.ATT_poolSize));
00281               int timeout = Integer.parseInt(connectionManager.valueOf("@"
00282                   + DatabasesXmlTags.ATT_timeout));
00283               this
00284                   .addConnectionManager(vuser,
00285                       new RandomWaitPoolConnectionManager(url, name, rlogin,
00286                           rpassword, driverPath, driverClassName, poolSize,
00287                           timeout));
00288             }
00289             else if (cname
00290                 .equals(DatabasesXmlTags.ELT_FailFastPoolConnectionManager))
00291             {
00292               int poolSize = Integer.parseInt(connectionManager.valueOf("@"
00293                   + DatabasesXmlTags.ATT_poolSize));
00294               this.addConnectionManager(vuser,
00295                   new FailFastPoolConnectionManager(url, name, rlogin,
00296                       rpassword, driverPath, driverClassName, poolSize));
00297             }
00298           }
00299         }
00300       }
00301 
00302     }
00303     catch (Exception e)
00304     {
00305       logger
00306           .error(Translate.get("backend.add.connection.manager.failed", e), e);
00307     }
00308   }
00309 
00310   /**
00311    * Additionnal constructor for setting a different dynamic schema level.
00312    * Default was to gather all information Creates a new
00313    * <code>DatabaseBackend</code> instance.
00314    * 
00315    * @param name logical name assigned to this backend
00316    * @param driverPath path for driver
00317    * @param driverClassName class name of the database native JDBC driver to
00318    *          load
00319    * @param url URL to access the database
00320    * @param vdbName Name of the virtual database this backend is attached to
00321    * @param connectionTestStatement SQL statement used to check if a connection
00322    *          is still valid
00323    * @param dynamicSchemaLevel for dynamically gathering schema from backend
00324    * @throws NotCompliantMBeanException (unless we refactor the code this cannot
00325    *           happend)
00326    */
00327   public DatabaseBackend(String name, String driverPath,
00328       String driverClassName, String url, String vdbName,
00329       String connectionTestStatement, String dynamicSchemaLevel)
00330       throws NotCompliantMBeanException
00331   {
00332     this(name, driverPath, driverClassName, url, vdbName, true,
00333         connectionTestStatement);
00334     this.dynamicPrecision = DatabaseBackendSchemaConstants
00335         .getDynamicSchemaLevel(dynamicSchemaLevel);
00336   }
00337 
00338   /**
00339    * Sets the sqlShortFormLength value.
00340    * 
00341    * @param sqlShortFormLength The sqlShortFormLength to set.
00342    */
00343   public void setSqlShortFormLength(int sqlShortFormLength)
00344   {
00345     this.sqlShortFormLength = sqlShortFormLength;
00346   }
00347 
00348   /**
00349    * Return the sql short form length to use when reporting an error.
00350    * 
00351    * @return sql short form length
00352    * @see org.objectweb.cjdbc.common.sql.AbstractRequest#getSQLShortForm(int)
00353    */
00354   public int getSQLShortFormLength()
00355   {
00356     return sqlShortFormLength;
00357   }
00358 
00359   /* Lookup functions */
00360 
00361   /**
00362    * Two database backends are considered equal if they have the same name, URL
00363    * and driver class name.
00364    * 
00365    * @param other an object
00366    * @return a <code>boolean</code> value
00367    */
00368   public boolean equals(Object other)
00369   {
00370     if ((other == null) || (!(other instanceof DatabaseBackend)))
00371       return false;
00372     else
00373     {
00374       DatabaseBackend b = (DatabaseBackend) other;
00375       return name.equals(b.getName())
00376           && driverClassName.equals(b.getDriverClassName())
00377           && url.equals(b.getURL());
00378     }
00379   }
00380 
00381   /**
00382    * Returns <code>true</code> if this backend has the given list of tables in
00383    * its schema. The caller must ensure that the database schema has been
00384    * defined, using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
00385    * {@link #checkDatabaseSchema()}methods.
00386    * 
00387    * @param tables the list of table names (<code>ArrayList</code> of
00388    *          <code>String</code>) to look for
00389    * @return <code>true</code> if all the tables are found
00390    */
00391   public boolean hasTables(ArrayList tables)
00392   {
00393     if (schema == null)
00394       throw new NullPointerException(Translate.get("backend.schema.not.set"));
00395 
00396     if (tables == null)
00397       throw new IllegalArgumentException(Translate.get("backend.null.tables"));
00398 
00399     int size = tables.size();
00400     for (int i = 0; i < size; i++)
00401     {
00402       if (!schema.hasTable((String) tables.get(i)))
00403         return false;
00404     }
00405     return true;
00406   }
00407 
00408   /**
00409    * Returns <code>true</code> if this backend has the given table in its
00410    * schema. The caller must ensure that the database schema has been defined,
00411    * using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
00412    * {@link #checkDatabaseSchema()}
00413    * 
00414    * @param table The table name to look for
00415    * @return <code>true</code> if tables is found in the schema
00416    */
00417   public boolean hasTable(String table)
00418   {
00419     if (schema == null)
00420       throw new NullPointerException(Translate.get("backend.schema.not.set"));
00421 
00422     return schema.hasTable(table);
00423   }
00424 
00425   /**
00426    * Get all the names of tables of this database
00427    * 
00428    * @return <code>ArrayList</code> of <code>DatabaseTable</code>
00429    */
00430   public ArrayList getTables()
00431   {
00432     if (schema == null)
00433       throw new NullPointerException(Translate.get("backend.schema.not.set"));
00434     return schema.getTables();
00435   }
00436 
00437   /**
00438    * Returns <code>true</code> if this backend has the given stored procedure
00439    * in its schema. The caller must ensure that the database schema has been
00440    * defined, using the {@link #setDatabaseSchema(DatabaseSchema, boolean)}or
00441    * {@link #checkDatabaseSchema()}
00442    * 
00443    * @param procedureName The stored procedure name to look for
00444    * @return <code>true</code> if procedure name is found in the schema
00445    */
00446   public boolean hasStoredProcedure(String procedureName)
00447   {
00448     if (schema == null)
00449       throw new NullPointerException(Translate.get("backend.schema.not.set"));
00450 
00451     return schema.hasProcedure(procedureName);
00452   }
00453 
00454   /* Connection management */
00455 
00456   /**
00457    * Initializes the connection managers' connections. The caller must ensure
00458    * that the driver has already been loaded else an exception will be thrown.
00459    * 
00460    * @exception SQLException if an error occurs
00461    */
00462   public synchronized void initializeConnections() throws SQLException
00463   {
00464     if (connectionManagers.isEmpty())
00465       throw new SQLException(Translate.get("backend.not.defined", new String[]{
00466           name, url}));
00467 
00468     AbstractConnectionManager connectionManager;
00469     Iterator iter = connectionManagers.values().iterator();
00470     while (iter.hasNext())
00471     {
00472       connectionManager = (AbstractConnectionManager) iter.next();
00473       if (!connectionManager.isInitialized())
00474         connectionManager.initializeConnections();
00475     }
00476   }
00477 
00478   /**
00479    * Releases all the connections to the database held by the connection
00480    * managers.
00481    * 
00482    * @throws SQLException if an error occurs
00483    */
00484   public synchronized void finalizeConnections() throws SQLException
00485   {
00486     if (connectionManagers.isEmpty())
00487       throw new SQLException(Translate.get("backend.not.defined", new String[]{
00488           name, url}));
00489 
00490     AbstractConnectionManager connectionManager;
00491     Iterator iter = connectionManagers.values().iterator();
00492     while (iter.hasNext())
00493     {
00494       connectionManager = (AbstractConnectionManager) iter.next();
00495       if (connectionManager.isInitialized())
00496         connectionManager.finalizeConnections();
00497     }
00498   }
00499 
00500   /**
00501    * Check if the given connection is valid or not. This function issues the
00502    * connectionTestStatement query on the connection and if it succeeds then the
00503    * connection is declared valid. If an exception occurs, the connection is
00504    * declared invalid.
00505    * 
00506    * @param connection the connection to test
00507    * @return true if the connection is valid
00508    */
00509   public boolean isValidConnection(Connection connection)
00510   {
00511     try
00512     {
00513       Statement s = connection.createStatement();
00514       s.executeQuery(connectionTestStatement);
00515     }
00516     catch (SQLException e)
00517     {
00518       if ("25P02".equals(e.getSQLState())
00519           || (e.getMessage() != null && e
00520               .getMessage()
00521               .indexOf(
00522                   "current transaction is aborted, queries ignored until end of transaction block") > 0))
00523       {
00524         // see bug item #300873 on the forge for details
00525         // postgres throws an exception if a query is issued after a request has
00526         // failed within a transaction, we now have to check for this exception
00527         // as it is means the connection is valid
00528         //
00529         // postgres versions after 7.4 will return the SQLState, whereas
00530         // postgres versions prior to 7.4 will have to be checked for the
00531         // message text
00532         return true;
00533       }
00534       return false;
00535     }
00536     return true;
00537   }
00538 
00539   /**
00540    * Adds a <code>ConnectionManager</code> to this backend. Note that the
00541    * <code>ConnectionManager</code> is not initialized in this method.
00542    * 
00543    * @param vLogin the virtual login corresponding to this connection manager
00544    * @param connectionManager the <code>ConnectionManager</code> to add
00545    */
00546   public void addConnectionManager(String vLogin,
00547       AbstractConnectionManager connectionManager)
00548   {
00549     if (connectionManager == null)
00550       throw new IllegalArgumentException(Translate.get(
00551           "backend.null.connection.manager", new String[]{name, url}));
00552     if (logger.isInfoEnabled())
00553       logger.info(Translate.get("backend.add.connection.manager.for.user",
00554           vLogin));
00555     connectionManager.setVLogin(vLogin);
00556     connectionManagers.put(vLogin, connectionManager);
00557   }
00558 
00559   /**
00560    * Retrieve a connection for a given transaction or create a new connection
00561    * and start a new transaction. <br>
00562    * This method is synchronized so that concurrent writes within the same
00563    * transaction that are allowed to execute out of order will not open separate
00564    * connection if they race on transaction begin.
00565    * 
00566    * @param tid transaction identifier
00567    * @param cm connection manager to get the connection from
00568    * @return the connection for the given transaction id
00569    * @throws UnreachableBackendException if the backend is no more reachable
00570    * @throws NoTransactionStartWhenDisablingException if a new transaction
00571    *           needed to be started but the backend is in the disabling state
00572    * @throws SQLException if another error occurs
00573    */
00574   public synchronized Connection getConnectionForTransactionAndLazyBeginIfNeeded(
00575       Long tid, AbstractConnectionManager cm)
00576       throws UnreachableBackendException,
00577       NoTransactionStartWhenDisablingException, SQLException
00578   {
00579     if (isStartedTransaction(tid))
00580     { // Transaction has already been started, retrieve connection
00581       return cm.retrieveConnection(tid.longValue());
00582     }
00583     else
00584     {
00585       if (isDisabling())
00586         throw new NoTransactionStartWhenDisablingException();
00587 
00588       // begin transaction
00589       startTransaction(tid);
00590 
00591       // Transaction has not been started yet, this is a lazy begin
00592       return AbstractLoadBalancer.getConnectionAndBeginTransaction(this, cm,
00593           tid.longValue());
00594     }
00595   }
00596 
00597   /* State management */
00598 
00599   /**
00600    * Signals that a transaction has been started on this backend. It means that
00601    * a connection has been allocated for this transaction.
00602    * 
00603    * @param tid transaction identifier
00604    */
00605   public void startTransaction(Long tid)
00606   {
00607     synchronized (activeTransactions)
00608     {
00609       totalTransactions++;
00610       activeTransactions.add(tid);
00611     }
00612   }
00613 
00614   /**
00615    * Signals that a transaction has been stopped on this backend. It means that
00616    * the connection has been released for this transaction.
00617    * 
00618    * @param tid transaction identifier
00619    */
00620   public void stopTransaction(Long tid)
00621   {
00622     synchronized (activeTransactions)
00623     {
00624       if (!activeTransactions.remove(tid))
00625         throw new IllegalArgumentException(Translate.get(
00626             "backend.transaction.not.started", new String[]{"" + tid, name}));
00627       // If this was the last open transaction, we notify people possibly
00628       // waiting on waitForAllTransactionsToComplete()
00629       if (activeTransactions.isEmpty())
00630       {
00631         activeTransactions.notifyAll();
00632       }
00633     }
00634   }
00635 
00636   /**
00637    * This method waits until all currently open transactions on this backend
00638    * complete. If no transaction are currently running on this backend, this
00639    * method immediately returns.
00640    */
00641   public void waitForAllTransactionsToComplete()
00642   {
00643     synchronized (activeTransactions)
00644     {
00645       if (activeTransactions.isEmpty())
00646         return;
00647       else
00648         try
00649         {
00650           activeTransactions.wait();
00651         }
00652         catch (InterruptedException ignore)
00653         {
00654         }
00655     }
00656   }
00657 
00658   /**
00659    * Returns <code>true</code> if the specified transaction has been started
00660    * on this backend (a connection has been allocated for this transaction).
00661    * 
00662    * @param tid transaction identifier
00663    * @return <code>true</code> if the transaction has been started
00664    */
00665   public boolean isStartedTransaction(Long tid)
00666   {
00667     synchronized (activeTransactions)
00668     {
00669       return activeTransactions.contains(tid);
00670     }
00671   }
00672 
00673   /**
00674    * Tests if this backend is enabled (active and synchronized).
00675    * 
00676    * @return <code>true</code> if this backend is enabled
00677    * @throws SQLException if an error occurs
00678    */
00679   public synchronized boolean isInitialized() throws SQLException
00680   {
00681     if (connectionManagers.isEmpty())
00682       throw new SQLException(Translate.get("backend.null.connection.manager",
00683           new String[]{name, url}));
00684     Iterator iter = connectionManagers.values().iterator();
00685     while (iter.hasNext())
00686     {
00687       if (!((AbstractConnectionManager) iter.next()).isInitialized())
00688         return false;
00689     }
00690     return true;
00691   }
00692 
00693   /**
00694    * Is the backend accessible ?
00695    * 
00696    * @return <tt>true</tt> if a jdbc connection is still possible from the
00697    *         controller
00698    */
00699   public synchronized boolean isJDBCConnected()
00700   {
00701     try
00702     {
00703       if (connectionManagers.isEmpty())
00704         throw new SQLException(Translate.get("backend.null.connection.manager",
00705             new String[]{name, url}));
00706 
00707       AbstractConnectionManager connectionManager;
00708       Iterator iter = connectionManagers.values().iterator();
00709       connectionManager = (AbstractConnectionManager) iter.next();
00710 
00711       Connection con = connectionManager.getConnectionFromDriver();
00712       con.createStatement().execute(this.connectionTestStatement);
00713       return true;
00714     }
00715     catch (Exception e)
00716     {
00717       String msg = Translate.get("loadbalancer.backend.unreacheable", name);
00718       logger.warn(msg, e);
00719       return false;
00720     }
00721   }
00722 
00723   /**
00724    * Tests if this backend is read enabled (active and synchronized).
00725    * 
00726    * @return <code>true</code> if this backend is enabled.
00727    */
00728   public synchronized boolean isReadEnabled()
00729   {
00730     return state == BackendState.READ_ENABLED_WRITE_DISABLED
00731         || state == BackendState.READ_ENABLED_WRITE_ENABLED;
00732   }
00733 
00734   /**
00735    * Tests if this backend is write enabled (active and synchronized).
00736    * 
00737    * @return <code>true</code> if this backend is enabled.
00738    */
00739   public synchronized boolean isWriteEnabled()
00740   {
00741     return state == BackendState.READ_ENABLED_WRITE_ENABLED
00742         || state == BackendState.READ_DISABLED_WRITE_ENABLED
00743         || state == BackendState.DISABLING;
00744   }
00745 
00746   /**
00747    * Returns the isRecovering value.
00748    * 
00749    * @return Returns the isRecovering.
00750    */
00751   public boolean isRecovering()
00752   {
00753     return state == BackendState.RECOVERING;
00754   }
00755 
00756   /**
00757    * Returns the isDisabling value.
00758    * 
00759    * @return Returns the isDisabling.
00760    */
00761   public boolean isDisabling()
00762   {
00763     return state == BackendState.DISABLING;
00764   }
00765 
00766   /**
00767    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#isDisabled()
00768    */
00769   public boolean isDisabled()
00770   {
00771     return state == BackendState.DISABLED;
00772   }
00773 
00774   /**
00775    * Returns true if the backend cannot be used anymore
00776    * 
00777    * @return Returns true if the backend was removed from activity by the load
00778    *         balancer
00779    */
00780   public boolean isKilled()
00781   {
00782     return state == BackendState.UNKNOWN;
00783   }
00784 
00785   /**
00786    * Retrieve the state of the backend.
00787    * 
00788    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLED
00789    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_RECOVERING
00790    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_BACKINGUP
00791    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLING
00792    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_ENABLED
00793    * @see CjdbcNotificationList#VIRTUALDATABASE_BACKEND_DISABLED
00794    * @return one of the above
00795    */
00796   public String getState()
00797   {
00798     switch (state)
00799     {
00800       case BackendState.READ_ENABLED_WRITE_DISABLED :
00801         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED;
00802       case BackendState.READ_ENABLED_WRITE_ENABLED :
00803         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED_WRITE;
00804       case BackendState.READ_DISABLED_WRITE_ENABLED :
00805         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_ENABLED_WRITE;
00806       case BackendState.DISABLING :
00807         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_DISABLING;
00808       case BackendState.BACKUPING :
00809         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_BACKINGUP;
00810       case BackendState.RECOVERING :
00811         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_RECOVERING;
00812       case BackendState.REPLAYING :
00813         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_REPLAYING;
00814       case BackendState.DISABLED :
00815         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_DISABLED;
00816       case BackendState.UNKNOWN :
00817         return CjdbcNotificationList.VIRTUALDATABASE_BACKEND_UNKNOWN;
00818       default :
00819         throw new IllegalArgumentException("Unknown backend state:" + state);
00820     }
00821   }
00822 
00823   /**
00824    * Return the integer value corresponding to the state of the backend. The
00825    * values are defined in <code>BackendState</code>
00826    * 
00827    * @return <tt>int</tt> value
00828    * @see BackendState
00829    */
00830   public int getStateValue()
00831   {
00832     return state;
00833   }
00834 
00835   /**
00836    * Enables the database backend for reads. This method should only be called
00837    * when the backend is synchronized with the others.
00838    */
00839   public synchronized void enableRead()
00840   {
00841     if (isWriteEnabled())
00842       setState(BackendState.READ_ENABLED_WRITE_ENABLED);
00843     else
00844       setState(BackendState.READ_ENABLED_WRITE_DISABLED);
00845   }
00846 
00847   /**
00848    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#disableRead()
00849    */
00850   public synchronized void disableRead()
00851   {
00852     if (isWriteEnabled())
00853       setState(BackendState.READ_DISABLED_WRITE_ENABLED);
00854     else
00855       setState(BackendState.DISABLED);
00856   }
00857 
00858   /**
00859    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#disableWrite()
00860    */
00861   public synchronized void disableWrite()
00862   {
00863     if (isReadEnabled())
00864       setState(BackendState.READ_ENABLED_WRITE_DISABLED);
00865     else
00866       setState(BackendState.DISABLED);
00867   }
00868 
00869   /**
00870    * Enables the database backend for writes. This method should only be called
00871    * when the backend is synchronized with the others.
00872    */
00873   public synchronized void enableWrite()
00874   {
00875     if (isReadEnabled())
00876       setState(BackendState.READ_ENABLED_WRITE_ENABLED);
00877     else
00878       setState(BackendState.READ_DISABLED_WRITE_ENABLED);
00879   }
00880 
00881   /**
00882    * This is used when the backend must be disabled but currently open
00883    * transactions must terminate. This is a transitional state. When disabling
00884    * is complete the caller must set the backend state to disabled.
00885    * <p>
00886    * Reads are no more allowed on the backend and the state is updated so that
00887    * isReadEnabled() returns false.
00888    * 
00889    * @see #disable()
00890    * @see #isReadEnabled()
00891    * @deprecated not used anymore. Please use the setState method instead
00892    */
00893   public void setDisabling()
00894   {
00895     setState(BackendState.DISABLING);
00896   }
00897 
00898   /**
00899    * Sets the database backend state to disable. This state is just an
00900    * indication and it has no semantic effect. It is up to the request manager
00901    * (especially the load balancer) to ensure that no more requests are sent to
00902    * this backend.
00903    */
00904   public synchronized void disable()
00905   {
00906     setState(BackendState.DISABLED);
00907   }
00908 
00909   /* Getter/setter methods */
00910 
00911   /**
00912    * Returns the <code>ConnectionManager</code> associated to this backend for
00913    * a given virtual login.
00914    * 
00915    * @param vLogin the virtual login
00916    * @return an <code>AbstractConnectionManager</code> instance
00917    */
00918   public AbstractConnectionManager getConnectionManager(String vLogin)
00919   {
00920     return (AbstractConnectionManager) connectionManagers.get(vLogin);
00921   }
00922 
00923   /**
00924    * Returns a <code>HashMap</code> of all the <code>ConnectionManager</code>
00925    * associated with this <code>DatabaseBackend</code>
00926    * 
00927    * @return the hashmap of connection managers
00928    */
00929   public HashMap getConnectionManagers()
00930   {
00931     return this.connectionManagers;
00932   }
00933 
00934   /**
00935    * Returns the SQL statement to use to check the connection validity.
00936    * 
00937    * @return a <code>String</code> containing a SQL statement
00938    */
00939   public String getConnectionTestStatement()
00940   {
00941     return connectionTestStatement;
00942   }
00943 
00944   /**
00945    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getDriverPath()
00946    */
00947   public String getDriverPath()
00948   {
00949     return driverPath;
00950   }
00951 
00952   /**
00953    * @see org.objectweb.cjdbc.controller.jmx.AbstractStandardMBean#getAssociatedString()
00954    */
00955   public String getAssociatedString()
00956   {
00957     return "backend";
00958   }
00959 
00960   /**
00961    * Returns the database native JDBC driver class name.
00962    * 
00963    * @return the driver class name
00964    */
00965   public String getDriverClassName()
00966   {
00967     return driverClassName;
00968   }
00969 
00970   /**
00971    * Returns the backend logical name.
00972    * 
00973    * @return the backend logical name
00974    */
00975   public String getName()
00976   {
00977     return name;
00978   }
00979 
00980   /**
00981    * Returns the virtual database name this backend belongs to.
00982    * 
00983    * @return Returns the virtual database name.
00984    */
00985   public String getVirtualDatabaseName()
00986   {
00987     return virtualDatabaseName;
00988   }
00989 
00990   /**
00991    * Returns the list of pending requests for this backend.
00992    * 
00993    * @return <code>Vector</code> of <code>AbstractRequests</code> or
00994    *         <code>AbstractTask</code> objects
00995    */
00996   public Vector getPendingRequests()
00997   {
00998     return pendingRequests;
00999   }
01000 
01001   /**
01002    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getPendingRequestsDescription(int,
01003    *      boolean, boolean)
01004    */
01005   public ArrayList getPendingRequestsDescription(int count, boolean fromFirst,
01006       boolean clone)
01007   {
01008     int size = pendingRequests.size();
01009     int limit = (count == 0 || count > size) ? size : Math.min(size, count);
01010     ArrayList list = new ArrayList(limit);
01011     int start = (fromFirst) ? 0 : Math.min(limit - count, 0);
01012     if (!clone)
01013     {
01014       synchronized (pendingRequests)
01015       {
01016         for (int i = start; i < limit; i++)
01017           list.add(pendingRequests.get(i).toString());
01018       }
01019       return list;
01020     }
01021     else
01022     {
01023       Vector cloneVector = (Vector) pendingRequests.clone();
01024       for (int i = start; i < limit; i++)
01025         list.add(cloneVector.get(i).toString());
01026       return list;
01027     }
01028   }
01029 
01030   /**
01031    * Adds a pending request (or task) to this backend. Note that the underlying
01032    * vector is synchronized.
01033    * 
01034    * @param request the request to add
01035    */
01036   public void addPendingReadRequest(Object request)
01037   {
01038     synchronized (this)
01039     {
01040       totalRequest++;
01041       totalReadRequest++;
01042     }
01043     pendingRequests.add(request);
01044   }
01045 
01046   /**
01047    * Adds a pending request (or task) to this backend. Note that the underlying
01048    * vector is synchronized.
01049    * 
01050    * @param request the request to add
01051    */
01052   public void addPendingWriteRequest(Object request)
01053   {
01054     synchronized (this)
01055     {
01056       totalRequest++;
01057       totalWriteRequest++;
01058     }
01059     pendingRequests.add(request);
01060   }
01061 
01062   /**
01063    * Removes a pending request from this backend. Note that the underlying
01064    * vector is synchronized.
01065    * 
01066    * @param request the request to remove
01067    * @return <code>true</code> if the request has been found and removed
01068    */
01069   public boolean removePendingRequest(Object request)
01070   {
01071     return pendingRequests.remove(request);
01072   }
01073 
01074   /**
01075    * Returns the schema of this database.
01076    * 
01077    * @return the schema of this database. Returns <code>null</code> if the
01078    *         schema has not been set.
01079    * @see #setDatabaseSchema
01080    */
01081   public DatabaseSchema getDatabaseSchema()
01082   {
01083     return schema;
01084   }
01085 
01086   /**
01087    * Sets the database schema.
01088    * 
01089    * @param databaseSchema the schema to set
01090    * @param isStatic <code>true</code> if the schema should be static
01091    * @see #getDatabaseSchema
01092    */
01093   public void setDatabaseSchema(DatabaseSchema databaseSchema, boolean isStatic)
01094   {
01095     if (schema == null)
01096     {
01097       schemaIsStatic = isStatic;
01098       schema = databaseSchema;
01099     }
01100     else
01101     {
01102       if (!isStatic)
01103         schema = databaseSchema;
01104     }
01105   }
01106 
01107   /**
01108    * Erase the current schema and force a re-fetch of all the meta data
01109    */
01110   public void refreshSchema()
01111   {
01112     setDatabaseSchema(null, isSchemaStatic());
01113     checkDatabaseSchema();
01114   }
01115 
01116   /**
01117    * Get the Database static metadata from this backend using a connection from
01118    * the first available connection manager.
01119    * 
01120    * @return Static metadata information
01121    */
01122   public MetadataContainer getDatabaseStaticMetadata()
01123   {
01124     AbstractConnectionManager connectionMananger;
01125     Iterator iter = connectionManagers.values().iterator();
01126     if (iter.hasNext())
01127     {
01128       connectionMananger = (AbstractConnectionManager) iter.next();
01129       // Gather the static metadata from the first connection manager
01130       DatabaseBackendMetaData meta = new DatabaseBackendMetaData(
01131           connectionMananger, logger, dynamicPrecision, gatherSystemTables,
01132           schemaName);
01133       try
01134       {
01135         return meta.retrieveDatabaseMetadata();
01136       }
01137       catch (SQLException e)
01138       {
01139         return null;
01140       }
01141     }
01142     else
01143       return null;
01144   }
01145 
01146   /**
01147    * @see DatabaseBackendMBean#checkDatabaseSchema()
01148    */
01149   public boolean checkDatabaseSchema()
01150   {
01151     if (logger.isDebugEnabled())
01152       logger.debug(Translate.get("backend.dynamic.schema",
01153           DatabaseBackendSchemaConstants
01154               .getDynamicSchemaLevel(dynamicPrecision)));
01155     boolean checked = true;
01156     AbstractConnectionManager connectionMananger;
01157     Iterator iter = connectionManagers.values().iterator();
01158     while (iter.hasNext())
01159     {
01160       connectionMananger = (AbstractConnectionManager) iter.next();
01161 
01162       // Gather the database schema from this connection manager
01163       DatabaseBackendMetaData meta = new DatabaseBackendMetaData(
01164           connectionMananger, logger, dynamicPrecision, gatherSystemTables,
01165           schemaName);
01166 
01167       DatabaseSchema metaSchema;
01168       try
01169       {
01170         if (logger.isInfoEnabled())
01171           logger.info(Translate.get("backend.gathering.database.schema"));
01172         metaSchema = meta.getDatabaseSchema();
01173       }
01174       catch (SQLException e)
01175       {
01176         if (logger.isWarnEnabled())
01177           logger.warn(Translate.get("backend.gather.schema.failed", e));
01178         return false;
01179       }
01180       if (schema == null)
01181       {
01182         if (logger.isDebugEnabled())
01183           logger.debug(Translate.get("backend.use.gathered.schema.as.new"));
01184         schema = metaSchema;
01185       }
01186       else
01187       {
01188         if (dynamicPrecision == DatabaseBackendSchemaConstants.DynamicPrecisionStatic)
01189         {
01190           if (logger.isInfoEnabled())
01191             logger.info(Translate.get("backend.schema.static.no.check", name));
01192         }
01193         else
01194         {
01195           if (logger.isInfoEnabled())
01196             logger.info(Translate.get("backend.check.schema.compatibility"));
01197           if (schema.isCompatibleSubset(metaSchema))
01198             logger.info(Translate.get("backend.schema.compatible.for.login",
01199                 connectionMananger.getLogin()));
01200           else
01201           {
01202             checked = false;
01203             logger.warn(Translate.get(
01204                 "backend.schema.not.compatible.for.login", connectionMananger
01205                     .getLogin()));
01206           }
01207         }
01208       }
01209     }
01210     return checked;
01211   }
01212 
01213   /**
01214    * @return the driver compliance to C-JDBC requirements.
01215    */
01216   public DriverCompliance getDriverCompliance()
01217   {
01218     return driverCompliance;
01219   }
01220 
01221   /**
01222    * Check if the driver used by this backend is compliant with C-JDBC needs.
01223    * 
01224    * @throws SQLException if the driver is not compliant
01225    */
01226   public void checkDriverCompliance() throws SQLException
01227   {
01228     if (connectionManagers.isEmpty())
01229       throw new SQLException(Translate.get("backend.null.connection.manager",
01230           new String[]{name, url}));
01231 
01232     AbstractConnectionManager connectionManager;
01233     Iterator iter = connectionManagers.values().iterator();
01234     connectionManager = (AbstractConnectionManager) iter.next();
01235 
01236     try
01237     {
01238       if (!driverCompliance.complianceTest(url, connectionManager.getLogin(),
01239           connectionManager.getPassword(), connectionManager.getDriverPath(),
01240           connectionManager.getDriverClassName(), connectionTestStatement))
01241         throw new SQLException(Translate.get("backend.driver.not.compliant",
01242             driverClassName));
01243     }
01244     catch (ConnectException e)
01245     {
01246       throw new SQLException(Translate.get("backend.cannot.connect.to", e));
01247     }
01248   }
01249 
01250   /**
01251    * Returns the JDBC URL used to access the database.
01252    * 
01253    * @return a JDBC URL
01254    */
01255   public String getURL()
01256   {
01257     return url;
01258   }
01259 
01260   /*
01261    * Rewriting Rule management
01262    */
01263 
01264   /**
01265    * Add a <code>AbstractRewritingRule</code> at the end of the rule list.
01266    * 
01267    * @param rule a AbstractRewritingRule
01268    */
01269   public void addRewritingRule(AbstractRewritingRule rule)
01270   {
01271     if (rewritingRules == null)
01272       rewritingRules = new ArrayList();
01273     if (logger.isDebugEnabled())
01274       logger.debug(Translate.get("backend.rewriting.rule.add", new String[]{
01275           rule.getQueryPattern(), rule.getRewrite()}));
01276     rewritingRules.add(rule);
01277   }
01278 
01279   /**
01280    * Rewrite the current query according to the rewriting rules.
01281    * 
01282    * @param sqlQuery request to rewrite
01283    * @return the rewritten SQL query according to rewriting rules.
01284    */
01285   public String rewriteQuery(String sqlQuery)
01286   {
01287     if (rewritingRules == null)
01288       return sqlQuery;
01289     int size = rewritingRules.size();
01290     for (int i = 0; i < size; i++)
01291     {
01292       AbstractRewritingRule rule = (AbstractRewritingRule) rewritingRules
01293           .get(i);
01294       sqlQuery = rule.rewrite(sqlQuery);
01295       if (rule.hasMatched())
01296       { // Rule matched, query rewriten
01297         if (logger.isDebugEnabled())
01298           logger.debug(Translate.get("backend.rewriting.query", sqlQuery));
01299         if (rule.isStopOnMatch())
01300           break; // Ok, stop here.
01301       }
01302     }
01303     return sqlQuery;
01304   }
01305 
01306   /*
01307    * Debug/Monitoring
01308    */
01309 
01310   /**
01311    * Get xml information about this backend.
01312    * 
01313    * @return xml formatted information on this database backend.
01314    */
01315   public String getXml()
01316   {
01317     StringBuffer info = new StringBuffer();
01318     info.append("<" + DatabasesXmlTags.ELT_DatabaseBackend + " "
01319         + DatabasesXmlTags.ATT_name + "=\"" + name + "\" "
01320         + DatabasesXmlTags.ATT_driver + "=\"" + driverClassName + "\" "
01321         + DatabasesXmlTags.ATT_url + "=\"" + url + "\" "
01322         + DatabasesXmlTags.ATT_connectionTestStatement + "=\""
01323         + connectionTestStatement + "\">");
01324 
01325     boolean expandSchema = this.schema != null
01326         && dynamicPrecision == DatabaseBackendSchemaConstants.DynamicPrecisionStatic;
01327 
01328     info.append(getSchemaXml(expandSchema));
01329 
01330     if (rewritingRules != null)
01331     {
01332       int size = rewritingRules.size();
01333       for (int i = 0; i < size; i++)
01334         info.append(((AbstractRewritingRule) rewritingRules.get(i)).getXml());
01335     }
01336     if (connectionManagers != null)
01337     {
01338       if (connectionManagers.isEmpty() == false)
01339       {
01340         AbstractConnectionManager connectionManager;
01341         Iterator iter = connectionManagers.values().iterator();
01342         while (iter.hasNext())
01343         {
01344           connectionManager = (AbstractConnectionManager) iter.next();
01345           info.append(connectionManager.getXml());
01346         }
01347       }
01348     }
01349     info.append("</" + DatabasesXmlTags.ELT_DatabaseBackend + ">");
01350     return info.toString();
01351   }
01352 
01353   /**
01354    * @see org.objectweb.cjdbc.common.jmx.mbeans.DatabaseBackendMBean#getSchemaXml(boolean)
01355    */
01356   public String getSchemaXml(boolean expandSchema)
01357   {
01358     StringBuffer info = new StringBuffer();
01359     info.append("<"
01360         + DatabasesXmlTags.ELT_DatabaseSchema
01361         + " "
01362         + DatabasesXmlTags.ATT_dynamicPrecision
01363         + "=\""
01364         + DatabaseBackendSchemaConstants
01365             .getDynamicSchemaLevel(dynamicPrecision) + "\" "
01366         + DatabasesXmlTags.ATT_gatherSystemTables + "=\""
01367         + (gatherSystemTables ? "true" : "false") + "\">");
01368     if (expandSchema)
01369       info.append(schema.getXml());
01370     info.append("</" + DatabasesXmlTags.ELT_DatabaseSchema + ">");
01371     return info.toString();
01372   }
01373 
01374   /**
01375    * @return Returns the activeTransactions.
01376    */
01377   public ArrayList getActiveTransactions()
01378   {
01379     return activeTransactions;
01380   }
01381 
01382   /**
01383    * @return Returns the schemaIsStatic.
01384    */
01385   public boolean isSchemaStatic()
01386   {
01387     return schemaIsStatic;
01388   }
01389 
01390   /**
01391    * Get data about this backend. Format is:
01392    * 
01393    * <pre>
01394    * data[0] = this.name;
01395    * data[1] = this.driverClassName;
01396    * data[2] = this.url;
01397    * data[3] = String.valueOf(this.activeTransactions.size());
01398    * data[4] = String.valueOf(this.pendingRequests.size());
01399    * data[5] = String.valueOf(this.isReadEnabled());
01400    * data[6] = String.valueOf(this.isWriteEnabled());
01401    * data[7] = String.valueOf(this.isInitialized());
01402    * data[8] = String.valueOf(this.schemaIsStatic);
01403    * data[9] = String.valueOf(this.connectionManagers.size());
01404    * data[10] = String.valueOf(getTotalActiveConnections());
01405    * data[11] = String.valueOf(totalRequest);
01406    * data[12] = String.valueOf(totalTransactions);
01407    * data[13] = lastKnownCheckpoint;
01408    *</pre>
01409    * 
01410    * @return an array of strings
01411    */
01412   public String[] getBackendData()
01413   {
01414     String[] data = new String[14];
01415     data[0] = this.name;
01416     data[1] = this.driverClassName;
01417     data[2] = this.url;
01418     data[3] = String.valueOf(this.activeTransactions.size());
01419     data[4] = String.valueOf(this.pendingRequests.size());
01420     data[5] = String.valueOf(this.isReadEnabled());
01421     data[6] = String.valueOf(this.isWriteEnabled());
01422     try
01423     {
01424       data[7] = String.valueOf(this.isInitialized());
01425     }
01426     catch (Exception e)
01427     {
01428       data[7] = "unknown";
01429     }
01430     data[8] = String.valueOf(this.schemaIsStatic);
01431 
01432     data[9] = String.valueOf(this.connectionManagers.size());
01433     data[10] = String.valueOf(getTotalActiveConnections());
01434     data[11] = String.valueOf(totalRequest);
01435     data[12] = String.valueOf(totalTransactions);
01436     if (lastKnownCheckpoint == null || lastKnownCheckpoint.equalsIgnoreCase(""))
01437       data[13] = "<unknown>";
01438     else
01439       data[13] = lastKnownCheckpoint;
01440     return data;
01441   }
01442 
01443   /**
01444    * Get the total number of active connections for this backend
01445    * 
01446    * @return number of active connections for all
01447    *         <code>AbstractConnectionManager</code> connected to this backend
01448    */
01449   public long getTotalActiveConnections()
01450   {
01451     int activeConnections = 0;
01452     Iterator iter = connectionManagers.keySet().iterator();
01453     while (iter.hasNext())
01454       activeConnections += ((AbstractConnectionManager) connectionManagers
01455           .get(iter.next())).getCurrentNumberOfConnections();
01456     return activeConnections;
01457   }
01458 
01459   /**
01460    * @return Returns the dynamicPrecision.
01461    */
01462   public int getDynamicPrecision()
01463   {
01464     return dynamicPrecision;
01465   }
01466 
01467   /**
01468    * Set the amount of information that must be gathered when fetching database
01469    * schema information.
01470    * 
01471    * @param dynamicPrecision The dynamicPrecision to set.
01472    * @param gatherSystemTables True if we must gather system tables
01473    * @param schemaName Schema name to use to gather tables
01474    */
01475   public void setDynamicPrecision(int dynamicPrecision,
01476       boolean gatherSystemTables, String schemaName)
01477   {
01478     this.dynamicPrecision = dynamicPrecision;
01479     this.gatherSystemTables = gatherSystemTables;
01480     this.schemaName = schemaName;
01481   }
01482 
01483   /**
01484    * Returns the total number of transactions executed by this backend.
01485    * 
01486    * @return Total number of transactions.
01487    */
01488   public int getTotalTransactions()
01489   {
01490     return totalTransactions;
01491   }
01492 
01493   /**
01494    * Returns the total number of read requests executed by this backend.
01495    * 
01496    * @return Returns the totalReadRequest.
01497    */
01498   public int getTotalReadRequest()
01499   {
01500     return totalReadRequest;
01501   }
01502 
01503   /**
01504    * Returns the total number of write requests executed by this backend.
01505    * 
01506    * @return Returns the totalWriteRequest.
01507    */
01508   public int getTotalWriteRequest()
01509   {
01510     return totalWriteRequest;
01511   }
01512 
01513   /**
01514    * Returns the total number of requests executed by this backend.
01515    * 
01516    * @return Returns the totalRequest.
01517    */
01518   public int getTotalRequest()
01519   {
01520     return totalRequest;
01521   }
01522 
01523   /**
01524    * setLastKnownCheckpoint for this backend
01525    * 
01526    * @param checkpoint the checkpoint
01527    */
01528   public void setLastKnownCheckpoint(String checkpoint)
01529   {
01530     this.lastKnownCheckpoint = checkpoint;
01531   }
01532 
01533   /**
01534    * Returns the lastKnownCheckpoint value.
01535    * 
01536    * @return Returns the lastKnownCheckpoint.
01537    */
01538   public String getLastKnownCheckpoint()
01539   {
01540     return lastKnownCheckpoint;
01541   }
01542 
01543   /**
01544    * Returns the databaseProductName value.
01545    * 
01546    * @return Returns the databaseProductName.
01547    */
01548   public String getDatabaseProductName()
01549   {
01550     return driverCompliance.getDatabaseProductName();
01551   }
01552 
01553   /**
01554    * Returns the rewritingRules value.
01555    * 
01556    * @return Returns the rewritingRules.
01557    */
01558   public ArrayList getRewritingRules()
01559   {
01560     return rewritingRules;
01561   }
01562 
01563   /**
01564    * Sets the rewritingRules value.
01565    * 
01566    * @param rewritingRules The rewritingRules to set.
01567    */
01568   public void setRewritingRules(ArrayList rewritingRules)
01569   {
01570     this.rewritingRules = rewritingRules;
01571   }
01572 
01573   /**
01574    * Returns a deeply copied clone of this backend Will use the same rewriting
01575    * rules and will get new instance of connection managers with the same
01576    * configuration
01577    * 
01578    * @param newName the new name for this new backend
01579    * @param parameters a set of parameters to use to replace values from the
01580    *          copied backend. <br>
01581    *          The different parameters are: <br>
01582    *          <ul>
01583    *          <li><tt>driverPath</tt>: the path to the driver</li>
01584    *          <li><tt>driver</tt>: the driver class name</li>
01585    *          <li><tt>url</tt>: the url to connect to the database</li>
01586    *          <li><tt>connectionTestStatement</tt>: the query to test the
01587    *          connection</li>
01588    *          </ul>
01589    *          <br>
01590    * @return <code>DatabaseBackend</code> instance
01591    * @throws Exception if cannot proceed the copy
01592    */
01593   public DatabaseBackend copy(String newName, Map parameters) throws Exception
01594   {
01595     // Get the parameters from the backend if they are not specified, or take
01596     // them from the map of parameters otherwise.
01597     String driverPath = parameters.containsKey(DatabasesXmlTags.ATT_driverPath)
01598         ? (String) parameters.get(DatabasesXmlTags.ATT_driverPath)
01599         : this.getDriverPath();
01600 
01601     String driverClassName = parameters
01602         .containsKey(DatabasesXmlTags.ATT_driver) ? (String) parameters
01603         .get(DatabasesXmlTags.ATT_driver) : this.getDriverClassName();
01604 
01605     String url = parameters.containsKey(DatabasesXmlTags.ATT_url)
01606         ? (String) parameters.get(DatabasesXmlTags.ATT_url)
01607         : this.getURL();
01608 
01609     String connectionTestStatement = parameters
01610         .containsKey(DatabasesXmlTags.ATT_connectionTestStatement)
01611         ? (String) parameters.get(DatabasesXmlTags.ATT_connectionTestStatement)
01612         : this.getConnectionTestStatement();
01613 
01614     // Create the new backend object
01615     DatabaseBackend newBackend = new DatabaseBackend(newName, driverPath,
01616         driverClassName, url, virtualDatabaseName, writeCanBeEnabled,
01617         connectionTestStatement);
01618 
01619     // Set the rewriting rules and the connection managers as the backend we
01620     // are copying
01621     newBackend.setRewritingRules(this.getRewritingRules());
01622 
01623     // Set Connection managers
01624     HashMap connectionManagers = this.getConnectionManagers();
01625     Iterator iter = connectionManagers.keySet().iterator();
01626 
01627     String vlogin = null;
01628     AbstractConnectionManager connectionManager;
01629     while (iter.hasNext())
01630     {
01631       vlogin = (String) iter.next();
01632       connectionManager = (AbstractConnectionManager) connectionManagers
01633           .get(vlogin);
01634       newBackend.addConnectionManager(vlogin, connectionManager.copy(url,
01635           newName));
01636     }
01637 
01638     return newBackend;
01639 
01640   }
01641 
01642   /**
01643    * Returns the isBackuping value.
01644    * 
01645    * @return Returns the isBackuping.
01646    */
01647   public boolean isBackuping()
01648   {
01649     return state == BackendState.BACKUPING;
01650   }
01651 
01652   /**
01653    * Set the state of a backend
01654    * 
01655    * @param state see BackendState for a possible list of the different state
01656    * @see org.objectweb.cjdbc.common.shared.BackendState
01657    */
01658   public synchronized void setState(int state)
01659   {
01660     switch (state)
01661     {
01662       case BackendState.UNKNOWN :
01663         lastKnownCheckpoint = null;
01664       case BackendState.READ_ENABLED_WRITE_DISABLED :
01665       case BackendState.READ_ENABLED_WRITE_ENABLED :
01666       case BackendState.READ_DISABLED_WRITE_ENABLED :
01667       case BackendState.DISABLING :
01668       case BackendState.BACKUPING :
01669       case BackendState.RECOVERING :
01670       case BackendState.REPLAYING :
01671       case BackendState.DISABLED :
01672         this.state = state;
01673         if (logger.isDebugEnabled())
01674           logger.debug(Translate.get("backend.state.changed", new String[]{
01675               name, getState()}));
01676         notifyStateChange();
01677         break;
01678       default :
01679         throw new IllegalArgumentException("Unknown backend state:" + state);
01680     }
01681   }
01682 
01683   /**
01684    * Notify the state of the backend has changed. This does two things: 1.
01685    * Change the state of the backend stored in the recovery log 2. Sends a jmx
01686    * notification. This method has all the data prefilled because we know all
01687    * the parameters in advance, except the type of the notification.
01688    * 
01689    * @see CjdbcNotificationList
01690    */
01691   public void notifyStateChange()
01692   {
01693     if (stateListener != null)
01694       stateListener.changeState(this);
01695     notifyJmx(getState());
01696   }
01697 
01698   /**
01699    * Sends JMX notification
01700    * 
01701    * @param type notification type
01702    * @see CjdbcNotificationList
01703    */
01704   public void notifyJmx(String type)
01705   {
01706     notifyJmx(type, CjdbcNotificationList.NOTIFICATION_LEVEL_INFO, Translate
01707         .get(type, getName()));
01708   }
01709 
01710   /**
01711    * Sends JMX error notification
01712    * 
01713    * @param e <tt>Exception</tt> object. Only the message will be used
01714    * @param type notification type
01715    * @see CjdbcNotificationList
01716    */
01717   public void notifyJmxError(String type, Exception e)
01718   {
01719     notifyJmx(type, CjdbcNotificationList.NOTIFICATION_LEVEL_ERROR, Translate
01720         .get(type, new String[]{getName(), e.getMessage()}));
01721 
01722   }
01723 
01724   private void notifyJmx(String type, String level, String message)
01725   {
01726     if (MBeanServerManager.isJmxEnabled())
01727     {
01728       // Send notification
01729       Hashtable data = new Hashtable();
01730       data.put(CjdbcNotificationList.DATA_DATABASE, getVirtualDatabaseName());
01731       data.put(CjdbcNotificationList.DATA_DRIVER, getDriverClassName());
01732       String checkpoint = getLastKnownCheckpoint();
01733       checkpoint = (checkpoint == null) ? "" : checkpoint;
01734       data.put(CjdbcNotificationList.DATA_CHECKPOINT, checkpoint);
01735       data.put(CjdbcNotificationList.DATA_NAME, getName());
01736       data.put(CjdbcNotificationList.DATA_URL, getURL());
01737       RmiConnector.broadcastNotification(this, type, level, message, data);
01738     }
01739   }
01740 
01741   /**
01742    * Returns the writeCanBeEnabled value.
01743    * 
01744    * @return Returns the writeCanBeEnabled.
01745    */
01746   public boolean isWriteCanBeEnabled()
01747   {
01748     return writeCanBeEnabled;
01749   }
01750 
01751   /**
01752    * Sets the stateListener value.
01753    * 
01754    * @param stateListener The stateListener to set.
01755    */
01756   public void setStateListener(BackendStateListener stateListener)
01757   {
01758     this.stateListener = stateListener;
01759   }
01760 
01761   /**
01762    * String description
01763    * 
01764    * @return a string description of the backend.
01765    */
01766   public String toString()
01767   {
01768     return "Backend: Name[" + this.name + "] State[" + this.state
01769         + "] JDBCConnected[" + isJDBCConnected() + "] ActiveTransactions["
01770         + activeTransactions.size() + "] PendingRequests["
01771         + pendingRequests.size() + "]";
01772   }
01773 
01774   /*
01775    * Serialization
01776    */
01777 
01778   private void writeObject(java.io.ObjectOutputStream out) throws IOException
01779   {
01780     out.writeUTF(name);
01781     out.writeUTF(connectionTestStatement);
01782     out.writeUTF(driverClassName);
01783     out.writeUTF(url);
01784 
01785     boolean b1 = driverPath != null;
01786     out.writeBoolean(b1);
01787     if (b1)
01788       out.writeUTF(driverPath);
01789 
01790     boolean b2 = lastKnownCheckpoint != null;
01791     out.writeBoolean(b2);
01792     if (b2)
01793       out.writeUTF(lastKnownCheckpoint);
01794 
01795     out.writeInt(state);
01796     out.writeInt(totalReadRequest);
01797     out.writeInt(totalRequest);
01798     out.writeInt(totalTransactions);
01799     out.writeInt(totalWriteRequest);
01800     out.writeInt(sqlShortFormLength);
01801 
01802     out.writeBoolean(schemaIsStatic);
01803     out.writeBoolean(writeCanBeEnabled);
01804 
01805     out.flush();
01806   }
01807 
01808   private void readObject(java.io.ObjectInputStream in) throws IOException
01809   {
01810     this.name = in.readUTF();
01811     this.connectionTestStatement = in.readUTF();
01812     this.driverClassName = in.readUTF();
01813     this.url = in.readUTF();
01814 
01815     if (in.readBoolean())
01816       this.driverPath = in.readUTF();
01817     if (in.readBoolean())
01818       this.lastKnownCheckpoint = in.readUTF();
01819 
01820     this.state = in.readInt();
01821     this.totalReadRequest = in.readInt();
01822     this.totalRequest = in.readInt();
01823     this.totalTransactions = in.readInt();
01824     this.totalWriteRequest = in.readInt();
01825     this.sqlShortFormLength = in.readInt();
01826 
01827     this.schemaIsStatic = in.readBoolean();
01828     this.writeCanBeEnabled = in.readBoolean();
01829   }
01830 }

Generated on Mon Apr 11 22:01:30 2005 for C-JDBC by  doxygen 1.3.9.1