Java tutorial
/* * Copyright 2015 Adaptris Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.adaptris.jdbc.connection; import java.io.PrintWriter; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import javax.sql.DataSource; import org.apache.commons.pool.PoolableObjectFactory; import org.apache.commons.pool.impl.GenericObjectPool; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Datasource that has a connection capable of failover to an alternate * database. * <p> * Because of we want to failover to an alternate database, the implementation * of <code>getConnection(String username, String password) * </code> simply discards the username and password and relies on the * configuration that was supplied when constructing this class. This is the * desired behaviour. * </p> * <p> * The underlying pool implementation is a * <code>org.apache.commons.pool.impl.GenericObjectPool</code>. The default size * of the pool is 10 with a wait time of 20seconds. If the pool is exhausted, * then a NoSuchElementException will be thrown after the wait time. * </p> * * @see DataSource * @see FailoverConnection * @see FailoverConfig */ public class FailoverDataSource implements DataSource { private static final String[] REQUIRED_PROPERTIES = { FailoverConfig.JDBC_DRIVER, FailoverConfig.JDBC_AUTO_COMMIT, FailoverConfig.JDBC_TEST_STATEMENT }; /** * Resource Key for the maximum size of the pool. * */ public static final String POOL_MAX_SIZE = "failover.pool.maximum"; /** * Resource Key for the time to wait for an available connection. * */ public static final String POOL_TIME_TO_WAIT = "failover.pool.timetowait"; protected transient Logger logR = LoggerFactory.getLogger(this.getClass()); private FailoverConfig databaseConfig; private GenericObjectPool pool = null; private int poolMaximum; private long poolTimeToWait; private FailoverDataSource() { } public FailoverDataSource(Properties p) { this(); init(p); } private void init(Properties p) { if (p == null) { throw new RuntimeException("No Configuration available "); } for (int i = 0; i < REQUIRED_PROPERTIES.length; i++) { if (!p.containsKey(REQUIRED_PROPERTIES[i])) { throw new RuntimeException("Missing Configuration " + REQUIRED_PROPERTIES[i]); } } databaseConfig = new FailoverConfig(p); poolMaximum = Integer.parseInt(p.getProperty(POOL_MAX_SIZE, "10")); poolTimeToWait = Integer.parseInt(p.getProperty(POOL_TIME_TO_WAIT, "20000")); pool = new GenericObjectPool(new PoolAttendant(databaseConfig), poolMaximum, GenericObjectPool.WHEN_EXHAUSTED_BLOCK, poolTimeToWait); pool.setTestOnBorrow(true); pool.setTestWhileIdle(true); } protected FailoverConfig config() { return databaseConfig; } protected int maxPoolSize() { return poolMaximum; } protected long timeToWait() { return poolTimeToWait; } protected void overrideObjectPool(GenericObjectPool p) throws Exception { destroy(); pool = p; } protected void destroy() throws Exception { pool.close(); } /** * Get the configured connection. * * @see javax.sql.DataSource#getConnection() */ @Override public Connection getConnection() throws SQLException { Connection c = null; try { c = (Connection) pool.borrowObject(); } catch (Exception e) { throw wrapSQLException(e); } return c; } /** * Get the configured connection. * <p> * This class will ignore the supplied credentials, relying on the * configuration used to create this source instead. * </p> * <p> * This can be considered breaking the javax.sql.DataSource contract, however, * this is the desired behaviour when you are failing over to multiple * databases. * </p> * * @see DataSource#getConnection(String, String) */ @Override public Connection getConnection(String username, String password) throws SQLException { return getConnection(); } /** * @see javax.sql.DataSource#setLoginTimeout(int) */ @Override public void setLoginTimeout(int loginTimeout) throws SQLException { DriverManager.setLoginTimeout(loginTimeout); } /** * @see javax.sql.DataSource#getLoginTimeout() */ @Override public int getLoginTimeout() throws SQLException { return DriverManager.getLoginTimeout(); } /** * @see javax.sql.DataSource#setLogWriter(java.io.PrintWriter) */ @Override public void setLogWriter(PrintWriter logWriter) throws SQLException { DriverManager.setLogWriter(logWriter); } /** * @see javax.sql.DataSource#getLogWriter() */ @Override public PrintWriter getLogWriter() throws SQLException { return DriverManager.getLogWriter(); } void returnConnection(ConnectionProxy p) throws SQLException { try { pool.returnObject(p); } catch (Exception e) { throw wrapSQLException(e); } } /** * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } /** * @see java.sql.Wrapper#unwrap(java.lang.Class) */ @Override public <T> T unwrap(Class<T> iface) throws SQLException { throw new SQLException("Nothing to unwrap"); } @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } protected static SQLClientInfoException wrapSQLClientInfoException(Exception e) { if (e instanceof SQLClientInfoException) { return (SQLClientInfoException) e; } SQLClientInfoException e2 = new SQLClientInfoException(); e2.initCause(e); return e2; } protected static SQLException wrapSQLException(Exception e) { if (e instanceof SQLException) { return (SQLException) e; } return new SQLException(e.getMessage(), e); } /** * This class is reponsible for creating swimmers who swim in the Datasource * Pool. */ protected class PoolAttendant implements PoolableObjectFactory { private FailoverConfig config; PoolAttendant(FailoverConfig config) { this.config = config; } /** * @see PoolableObjectFactory#makeObject() */ @Override public Object makeObject() throws Exception { ConnectionProxy conn = new ConnectionProxy(config); return conn; } /** * Validate the object. * <p> * Basically attempts to get an instance of the underlying connection. If * the database has failed, it will throw an SQLException. * * @see PoolableObjectFactory#validateObject(java.lang.Object) * */ @Override public boolean validateObject(Object obj) { if (null == obj) { return false; } try { ConnectionProxy conn = (ConnectionProxy) obj; conn.getWrappedConnection(); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * @see PoolableObjectFactory#destroyObject(java.lang.Object) */ @Override public void destroyObject(Object arg0) throws Exception { if (arg0.getClass().equals(ConnectionProxy.class)) { ConnectionProxy c = (ConnectionProxy) arg0; c.getWrappedConnection().close(); } } /** * In this implementation it does nothing. * * @see PoolableObjectFactory#activateObject(java.lang.Object) */ @Override public void activateObject(Object arg0) throws Exception { } /** * In this implementation it does nothing. * * @see PoolableObjectFactory#passivateObject(java.lang.Object) */ @Override public void passivateObject(Object arg0) throws Exception { } } /** * This class proxies the underlying FailoverConnection class. * <p> * It directly implements Connection which is a front to the underlying * FailoverConnection.getConnection(), the only difference is that when * close() is called, it is simply replaced back into the pool until required. * <p> * Finalization of the Datasource itself will shutdown the pool, and close any * resources that are held by this object. * </p> */ protected class ConnectionProxy implements Connection { private FailoverConnection conn; public ConnectionProxy(FailoverConfig conf) throws SQLException { conn = new FailoverConnection(conf); } Connection getWrappedConnection() throws SQLException { return conn.getConnection(); } /** * @see java.sql.Connection#createStatement() */ @Override public Statement createStatement() throws SQLException { return getWrappedConnection().createStatement(); } /** * @see java.sql.Connection#prepareStatement(java.lang.String) */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return getWrappedConnection().prepareStatement(sql); } /** * @see java.sql.Connection#prepareCall(java.lang.String) */ @Override public CallableStatement prepareCall(String sql) throws SQLException { return getWrappedConnection().prepareCall(sql); } /** * @see java.sql.Connection#nativeSQL(java.lang.String) */ @Override public String nativeSQL(String sql) throws SQLException { return getWrappedConnection().nativeSQL(sql); } /** * @see java.sql.Connection#setAutoCommit(boolean) */ @Override public void setAutoCommit(boolean autoCommit) throws SQLException { getWrappedConnection().setAutoCommit(autoCommit); } /** * @see java.sql.Connection#getAutoCommit() */ @Override public boolean getAutoCommit() throws SQLException { return getWrappedConnection().getAutoCommit(); } /** * @see java.sql.Connection#commit() */ @Override public void commit() throws SQLException { getWrappedConnection().commit(); } /** * @see java.sql.Connection#rollback() */ @Override public void rollback() throws SQLException { getWrappedConnection().rollback(); } /** * @see java.sql.Connection#close() */ @Override public void close() throws SQLException { returnConnection(this); } /** * @see java.sql.Connection#isClosed() */ @Override public boolean isClosed() throws SQLException { return getWrappedConnection().isClosed(); } /** * @see java.sql.Connection#getMetaData() */ @Override public DatabaseMetaData getMetaData() throws SQLException { return getWrappedConnection().getMetaData(); } /** * @see java.sql.Connection#setReadOnly(boolean) */ @Override public void setReadOnly(boolean readOnly) throws SQLException { getWrappedConnection().setReadOnly(readOnly); } /** * @see java.sql.Connection#isReadOnly() */ @Override public boolean isReadOnly() throws SQLException { return getWrappedConnection().isReadOnly(); } /** * @see java.sql.Connection#setCatalog(java.lang.String) */ @Override public void setCatalog(String catalog) throws SQLException { getWrappedConnection().setCatalog(catalog); } /** * @see java.sql.Connection#getCatalog() */ @Override public String getCatalog() throws SQLException { return getWrappedConnection().getCatalog(); } /** * @see java.sql.Connection#setTransactionIsolation(int) */ @Override public void setTransactionIsolation(int level) throws SQLException { getWrappedConnection().setTransactionIsolation(level); } /** * @see java.sql.Connection#getTransactionIsolation() */ @Override public int getTransactionIsolation() throws SQLException { return getWrappedConnection().getTransactionIsolation(); } /** * @see java.sql.Connection#getWarnings() */ @Override public SQLWarning getWarnings() throws SQLException { return getWrappedConnection().getWarnings(); } /** * @see java.sql.Connection#clearWarnings() */ @Override public void clearWarnings() throws SQLException { getWrappedConnection().clearWarnings(); } /** * @see java.sql.Connection#createStatement(int, int) */ @Override public Statement createStatement(int i, int j) throws SQLException { return getWrappedConnection().createStatement(i, j); } /** * @see java.sql.Connection#prepareStatement(String, int, int) */ @Override public PreparedStatement prepareStatement(String sql, int i, int j) throws SQLException { return getWrappedConnection().prepareStatement(sql, i, j); } /** * @see java.sql.Connection#prepareCall(java.lang.String, int, int) */ @Override public CallableStatement prepareCall(String sql, int i, int j) throws SQLException { return getWrappedConnection().prepareCall(sql, i, j); } /** * @see java.sql.Connection#getTypeMap() */ @Override public Map getTypeMap() throws SQLException { return getWrappedConnection().getTypeMap(); } /** * @see java.sql.Connection#setHoldability(int) */ @Override public void setHoldability(int holdability) throws SQLException { getWrappedConnection().setHoldability(holdability); } /** * @see java.sql.Connection#getHoldability() */ @Override public int getHoldability() throws SQLException { return getWrappedConnection().getHoldability(); } /** * @see java.sql.Connection#setSavepoint() */ @Override public Savepoint setSavepoint() throws SQLException { return getWrappedConnection().setSavepoint(); } /** * @see java.sql.Connection#setSavepoint(java.lang.String) */ @Override public Savepoint setSavepoint(String name) throws SQLException { return getWrappedConnection().setSavepoint(name); } /** * @see java.sql.Connection#rollback(java.sql.Savepoint) */ @Override public void rollback(Savepoint savepoint) throws SQLException { getWrappedConnection().rollback(savepoint); } /** * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint) */ @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { getWrappedConnection().releaseSavepoint(savepoint); } /** * @see java.sql.Connection#createStatement(int, int, int) */ @Override public Statement createStatement(int i, int j, int k) throws SQLException { return getWrappedConnection().createStatement(i, j, k); } /** * @see java.sql.Connection#prepareStatement(String, int, int, int) */ @Override public PreparedStatement prepareStatement(String sql, int i, int j, int k) throws SQLException { return getWrappedConnection().prepareStatement(sql, i, j, k); } /** * @see java.sql.Connection#prepareCall(java.lang.String, int, int, int) */ @Override public CallableStatement prepareCall(String sql, int i, int j, int k) throws SQLException { return getWrappedConnection().prepareCall(sql, i, j, k); } /** * @see java.sql.Connection#prepareStatement(java.lang.String, int) */ @Override public PreparedStatement prepareStatement(String sql, int i) throws SQLException { return getWrappedConnection().prepareStatement(sql, i); } /** * @see java.sql.Connection#prepareStatement(java.lang.String, int[]) */ @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return getWrappedConnection().prepareStatement(sql, columnIndexes); } /** * @see java.sql.Connection#prepareStatement(String, String[]) */ @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return getWrappedConnection().prepareStatement(sql, columnNames); } /** * @see java.sql.Connection#createArrayOf(java.lang.String, * java.lang.Object[]) */ @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return getWrappedConnection().createArrayOf(typeName, elements); } /** * @see java.sql.Connection#createBlob() */ @Override public Blob createBlob() throws SQLException { return getWrappedConnection().createBlob(); } /** * @see java.sql.Connection#createClob() */ @Override public Clob createClob() throws SQLException { return getWrappedConnection().createClob(); } /** * @see java.sql.Connection#createNClob() */ @Override public NClob createNClob() throws SQLException { return getWrappedConnection().createNClob(); } /** * @see java.sql.Connection#createSQLXML() */ @Override public SQLXML createSQLXML() throws SQLException { return getWrappedConnection().createSQLXML(); } /** * @see java.sql.Connection#createStruct(java.lang.String, * java.lang.Object[]) */ @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return getWrappedConnection().createStruct(typeName, attributes); } /** * @see java.sql.Connection#getClientInfo() */ @Override public Properties getClientInfo() throws SQLException { return getWrappedConnection().getClientInfo(); } /** * @see java.sql.Connection#getClientInfo(java.lang.String) */ @Override public String getClientInfo(String name) throws SQLException { return getWrappedConnection().getClientInfo(name); } /** * @see java.sql.Connection#isValid(int) */ @Override public boolean isValid(int timeout) throws SQLException { return getWrappedConnection().isValid(timeout); } /** * @see java.sql.Connection#setClientInfo(java.util.Properties) */ @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { try { getWrappedConnection().setClientInfo(properties); } catch (SQLException e) { throw wrapSQLClientInfoException(e); } } /** * @see java.sql.Connection#setClientInfo(java.lang.String, * java.lang.String) */ @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { try { getWrappedConnection().setClientInfo(name, value); } catch (SQLException e) { throw wrapSQLClientInfoException(e); } } /** * @see java.sql.Connection#setTypeMap(java.util.Map) */ @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { getWrappedConnection().setTypeMap(map); } /** * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) */ @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return getWrappedConnection().isWrapperFor(iface); } /** * @see java.sql.Wrapper#unwrap(java.lang.Class) */ @Override public <T> T unwrap(Class<T> iface) throws SQLException { return getWrappedConnection().unwrap(iface); } public void setSchema(String schema) throws SQLException { getWrappedConnection().setSchema(schema); } public String getSchema() throws SQLException { return getWrappedConnection().getSchema(); } public void abort(Executor executor) throws SQLException { getWrappedConnection().abort(executor); } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { getWrappedConnection().setNetworkTimeout(executor, milliseconds); } public int getNetworkTimeout() throws SQLException { return getWrappedConnection().getNetworkTimeout(); } } }