/** * Narvaro: @VERSION@ * Build Date: @DATE@ * Commit Head: @HEAD@ * JDK: @JDK@ * ANT: @ANT@ * */ package edu.csus.ecs.moneybeets.narvaro.database; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.log4j.Logger; import edu.csus.ecs.moneybeets.narvaro.util.ClassUtils; import edu.csus.ecs.moneybeets.narvaro.util.ConfigurationManager; /** * Central manager of database connections. * * <p> * This manager also provides a set of utility methods * that abstract out operations that may not work on all databases * such as setting the max number of rows that a query should return. * </p> * */ public enum DatabaseManager { Narvaro, ; private DatabaseManager() { } private static final Logger LOG = Logger.getLogger(DatabaseManager.class.getName()); private ConnectionProvider connectionProvider; private static SchemaManager schemaManager = new SchemaManager(); /** * Returns a connection from the currently active connection provider. If * one is not active, an attempt will be made to set it. An exception will be * thrown if the connection provider is not able to be set, or if no connection * was able to be made. * * @return A <code>Connection</code>. * @throws SQLException If an SQL Exception occurs. * @throws IllegalStateException If no <code>ConnectionProvider</code> is available or capable of being set. */ public Connection getConnection() throws SQLException, IllegalStateException { if (connectionProvider == null) { String className = ConfigurationManager.NARVARO.getString("narvaro.connectionprovider.classname"); if (!"".equals(className) && className != null) { // attempt to load the class try { Class<?> conClass = ClassUtils.forName(className); setConnectionProvider((ConnectionProvider)conClass.newInstance()); } catch (Exception e) { LOG.fatal("Failed to create the connection provider specified by configuration", e); throw new IllegalStateException("No connection provider specified in configuration", e); } } else { LOG.fatal("Failed to create the connection provider specified by configuration"); throw new IllegalStateException("No connection provider specified in configuration"); } } Integer retryCount = 0; Integer retryMax = 10; Integer retryWait = 250; // milliseconds Connection con = null; SQLException lastException = null; do { try { con = connectionProvider.getConnection(); if (con != null) { // got one, let's hand it off. return con; } } catch (SQLException e) { lastException = e; LOG.info("Unable to get a connection from the database pool (attempt " + retryCount + " out of " + retryMax + ")", e); } try { Thread.sleep(retryWait); } catch (Exception e) { // ignore } retryCount++; } while (retryCount <= retryMax); // if we made it to here, it means we did not get a connection. throw an exception throw new SQLException("DatabaseManager.getConnection() failed to obtain a connection after " + retryCount + " retries. The exception from the last attept is as follows" + lastException); } /** * Returns a Connection from the currently active connection provider that * is ready to participate in transactions (auto commit is set to false). * * @return A connection with transactions enabled. * @throws SQLException If a SQL exception occurs. */ public Connection getTransactionConnection() throws SQLException { Connection con = getConnection(); con.setAutoCommit(false); return con; } /** * Closes a PreparedStatement and Connection. However, it firsts rolls back the transaction or * commits it depending on the value of the <code>abortTransaction</code>. * * @param ps The prepared statement to close. * @param con The connection to close. * @param abortTransaction True if the transaction should be rolled back. */ public void closeTransactionConnection(final PreparedStatement ps, final Connection con, final boolean abortTransaction) { closeStatement(ps); closeTransactionConnection(con, abortTransaction); } /** * Closes a Connection. However, it first rolls back the transaction or * commits it depending on the value of the <code>abortTransaction</code>. * * @param con The connection to close. * @param abortTransaction True if the transaction should be rolled back. */ public void closeTransactionConnection(final Connection con, final boolean abortTransaction) { // rollback or commit the transaction try { if (abortTransaction) { con.rollback(); } else { con.commit(); } } catch (Exception e) { LOG.error(e.getMessage(), e); } // reset the connection to auto-commit mode. try { con.setAutoCommit(true); } catch (Exception e) { LOG.error(e.getMessage(), e); } closeConnection(con); } /** * Closes a result set. This method should be called within the finally section of * your database logic, as in the following example: * * <pre> * public void doSomething(final Connection con) { * ResultSet rs = null; * PreparedStatement ps = null; * try { * ps = con.prepareStatement("SELECT foo FROM bar"); * rs = ps.executeQuery(); * .... * } catch (SQLException e) { * LOG.error(e.getMessage(), e); * } finally { * DatabaseManager.closeResultSet(rs); * DatabaseManager.closeStatement(ps); * } * } * </pre> * * @param rs The result set to close. */ public void closeResultSet(final ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { LOG.error(e.getMessage(), e); } } } /** * Closes a statement. This method should be called within the finally section of * your database logic, as in the following example: * * <pre> * public void doSomething(final Connection con) { * PreparedStatement ps = null; * try { * ps = con.prepareStatement("SELECT foo FROM bar"); * .... * } catch (SQLException e) { * LOG.error(e.getMessage(), e); * } finally { * DatabaseManager.closeStatement(ps); * } * } * </pre> * * @param stmt The statement. */ public void closeStatement(final PreparedStatement ps) { if (ps != null) { try { ps.close(); } catch (Exception e) { LOG.error(e.getMessage(), e); } } } /** * Closes a statement and result set. This method should be called within the finally * section of your database logic, as in the following example: * * <pre> * public void doSomething(final Connection con) { * PreparedStatement ps = null; * ResultSet rs = null; * try { * ps = con.prepareStatement("SELECT foo FROM bar"); * rs = ... * .... * } catch (SQLException e) { * LOG.error(e.getMessage(), e); * } finally { * DatabaseManager.closeStatement(rs, ps); * } * } * </pre> * * @param rs The result set. * @param stmt The statement. */ public void closeStatement(final ResultSet rs, final PreparedStatement ps) { closeResultSet(rs); closeStatement(ps); } /** * Closes a statement. This method should be called within the try section of * you database logic when you reuse a statement. It may throw an exception, * so don't place it in the finally section. Example: * * <pre> * public void doSomething(final Connection con) { * PreparedStatement ps = null; * try { * ps = con.prepareStatement("SELECT foo FROM bar"); * ps.execute(); * .... * <b>DatabaseManager.fastCloseStatement(ps);</b> * ps = con.prepareStatement("SELECT * FROM blah"); * .... * } * .... * } * </pre> * * @param ps The statement to close. * @throws SQLException If an exception occurs while closing the statement. */ public void fastCloseStatement(final PreparedStatement ps) throws SQLException { ps.close(); } /** * Closes a statement and a result set. This method should be called within * the try section of you database logic when you reuse a statement. It may * throw an exception, so don't place it in the finally section. Example: * * <pre> * public void doSomething(final Connection con) { * PreparedStatement ps = null; * ResultSet rs = null; * try { * ps = con.prepareStatement("SELECT foo FROM bar"); * rs = ps.executeQuery(); * .... * <b>DatabaseManager.fastCloseStatement(rs, ps);</b> * ps = con.prepareStatement("SELECT * FROM blah"); * .... * } * .... * } * </pre> * * @param rs The result set to close. * @param ps The statement to close. * @throws SQLException If an exception occurs while closing the statement or result set. */ public void fastCloseStatement(final ResultSet rs, final PreparedStatement ps) throws SQLException { rs.close(); ps.close(); } /** * Closes a result set, statement and database connection (returning the connection to * the connection pool). This method should be called within the finally section of * your database logic, as in the following example: * * <pre> * Connection con = null; * PreparedStatement ps = null; * ResultSet rs = null; * try { * con = DatabaseManager.getConnection(); * ps = con.prepareStatement("SELECT foo FROM bar"); * rs = ps.executeQuery(); * .... * } catch (SQLException e) { * * } * </pre> * * @param rs The result set. * @param stmt The statement. * @param con The connection. */ public void closeConnection(final ResultSet rs, final PreparedStatement ps, final Connection con) { closeResultSet(rs); closeStatement(ps); closeConnection(con); } /** * Closes a statement and database connection (returning the connection to the * connection pool). This method should be called within the finally section of * your database logic, as in the following example: * * <pre> * Connection con = null; * PreparedStatement ps = null; * try { * con = DatabaseManager.getConnection(); * ps = con.prepareStatement("SELECT foo FROM bar"); * .... * } catch (SQLException e) { * LOG.error(e.getMessage(), e); * } finally { * DatabaseManager.closeConnection(ps, con); * } * </pre> * * @param stmt The statement. * @param con The connection. */ public void closeConnection(final PreparedStatement ps, final Connection con) { closeStatement(ps); closeConnection(con); } /** * Closes a database connection (returning the connection to the connection pool). * Any statement associated with the connection should be closed before calling this method. * This method should be called within the finally section of your database logic, as * in the following example: * * <pre> * Connection con = null; * try { * con = DatabaseManager.getConnection(); * .... * } catch (SQLException e) { * LOG.error(e.getMessage(), e); * } finally { * DatabaseManager.closeConnection(con); * } * </pre> * * @param con The connection. */ public void closeConnection(final Connection con) { if (con != null) { try { con.close(); } catch (Exception e) { LOG.error(e.getMessage(), e); } } } /** * Returns the current connection provider. The only case in which * this method should be called is if more information about the current * connection provider is needed. Database connections should always be * obtained by calling the <code>getConnection()</code> method of this class. * * @return The connection provider. */ public ConnectionProvider getConnectionProvider() { return connectionProvider; } /** * Sets the connection provider. The old provider (if it exists) is * shut down before the new one is started. A connection provider * <b>should not</b> be started before being passed to the connection * manager because the manager will call the start() method automatically. * * @param provider The ConnectionProvider that the manager should obtain connections from. */ private void setConnectionProvider(final ConnectionProvider provider) { // guarantee there is not // an active connection provider... // if there is, destroy it... destroyConnectionProvider(); connectionProvider = provider; connectionProvider.start(); // Now, check the provider schema for updates Connection con = null; try { con = connectionProvider.getConnection(); schemaManager.checkNarvaroSchema(con); } catch (Exception e) { LOG.error(e.getMessage(),e); } finally { closeConnection(con); } } /** * Destroys the current connection provider. Future calls to * {@link #getConnectionProvider()} will return <code>null</code> * until a new ConnectionProvider is set, or one is automatically loaded * by a call to {@link #getConnection()}. */ public void destroyConnectionProvider() { if (connectionProvider != null) { connectionProvider.destroy(); connectionProvider = null; } } /** * Returns a SchemaManager instance, which can be used to manage * the database schema information for Narvaro. * * @return A SchemaManager instance. */ public static SchemaManager getSchemaManager() { return schemaManager; } /** * Terminates the DatabaseManager. * * <p> * The shutdown() method call will: * <ol> * <li>Destroy all ConnectionProvider instances, which will terminate any open connections in the pool.</li> * </ol> * </p> */ public void shutdown() { connectionProvider.destroy(); } }