package org.sharegov.cirm.rdb.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.Properties;
import javax.sql.DataSource;
import mjson.Json;
import oracle.jdbc.pool.OracleDataSource;
import oracle.ucp.UniversalConnectionPoolException;
import oracle.ucp.admin.UniversalConnectionPoolManager;
import oracle.ucp.admin.UniversalConnectionPoolManagerImpl;
import oracle.ucp.jdbc.PoolDataSource;
import oracle.ucp.jdbc.PoolDataSourceFactory;
import static org.sharegov.cirm.rdb.Sql.*;
import org.sharegov.cirm.rdb.DBU;
import org.sharegov.cirm.rdb.DatabaseHook;
import org.sharegov.cirm.utils.DBGUtils;
import org.sharegov.cirm.utils.ThreadLocalStopwatch;
public class OracleHook implements DatabaseHook
{
public static final int POOL_SIZE_INITIAL = 5;
public static final int POOL_SIZE_MAX = 50; //150 processes limit on server
public static final int POOL_CONNECTION_REUSE_COUNT_MAX = 1000;
public static final int POOL_CONNECTION_STATEMENTS_MAX = 40;
public static final boolean POOL_CONNECTION_VALIDATE_ON_BORROW = true;
public static final int POOL_CONNECTION_WAIT_TIMEOUT_SECS = 120;
public static final int POOL_CONNECTION_INACTIVE_TIMEOUT_SECS = 8 * 3600; //before it is removed from pool
public static final int POOL_CONNECTION_PREFETCH_ROWS = 50; //single db roundtrip
public static final int POOL_CONNECTION_BATCH_ROWS = 50; //single db roundtrip
public DataSource createDataSource(Json description)
{
try
{
OracleDataSource ods = new OracleDataSource();
ods.setURL(description.at("hasUrl").asString());
ods.setUser(description.at("hasUsername").asString());
ods.setPassword(description.at("hasPassword").asString());
//Set connection timeout if configured
if (description.has("hasTimeoutSecs")) {
try {
Properties connectionProperties = new Properties();
int timeoutSecs = Integer.parseInt(description.at("hasTimeoutSecs").asString());
connectionProperties.setProperty("oracle.jdbc.ReadTimeout", "" + timeoutSecs * 1000);
ods.setConnectionProperties(connectionProperties);
} catch (Exception e) {
ThreadLocalStopwatch.error("Database connection hasTimeoutSecs not an integer value, using default for data source url" + ods.getURL());
e.printStackTrace();
}
}
return ods;
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
public DataSource createPooledDataSource(Json description)
{
String poolName = "Cirm UCP Pool for " + description.at("iri").asString();
PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
try
{
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL(description.at("hasUrl").asString());
pds.setUser(description.at("hasUsername").asString());
pds.setPassword(description.at("hasPassword").asString());
pds.setConnectionPoolName(poolName);
pds.setInitialPoolSize(POOL_SIZE_INITIAL);
pds.setMinPoolSize(POOL_SIZE_INITIAL);
pds.setMaxPoolSize(POOL_SIZE_MAX);
pds.setMaxConnectionReuseCount(POOL_CONNECTION_REUSE_COUNT_MAX);
//Sets implicit statement cache on all pooled connections
pds.setMaxStatements(POOL_CONNECTION_STATEMENTS_MAX);
pds.setValidateConnectionOnBorrow(POOL_CONNECTION_VALIDATE_ON_BORROW);
//How long to wait if a conn is not available
pds.setConnectionWaitTimeout(POOL_CONNECTION_WAIT_TIMEOUT_SECS);
//How many secs to wait until a pooled and unused connection is removed from pool
// 8 h
pds.setInactiveConnectionTimeout(POOL_CONNECTION_INACTIVE_TIMEOUT_SECS);
Properties connectionProperties = new Properties();
connectionProperties.setProperty("defaultRowPrefetch", "" + POOL_CONNECTION_PREFETCH_ROWS);
connectionProperties.setProperty("defaultBatchValue", "" + POOL_CONNECTION_BATCH_ROWS);
//Set connection timeout if configured
if (description.has("hasTimeoutSecs")) {
try {
int timeoutSecs = Integer.parseInt(description.at("hasTimeoutSecs").asString());
connectionProperties.setProperty("oracle.jdbc.ReadTimeout", "" + timeoutSecs);
} catch (Exception e) {
ThreadLocalStopwatch.error("Database connection hasTimeoutSecs not an integer value, using default for pool data source url" + pds.getURL());
e.printStackTrace();
}
}
pds.setConnectionProperties(connectionProperties);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
try
{
Connection testConn = pds.getConnection();
testConn.close();
}
catch (Exception e)
{
ThreadLocalStopwatch.getWatch().time("POOL DATA SOURCE: FAILED TO GET A TEST CONNECTION FROM POOL!\r\n Exception was: ");
e.printStackTrace();
System.err.print("Attemting to destroy the failing pool \"" + poolName + "\"...");
try
{
UniversalConnectionPoolManager pm = UniversalConnectionPoolManagerImpl.getUniversalConnectionPoolManager();
pm.destroyConnectionPool(poolName);
System.err.println("Succeeded.");
}
catch (UniversalConnectionPoolException e1)
{
System.err.println("Failed. Exception on failing to destroy pool was:");
e1.printStackTrace();
}
throw new RuntimeException(e);
}
DBGUtils.printPoolDataSourceInfo(pds);
return pds;
}
public long nextSequence(Connection conn, String sequenceName) throws SQLException
{
ResultSet rs = null;
java.sql.Statement stmt = null;
try
{
stmt = conn.createStatement();
rs = stmt.executeQuery("select " + sequenceName+ ".nextval from dual");
rs.next();
long nextId = rs.getLong(1);
conn.commit();
return nextId;
}
finally
{
DBU.close(null, stmt, rs);
}
}
public String nextSequenceClause(String sequenceName)
{
return sequenceName + ".NEXTVAL";
}
public Date timeStamp(Connection conn) throws SQLException
{
PreparedStatement stmt = null;
ResultSet rs = null;
Date result;
String select = "SELECT SYSTIMESTAMP FROM DUAL";
try
{
stmt = conn.prepareStatement(select);
rs = stmt.executeQuery();
rs.next();
result = rs.getTimestamp(1);
conn.commit();
}
finally
{
DBU.close(null, stmt, rs);
}
return result;
}
public String paginate(String sql, long minValue, long maxValue)
{
return SELECT()
.COLUMN("*")
.FROM("(" +
SELECT()
.COLUMN("a.*")
.COLUMN("rownum rnum")
.FROM("(" + sql + ") a")
.WHERE("rownum")
.LESS_THAN_OR_EQUAL("" + maxValue).SQL()
+")")
.WHERE("rnum")
.GREATER_THAN_OR_EQUAL("" + minValue).SQL();
}
public void resetSequence(Connection conn, String sequenceName) throws SQLException
{
ResultSet rs = null;
java.sql.Statement stmt = null;
try
{
stmt = conn.createStatement();
stmt.execute("drop sequence " + sequenceName);
stmt.execute("CREATE SEQUENCE " + sequenceName + " start with 1 minvalue 1 increment by 1 cache 20 order");
conn.commit();
}
finally
{
DBU.close(null, stmt, rs);
}
}
}