/*******************************************************************************
* Copyright 2014 Miami-Dade County
*
* 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 org.sharegov.cirm.rdb;
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.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
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 oracle.jdbc.OracleConnection;
/**
* This class realizes a thread local connection wrapper that supports a
* topLevelMethod and a subLevelMethod mode of operation. In sublevel mode, commit,
* rollback, close and some other methods are quietly disabled. In toplevel
* mode, all methods are enabled. A close in toplevel mode will end the
* relationship with the thread. For each thread, two objects will be created.
* One for the toplevel method and one to be used by sublevel method calls.
*
* To use this class a thread shall call createThreadLocalConnection(Connection) in the toplevelMethod.
* This will return a fully functional wrapper in topLevelMethod mode and also associate a threadlocal
* wrapper in subLevelMethodMode, which has commit, rollback, et.c. disabled with it.
* The thread can now run through sublevel methods and getThreadLocalConnection() will repeatedly
* return this wrapper object in sublevel mode. All commit and close operations in sublevel methods will be ignored.
* Once execution returns to the starting toplevel method, the toplevel mode wrapper can be used to commit, rollback, et.c.
* the wrapped connection.
*
*
* @author Thomas Hilpold
*/
public class ThreadLocalConnection implements Connection
{
public static boolean DBG = false;
// Access to this variable does not need to be synchronized.
private static ThreadLocal<ThreadLocalConnection> threadlocalSubLevelConnections = new ThreadLocal<ThreadLocalConnection>();
private boolean topLevelMethodMode;
private Connection wrappedConnection;
/**
* Creates and returns a fully functional toplevel thread local connection wrapper for the given
* connection. User needs to make sure, that close() is called by the same
* thread that calls this method.
* This method will also create another wrapper object in sublevelmode and associate it with the thread.
*
* @param conn a connection to be wrapped.
* @return a ThreadLocalConnection with topLevelMethodMode enabled.
*/
public static ThreadLocalConnection createThreadLocalConnectionTopLevel(Connection conn)
{
if (threadlocalSubLevelConnections.get() != null)
{
throw new IllegalStateException("A toplevel connection for thread: " + Thread.currentThread().getName()
+ " was not closed properly.");
}
ThreadLocalConnection topLevelWrappedConnection = new ThreadLocalConnection(conn, true);
threadlocalSubLevelConnections.set(new ThreadLocalConnection(conn, false));
if (DBG)
{
System.out.println("ThreadLocalConnection toplevel created conn : " + topLevelWrappedConnection
+ " associated with thread " + Thread.currentThread().toString());
}
return topLevelWrappedConnection;
}
/**
* Returns the sublevel connection wrapper for this thread, if a toplevel wrapper was already created.
* The same sublevel object will be returned for the same thread in subsequent calls.
*
* @return null or a ThreadLocalConnection with topLevelMethodMode disables (sublevel).
*/
public static ThreadLocalConnection getThreadLocalConnection()
{
if (DBG) {
System.out.println("ThreadLocalConnection sublevel request for : " + Thread.currentThread());
}
ThreadLocalConnection localConn = threadlocalSubLevelConnections.get();
if (DBG && localConn != null) {
System.out.print("returning sublevel connection : " + localConn.wrappedConnection);
}
return localConn;
}
/**
* Tests, if the calling thread has a threadlocal connection associated with it.
* @return
*/
public static boolean hasThreadLocalConnection()
{
return threadlocalSubLevelConnections.get() != null;
}
private ThreadLocalConnection(Connection connection, boolean topLevelMode)
{
if (DBG && topLevelMode) {
System.out.println("Creating toplevel for connection : " + connection + " tlc: " + this);
}
if (DBG && !topLevelMode) {
System.out.println("Creating sublevel for connection : " + connection + " tlc: " + this);
}
this.topLevelMethodMode = topLevelMode;
wrappedConnection = connection;
}
public void assertTopLevelMode() {
if (!isTopLevelMode()) {
throw new IllegalStateException("This threadlocalconnection ignores commit, rollback and close.");
}
}
/**
* For testing only. Do not use.
* @return
*/
public Connection getDirectConnection() {
return wrappedConnection;
}
public boolean isTopLevelMode()
{
return topLevelMethodMode;
}
public void setTopLevelMode(boolean topLevelMode)
{
this.topLevelMethodMode = topLevelMode;
}
// ------------------------------------------------------------------------
// MODIFIED METHODS:
//
public void setAutoCommit(boolean autoCommit) throws SQLException
{
if (topLevelMethodMode)
{
wrappedConnection.setAutoCommit(autoCommit);
}
}
public void commit() throws SQLException
{
if (topLevelMethodMode)
{
wrappedConnection.commit();
}
}
public void rollback() throws SQLException
{
if (topLevelMethodMode)
{
wrappedConnection.rollback();
}
}
public void closeAndDiscard() throws SQLException {
if (topLevelMethodMode) {
if (wrappedConnection.isWrapperFor(OracleConnection.class)) {
try {
OracleConnection oc =wrappedConnection.unwrap(OracleConnection.class);
oc.close(OracleConnection.INVALID_CONNECTION);
System.err.println("Successfully Discarded: " + oc);
} finally {
// Disassociate this connection from the threadlocal var.
threadlocalSubLevelConnections.remove();
}
} else {
close();
}
}
}
public void close() throws SQLException
{
if (topLevelMethodMode)
{
try
{
if (wrappedConnection.getWarnings() != null) {
System.out.println("Connection : " + wrappedConnection + " had warnings before close.:");
System.out.println(wrappedConnection.getWarnings());
}
wrappedConnection.close();
}
catch (SQLException e)
{
throw e;
}
finally
{
// Disassociate this connection from the threadlocal var.
threadlocalSubLevelConnections.remove();
if (DBG)
{
System.out.println("ThreadLocalConnection toplevel close on " + this + " by "
+ Thread.currentThread().toString());
}
}
}
}
/**
* Inactive if not topLevel.
*/
public void setReadOnly(boolean readOnly) throws SQLException
{
if (topLevelMethodMode)
{
wrappedConnection.setReadOnly(readOnly);
}
}
/**
* Effective for ToplevelConnection only, because it needs to be called as
* first statement of a transaction.
*/
public void setTransactionIsolation(int level) throws SQLException
{
if (topLevelMethodMode)
{
wrappedConnection.setTransactionIsolation(level);
}
}
public void setCatalog(String catalog) throws SQLException
{
wrappedConnection.setCatalog(catalog);
}
public void clearWarnings() throws SQLException
{
wrappedConnection.clearWarnings();
}
public void setHoldability(int holdability) throws SQLException
{
wrappedConnection.setHoldability(holdability);
}
public void setTypeMap(Map<String, Class<?>> map) throws SQLException
{
wrappedConnection.setTypeMap(map);
}
// ------------------------------------------------------------------------
// UNMODIFIED METHODS DIRECTLY DELEGATED:
//
public <T> T unwrap(Class<T> iface) throws SQLException
{
return wrappedConnection.unwrap(iface);
}
public boolean isWrapperFor(Class<?> iface) throws SQLException
{
return wrappedConnection.isWrapperFor(iface);
}
public Statement createStatement() throws SQLException
{
return wrappedConnection.createStatement();
}
public PreparedStatement prepareStatement(String sql) throws SQLException
{
return wrappedConnection.prepareStatement(sql);
}
public CallableStatement prepareCall(String sql) throws SQLException
{
return wrappedConnection.prepareCall(sql);
}
public String nativeSQL(String sql) throws SQLException
{
return wrappedConnection.nativeSQL(sql);
}
public boolean getAutoCommit() throws SQLException
{
return wrappedConnection.getAutoCommit();
}
public boolean isClosed() throws SQLException
{
return wrappedConnection.isClosed();
}
public DatabaseMetaData getMetaData() throws SQLException
{
return wrappedConnection.getMetaData();
}
public boolean isReadOnly() throws SQLException
{
return wrappedConnection.isReadOnly();
}
public String getCatalog() throws SQLException
{
return wrappedConnection.getCatalog();
}
public int getTransactionIsolation() throws SQLException
{
return wrappedConnection.getTransactionIsolation();
}
public SQLWarning getWarnings() throws SQLException
{
return wrappedConnection.getWarnings();
}
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
{
return wrappedConnection.createStatement(resultSetType, resultSetConcurrency);
}
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException
{
return wrappedConnection.prepareStatement(sql, resultSetType, resultSetConcurrency);
}
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException
{
return wrappedConnection.prepareCall(sql, resultSetType, resultSetConcurrency);
}
public Map<String, Class<?>> getTypeMap() throws SQLException
{
return wrappedConnection.getTypeMap();
}
public int getHoldability() throws SQLException
{
return wrappedConnection.getHoldability();
}
public Savepoint setSavepoint() throws SQLException
{
return wrappedConnection.setSavepoint();
}
public Savepoint setSavepoint(String name) throws SQLException
{
return wrappedConnection.setSavepoint(name);
}
public void rollback(Savepoint savepoint) throws SQLException
{
wrappedConnection.rollback(savepoint);
}
public void releaseSavepoint(Savepoint savepoint) throws SQLException
{
wrappedConnection.releaseSavepoint(savepoint);
}
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException
{
return wrappedConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException
{
return wrappedConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException
{
return wrappedConnection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
}
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException
{
return wrappedConnection.prepareStatement(sql, autoGeneratedKeys);
}
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException
{
return wrappedConnection.prepareStatement(sql, columnIndexes);
}
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return wrappedConnection.prepareStatement(sql, columnNames);
}
public Clob createClob() throws SQLException
{
return wrappedConnection.createClob();
}
public Blob createBlob() throws SQLException
{
return wrappedConnection.createBlob();
}
public NClob createNClob() throws SQLException
{
return wrappedConnection.createNClob();
}
public SQLXML createSQLXML() throws SQLException
{
return wrappedConnection.createSQLXML();
}
public boolean isValid(int timeout) throws SQLException
{
return wrappedConnection.isValid(timeout);
}
public void setClientInfo(String name, String value) throws SQLClientInfoException
{
wrappedConnection.setClientInfo(name, value);
}
public void setClientInfo(Properties properties) throws SQLClientInfoException
{
wrappedConnection.setClientInfo(properties);
}
public String getClientInfo(String name) throws SQLException
{
return wrappedConnection.getClientInfo(name);
}
public Properties getClientInfo() throws SQLException
{
return wrappedConnection.getClientInfo();
}
public Array createArrayOf(String typeName, Object[] elements) throws SQLException
{
return wrappedConnection.createArrayOf(typeName, elements);
}
public Struct createStruct(String typeName, Object[] attributes) throws SQLException
{
return wrappedConnection.createStruct(typeName, attributes);
}
@Override
public void setSchema(String schema) throws SQLException
{
wrappedConnection.setSchema(schema);
}
@Override
public String getSchema() throws SQLException
{
return wrappedConnection.getSchema();
}
@Override
public void abort(Executor executor) throws SQLException
{
wrappedConnection.abort(executor);
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException
{
wrappedConnection.setNetworkTimeout(executor, milliseconds);
}
@Override
public int getNetworkTimeout() throws SQLException
{
return wrappedConnection.getNetworkTimeout();
}
}