/* * DatabaseConnector.java * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * Copyright (c) 2004 Per Cederberg. All rights reserved. */ package org.liquidsite.util.db; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.HashMap; import java.util.Properties; import org.liquidsite.util.log.Log; /** * A database connector. The database connector manages all * connections to the database, pooling resources as desired. By * default connection pooling is not used, but is may be activated by * modifying the pool size. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.0 */ public class DatabaseConnector { /** * The class logger. */ private static final Log LOG = new Log(DatabaseConnector.class); /** * The default connection timeout in milliseconds. By default * this is set to 14400000 ms (4 h). */ public static final long DEFAULT_CONNECTION_TIMEOUT = 14400000L; /** * The default query timeout in seconds. By default this is set * to 5 seconds. * * @see DatabaseConnection#setQueryTimeout */ public static final int DEFAULT_QUERY_TIMEOUT = 5; /** * The JDBC database URL. */ private String url; /** * The JDBC database properties. */ private Properties properties; /** * The database connection pool. By default no connection pooling * is used. A connection pool is created if the pool size is set. */ private DatabaseConnectionPool pool = null; /** * The connection expiration timeout in milliseconds. If this * value is negative the database connections will never expire. * * @see #DEFAULT_CONNECTION_TIMEOUT */ private long connectionTimeout = DEFAULT_CONNECTION_TIMEOUT; /** * The map of database functions. This maps a name to an SQL * query or statement. The SQL may contain optional parameters * with the '?' character. */ private HashMap functions = new HashMap(); /** * Loads the specified JDBC database driver. This method must be * called once before attempting to connect with the specified * driver. Calling this method several times has no effect. * * @param driver the fully qualified classname * * @throws DatabaseConnectionException if the class couldn't be * found or loaded correctly */ public static void loadDriver(String driver) throws DatabaseConnectionException { String message; try { Class.forName(driver).newInstance(); } catch (Exception e) { message = "couldn't find JDBC driver " + driver; LOG.warning(message, e); throw new DatabaseConnectionException(message, e); } } /** * Creates a new database connector. This connector properties * will initially be empty. * * @param url the JDBC database URL */ public DatabaseConnector(String url) { this(url, new Properties()); } /** * Creates a new database connector. * * @param url the JDBC database URL * @param properties the JDBC database properties */ public DatabaseConnector(String url, Properties properties) { this.url = url; this.properties = properties; LOG.info("created database connector for " + url); } /** * Returns a string representation of this object. * * @return a string representation of this object */ public String toString() { return url; } /** * Returns the JDBC database URL. * * @return the JDBC database URL */ public String getUrl() { return url; } /** * Returns a specified database connector property. * * @param name the property name * * @return the property value, or * null if not found */ public String getProperty(String name) { return properties.getProperty(name); } /** * Returns the database connector properties. * * @return the database properties */ public Properties getProperties() { return properties; } /** * Sets the specified database connector property. * * @param name the property name * @param value the property value */ public void setProperty(String name, String value) { properties.setProperty(name, value); } /** * Returns the database connection expiration timeout. If this * value is negative the database connections will never expire. * * @return the connection expiration timeout (in milliseconds), or * a negative value for unlimited * * @see #setConnectionTimeout * @see #DEFAULT_CONNECTION_TIMEOUT */ public long getConnectionTimeout() { return connectionTimeout; } /** * Sets the database connection expiration timeout. If this value * is negative the database connections will never expire. By * modifying this value already open connections may be caused to * expire. * * @param timeout the new connection timeout (in milliseconds), or * a negative value for unlimited * * @see #getConnectionTimeout */ public void setConnectionTimeout(long timeout) { LOG.info("new connection timeout: " + timeout + ", was: " + connectionTimeout + ", for " + this); this.connectionTimeout = timeout; } /** * Returns the maximum database connection pool size. By default * no connection pooling is used, and the pool size is therefore * zero (0). A new connection pool is created the first time the * setPoolSize() method is called. * * @return the database connection pool size * * @see #setPoolSize */ public int getPoolSize() { if (pool == null) { return 0; } else { return pool.getMaximumSize(); } } /** * Sets the maximum database connection pool size. The first time * this method is called, a new database connection pool is * created. The pool minimum size and timeout will be set to * default values. * * @param size the new maximum connection pool size * * @see #getPoolSize */ public void setPoolSize(int size) { if (pool == null) { pool = new DatabaseConnectionPool(this); } if (size < 1) { pool.setMinimumSize(0); } else { pool.setMinimumSize(1); } pool.setMaximumSize(size); } /** * Returns the database function with the specified name. * * @param name the database function name * * @return the database function SQL, or * null if not found */ String getFunction(String name) { return (String) functions.get(name); } /** * Returns a database connection. This method will either create * a new connection, or return a previous connection from the * connection pool (if one exists). All connections returned by * this method must be disposed of by calling the * returnConnection() method. * * @return a database connection * * @throws DatabaseConnectionException if a database connection * couldn't be established * * @see #returnConnection */ public DatabaseConnection getConnection() throws DatabaseConnectionException { LOG.trace("database connection requested for " + this); if (pool == null) { return new DatabaseConnection(this); } else { return pool.getConnection(); } } /** * Disposes of a database connection. This method will either * return the connection to the connection pool, or close the * connection, depending on if a pool exists or not. * * @param con the database connection * * @see #getConnection */ public void returnConnection(DatabaseConnection con) { LOG.trace("database connection returned for " + this); if (pool == null) { con.close(); } else { pool.returnConnection(con); } } /** * Loads a set of database functions. The functions are stored in * a normal properties file, with the value being the SQL code. * * @param file the file containing the functions * * @throws FileNotFoundException if the file couldn't be found * @throws IOException if the file couldn't be read properly */ public void loadFunctions(File file) throws FileNotFoundException, IOException { Properties props = new Properties(); FileInputStream input; input = new FileInputStream(file); props.load(input); input.close(); functions.putAll(props); } /** * Updates the connection pool. This method will step through all * available database connections in the pool, removing all * broken or timed out connections. The connection pool size may * also be adjusted. * * Note that any call to this method should be made from a * background thread, as this method may get stuck waiting for * I/O timeouts. * * @throws DatabaseConnectionException if a database connection * couldn't be established */ public void update() throws DatabaseConnectionException { if (pool != null) { pool.update(); } } /** * Executes a database query or statement. * * @param query the database query * * @return the database query results, or * null for database statements * * @throws DatabaseConnectionException if a database connection * couldn't be established * @throws DatabaseException if the query or statement couldn't * be executed correctly */ public DatabaseResults execute(DatabaseQuery query) throws DatabaseConnectionException, DatabaseException { DatabaseConnection con; DatabaseResults res; con = getConnection(); try { res = con.execute(query); } finally { returnConnection(con); } return res; } /** * Executes a set of SQL statements from a file. Each SQL * statement must be terminated by a ';' character. * * @param file the file with SQL statements * * @throws FileNotFoundException if the file couldn't be found * @throws IOException if the file couldn't be read properly * @throws DatabaseConnectionException if a database connection * couldn't be established * @throws DatabaseException if some statement couldn't be * executed correctly */ public void execute(File file) throws FileNotFoundException, IOException, DatabaseConnectionException, DatabaseException { DatabaseConnection con; con = getConnection(); try { con.execute(file); } finally { returnConnection(con); } } }