package de.zalando.sprocwrapper.proxy.executors;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.zalando.sprocwrapper.SProcCall.AdvisoryLock;
import de.zalando.sprocwrapper.dsprovider.SameConnectionDatasource;
import de.zalando.sprocwrapper.proxy.InvocationContext;
/**
* This Executor wraps stored procedure calls that use advisory locks and / or need different statement timeouts set.
*
* @author jmussler
*/
public class ExecutorWrapper implements Executor {
private final Executor executor;
private final long timeoutInMilliSeconds;
private final AdvisoryLock lock;
private static final Logger LOG = LoggerFactory.getLogger(ExecutorWrapper.class);
public ExecutorWrapper(final Executor e, final long t, final AdvisoryLock a) {
executor = e;
timeoutInMilliSeconds = t;
lock = a;
}
private void setTimeout(final Connection conn) throws SQLException {
if (timeoutInMilliSeconds <= 0) {
return;
}
LOG.debug("Setting statement timeout {}", timeoutInMilliSeconds);
final Statement st = conn.createStatement();
st.execute("SET application_name TO 'timeout:" + timeoutInMilliSeconds + "'");
st.execute("SET statement_timeout TO " + timeoutInMilliSeconds);
st.close();
}
private void resetTimeout(final Connection conn) throws SQLException {
if (timeoutInMilliSeconds <= 0) {
return;
}
LOG.debug("Resetting statement timeout");
final Statement st = conn.createStatement();
st.execute("RESET statement_timeout");
st.execute("RESET application_name");
st.close();
}
private boolean lockAdvisoryLock(final Connection conn) throws SQLException {
if (lock == null || lock.equals(AdvisoryLock.NoLock.LOCK)) {
return true;
}
final Statement st = conn.createStatement();
final ResultSet rs = st.executeQuery("SELECT pg_advisory_lock(" + lock.getLockId() + ") AS \"" + lock
.getName() + "\";");
boolean b = false;
if (rs.next()) {
b = true;
}
rs.close();
st.close();
return b;
}
private boolean unlockAdvisoryLock(final Connection conn) throws SQLException {
if (lock == null || lock.equals(AdvisoryLock.NoLock.LOCK)) {
return true;
}
final Statement st = conn.createStatement();
final ResultSet rs = st.executeQuery("SELECT pg_advisory_unlock(" + lock.getLockId() + ")");
boolean b = false;
if (rs.next()) {
b = rs.getBoolean(1);
}
rs.close();
st.close();
return b;
}
@Override
public Object executeSProc(final DataSource ds, final String sql, final Object[] args, final int[] types,
final InvocationContext invocationContext, final Class<?> returnType) {
SameConnectionDatasource sameConnDs = null;
try {
sameConnDs = new SameConnectionDatasource(ds.getConnection());
setTimeout(sameConnDs.getConnection());
if (!lockAdvisoryLock(sameConnDs.getConnection())) {
throw new RuntimeException("Could not acquire AdvisoryLock " + lock.getName());
}
return executor.executeSProc(sameConnDs, sql, args, types, invocationContext, returnType);
} catch (final SQLException e) {
throw new RuntimeException("SQL Exception in execute sproc: " + sql, e);
} finally {
if (sameConnDs != null) {
try {
if (timeoutInMilliSeconds > 0) {
try {
resetTimeout(sameConnDs.getConnection());
} catch (final SQLException ex) {
LOG.error("Exception in reseting statement timeout!", ex);
}
}
// unlock in all cases, locks not owned by this session cannot be unlocked
if (lock != null && !lock.equals(AdvisoryLock.NoLock.LOCK)) {
try {
unlockAdvisoryLock(sameConnDs.getConnection());
} catch (final SQLException ex) {
LOG.error("Exception in reseting advisory lock!", ex);
}
}
} finally {
try {
sameConnDs.close();
} catch (final SQLException ex) {
LOG.error("Exception in closing underlying connection!", ex);
}
}
}
}
}
}