package org.caudexorigo.jdbc;
import java.io.BufferedReader;
import java.io.IOException;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.caudexorigo.ErrorAnalyser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Db
{
private static Logger log = LoggerFactory.getLogger(Db.class);
private static final int MAX_TRIES = 0;
public static void closeQuietly(Connection conn)
{
try
{
if (conn != null)
{
conn.close();
}
}
catch (SQLException e)
{
// quiet
}
}
public static void closeQuietly(ResultSet rs)
{
try
{
if (rs != null)
{
rs.close();
}
}
catch (SQLException e)
{
// quiet
}
}
public static void closeQuietly(Statement stmt)
{
try
{
if (stmt != null)
{
stmt.close();
}
}
catch (SQLException e)
{
// quiet
}
}
private Map<String, CallableStatementEntry> call_statement_cache;
private Connection connection;
private final DbInfo dbinfo;
private boolean isActive;
private boolean isOracle = false;
private Map<String, PreparedStatementEntry> prep_statement_cache;
private Statement statement;
private Object mutex = new Object();
public Db(DbInfo dbinfo)
{
super();
this.dbinfo = dbinfo;
init();
}
protected boolean useStatementCache()
{
return dbinfo.getUseCache();
}
public void beginTransaction()
{
try
{
validateConnection();
connection.setAutoCommit(false);
}
catch (SQLException e)
{
isActive = false;
throw new RuntimeException(e.getMessage(), e);
}
}
public void commitTransaction()
{
try
{
validateConnection();
connection.commit();
connection.setAutoCommit(true);
}
catch (SQLException e)
{
isActive = false;
throw new RuntimeException(e.getMessage(), e);
}
}
public Connection getConnection()
{
validateConnection();
return connection;
}
public DbInfo getDbInfo()
{
return dbinfo;
}
public void rollbackTransaction()
{
try
{
// validateConnection();
connection.rollback();
connection.setAutoCommit(true);
}
catch (SQLException e)
{
isActive = false;
throw new RuntimeException(e.getMessage(), e);
}
}
private void __closeQuietly(Connection conn)
{
closeQuietly(conn);
isActive = false;
conn = null;
}
private void bindParameter(PreparedStatement prepStatement, Object param, int index, int step) throws SQLException
{
// Have to handle null values seperately
if (param != null)
{
if (log.isDebugEnabled())
{
log.debug("Setting the '" + param + "' value in the statement at position " + (index + step));
}
// If the object is our representation of a null value, then
// handle it seperately
if (param instanceof NullSQLType)
{
prepStatement.setNull(index + step, ((NullSQLType) param).getFieldType());
}
else if (param instanceof String)
{
String s = (String) param;
prepStatement.setString(index + step, s);
}
else if (param instanceof Boolean)
{
boolean b = ((Boolean) param).booleanValue();
prepStatement.setBoolean(index + 1, b);
}
else if (param instanceof Short)
{
short sh = ((Short) param).shortValue();
prepStatement.setShort(index + step, sh);
}
else if (param instanceof Integer)
{
int value = ((Integer) param).intValue();
prepStatement.setInt(index + 1, value);
}
else if (param instanceof Long)
{
long l = ((Long) param).longValue();
prepStatement.setLong(index + step, l);
}
else if (param instanceof Float)
{
float f = ((Float) param).floatValue();
prepStatement.setFloat(index + step, f);
}
else if (param instanceof Double)
{
double d = ((Double) param).doubleValue();
prepStatement.setDouble(index + step, d);
}
else if (param instanceof java.util.Date)
{
java.util.Date pdate = (java.util.Date) param;
prepStatement.setTimestamp(index + step, new java.sql.Timestamp(pdate.getTime()));
}
else if (param instanceof java.sql.Timestamp)
{
prepStatement.setTimestamp(index + step, (java.sql.Timestamp) param);
}
else if (param instanceof java.sql.Date)
{
prepStatement.setDate(index + step, (java.sql.Date) param);
}
else if (param instanceof java.sql.Time)
{
prepStatement.setTime(index + step, (java.sql.Time) param);
}
else if (param instanceof org.caudexorigo.jdbc.SqlArray)
{
org.caudexorigo.jdbc.SqlArray arr_sql = (org.caudexorigo.jdbc.SqlArray) param;
java.sql.Array arr_param = connection.createArrayOf(arr_sql.typeName, arr_sql.elements);
prepStatement.setArray(index + step, arr_param);
}
else if (param instanceof byte[])
{
prepStatement.setBytes(index + step, (byte[]) param);
}
else
{
prepStatement.setObject(index + step, param);
}
}
else
{
// Can't use a null value in a prepared statement. If you want
// to persist a null value, use the "NullSQLType" object to
// represent this in the prepared statement.;
// Try to use the generic NULL placeholder
prepStatement.setNull(index + step, java.sql.Types.NULL);
}
}
private CallableStatement buildCallableStatement(String spName, int param_lenght)
{
StringBuilder sql;
if (isOracle)
{
sql = new StringBuilder("{ call ? := " + spName + "(");
}
else
{
sql = new StringBuilder("{call " + spName + "(");
}
for (int i = 0; i < param_lenght; i++)
{
sql.append("?");
if (i < (param_lenght - 1))
sql.append(",");
}
sql.append(")}");
try
{
CallableStatement cs = connection.prepareCall(sql.toString());
try
{
cs.setQueryTimeout(dbinfo.getQueryTimeout());
}
catch (Throwable t)
{
log.warn("'setQueryTimeout(int)' is not yet implemented");
}
return cs;
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
private PreparedStatement buildPreparedStatment(String sql)
{
synchronized (mutex)
{
try
{
PreparedStatement prepStatement = connection.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
try
{
prepStatement.setQueryTimeout(dbinfo.getQueryTimeout());
}
catch (Throwable t)
{
log.warn("'setQueryTimeout(int)' is not yet implemented");
}
return prepStatement;
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
}
private Statement buildStatement()
{
try
{
Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
try
{
statement.setQueryTimeout(dbinfo.getQueryTimeout());
}
catch (Throwable t)
{
log.warn("'setQueryTimeout(int)' is not yet implemented");
}
return statement;
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
private String clobToString(Clob cl)
{
if (cl == null)
return "";
StringBuilder strOut = new StringBuilder();
String aux;
// We access to stream, as this way we don't have to use the
// CLOB.length() which is slower...
BufferedReader br = null;
try
{
br = new BufferedReader(cl.getCharacterStream());
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
try
{
while ((aux = br.readLine()) != null)
strOut.append(aux);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
return strOut.toString();
}
private int executeCallableStatement(int retryCount, String spName, Object... params)
{
if (params == null)
{
params = new Object[0];
}
validateConnection();
CallableStatement cs = getCallableStatement(spName, params.length);
setCallableStatementParameters(cs, params);
try
{
return cs.executeUpdate(); // run the stored procedure
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return executeCallableStatement(++retryCount, spName, params);
}
}
finally
{
if (!dbinfo.getUseCache())
{
closeQuietly(cs);
}
}
}
private int executePreparedStatement(int retryCount, String sql, Object... params)
{
validateConnection();
PreparedStatement prepStatement = getPreparedStatement(sql);
try
{
setPreparedStatementParameters(prepStatement, params);
return prepStatement.executeUpdate();
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return executePreparedStatement(++retryCount, sql, params);
}
}
finally
{
if (!dbinfo.getUseCache())
{
closeQuietly(prepStatement);
}
}
}
protected int executePreparedStatement(PreparedStatement pstmt, Object... params)
{
try
{
setPreparedStatementParameters(pstmt, params);
return pstmt.executeUpdate();
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
private int executeStatement(int retryCount, String sql)
{
validateConnection();
try
{
return statement.executeUpdate(sql);
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return executeStatement(++retryCount, sql);
}
}
}
private ResultSet fetchResultSetWithCallableStatement(int retryCount, CallableStatement cs, Object... params)
{
if (params == null)
{
params = new Object[0];
}
validateConnection();
try
{
if (isOracle)
{
// oracle.jdbc.driver.OracleTypes.CURSOR
// public static final int CURSOR = -10;
final int ORACLE_CURSOR = -10;
cs.registerOutParameter(1, ORACLE_CURSOR);
setCallableStatementParameters(cs, params);
cs.execute();
return (ResultSet) cs.getObject(1);
}
else
{
setCallableStatementParameters(cs, params);
return cs.executeQuery(); // run the stored procedure
}
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return fetchResultSetWithPreparedStatment(++retryCount, cs, params);
}
}
}
private ResultSet fetchResultSetWithPreparedStatment(int retryCount, PreparedStatement ps, Object... params)
{
validateConnection();
ResultSet rs = null;
try
{
setPreparedStatementParameters(ps, params);
rs = ps.executeQuery();
return (rs);
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return fetchResultSetWithPreparedStatment(++retryCount, ps, params);
}
}
}
private ResultSet fetchResultSetWithStatment(int retryCount, String sql)
{
validateConnection();
ResultSet rs = null;
try
{
rs = statement.executeQuery(sql);
}
catch (Throwable t)
{
if (isInTransaction() || (retryCount >= MAX_TRIES))
{
throw new RuntimeException(t);
}
else
{
destroy();
log.warn("Error: '{}'. Will try to execute database command one more time", t.getMessage());
init();
return fetchResultSetWithStatment(++retryCount, sql);
}
}
return (rs);
}
private synchronized CallableStatement getCallableStatement(String spName, int param_lenght)
{
if (dbinfo.getUseCache())
{
synchronized (mutex)
{
String cacheKey = spName + param_lenght;
CallableStatementEntry cse = call_statement_cache.get(cacheKey);
if ((cse == null) || cse.isStale())
{
if (cse != null)
{
closeQuietly(cse.get());
}
CallableStatement cs = buildCallableStatement(spName, param_lenght);
cse = new CallableStatementEntry(dbinfo.getTtl(), cs, cacheKey);
call_statement_cache.put(cacheKey, cse);
}
return cse.get();
}
}
else
{
return buildCallableStatement(spName, param_lenght);
}
}
PreparedStatement getPreparedStatement(String sql)
{
if (dbinfo.getUseCache())
{
synchronized (mutex)
{
PreparedStatementEntry pse = prep_statement_cache.get(sql);
if ((pse == null) || pse.isStale())
{
if (pse != null)
{
closeQuietly(pse.get());
}
PreparedStatement ps = buildPreparedStatment(sql);
pse = new PreparedStatementEntry(dbinfo.getTtl(), ps, sql);
prep_statement_cache.put(sql, pse);
}
return pse.get();
}
}
else
{
return buildPreparedStatment(sql);
}
}
private void init()
{
try
{
this.connection = DriverManager.getConnection(dbinfo.getDriverUrl(), dbinfo.getUsername(), dbinfo.getPassword());
this.statement = buildStatement();
isActive = true;
}
catch (Throwable error)
{
log.error(error.getMessage());
isActive = false;
}
this.prep_statement_cache = new HashMap<String, PreparedStatementEntry>();
this.call_statement_cache = new HashMap<String, CallableStatementEntry>();
if ("oracle.jdbc.OracleDriver".equals(dbinfo.getDriverClass()))
{
isOracle = true;
}
}
private boolean isInTransaction()
{
try
{
return !connection.getAutoCommit();
}
catch (SQLException e)
{
log.error(e.getMessage(), e);
return false;
}
}
private void setCallableStatementParameters(CallableStatement cs, Object... params)
{
try
{
validateStatement(cs, params);
int step = 1;
if (isOracle)
{
step = 2;
}
for (int i = 0; i < params.length; i++)
{
Object param = params[i];
bindParameter(cs, param, i, step);
}
}
catch (SQLException e)
{
throw new RuntimeException("Store proc call failed: " + e.getMessage());
}
}
private void setPreparedStatementParameters(PreparedStatement prepStatement, Object[] params) throws SQLException
{
validateStatement(prepStatement, params);
// Loop through each value, determine it's corresponding SQL type,
// and stuff that value into the prepared statement.
for (int i = 0; i < params.length; i++)
{
Object param = params[i];
bindParameter(prepStatement, param, i, 1);
}
}
private void validateConnection()
{
try
{
if ((connection != null) && !connection.getAutoCommit())
{
return;
}
}
catch (Throwable e)
{
Throwable r = ErrorAnalyser.findRootCause(e);
log.error("Database Connection (in auto commit mode == false) is broken. Message: '{}'. Will try to create a new connection", r.getMessage());
__closeQuietly(connection);
init();
}
boolean isClosed = false;
try
{
isClosed = connection.isClosed();
}
catch (Throwable e)
{
isClosed = true;
}
if ((connection == null) || !isActive() || isClosed)
{
log.error("Database Connection to the configured database is broken. Will try to create a new connection");
__closeQuietly(connection);
init();
}
}
private void validateStatement(PreparedStatement prepStatement, Object[] params) throws SQLException
{
// If we have a null value here, then bail.
if ((params == null) || (prepStatement == null))
{
String message = "Cannot prepare statement with null arguments.";
log.error(message);
throw new SQLException(message);
}
}
protected void destroy()
{
closeQuietly(statement);
Collection<PreparedStatementEntry> pres_lst = prep_statement_cache.values();
for (PreparedStatementEntry entry : pres_lst)
{
closeQuietly(entry.get());
}
Collection<CallableStatementEntry> cs_lst = call_statement_cache.values();
for (CallableStatementEntry entry : cs_lst)
{
closeQuietly(entry.get());
}
__closeQuietly(connection);
}
protected int executeCallableStatement(CallableStatement cs, Object... params)
{
if (params == null)
{
params = new Object[0];
}
setCallableStatementParameters(cs, params);
try
{
return cs.executeUpdate(); // run the stored procedure
}
catch (Throwable t)
{
throw new RuntimeException(t);
}
}
protected int executeCallableStatement(String spName)
{
return executeCallableStatement(0, spName, new Object[0]);
}
protected int executeCallableStatement(String spName, Object... params)
{
return executeCallableStatement(0, spName, params);
}
protected int executePreparedStatement(String sql, Object... params)
{
return executePreparedStatement(0, sql, params);
}
protected int executeStatement(String sql)
{
return executeStatement(0, sql);
}
public ResultSet fetchResultSetWithCallableStatement(CallableStatement cs)
{
return fetchResultSetWithCallableStatement(cs, new Object[0]);
}
public ResultSet fetchResultSetWithCallableStatement(CallableStatement cs, Object... params)
{
return fetchResultSetWithCallableStatement(0, cs, params);
}
public ResultSet fetchResultSetWithPreparedStatment(PreparedStatement ps, Object... params)
{
return fetchResultSetWithPreparedStatment(0, ps, params);
}
public ResultSet fetchResultSetWithStatment(String sql)
{
return fetchResultSetWithStatment(0, sql);
}
protected boolean isActive()
{
return isActive;
}
@Override
public String toString()
{
return String.format("Db [dbinfo=%s]", dbinfo);
}
}