/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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.jkiss.dbeaver.model.impl.jdbc;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.messages.ModelMessages;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPTransactionIsolation;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCSession;
import org.jkiss.dbeaver.model.impl.AbstractExecutionContext;
import org.jkiss.dbeaver.model.impl.jdbc.exec.JDBCSavepointImpl;
import org.jkiss.dbeaver.model.qm.QMUtils;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.Savepoint;
/**
* JDBCExecutionContext
*/
public class JDBCExecutionContext extends AbstractExecutionContext<JDBCDataSource> implements DBCTransactionManager
{
private static final Log log = Log.getLog(JDBCExecutionContext.class);
private volatile Connection connection;
private volatile Boolean autoCommit;
private volatile Integer transactionIsolationLevel;
public JDBCExecutionContext(@NotNull JDBCDataSource dataSource, String purpose)
{
super(dataSource, purpose);
}
@NotNull
private Connection getConnection() {
return connection;
}
public void connect(DBRProgressMonitor monitor) throws DBCException
{
connect(monitor, null, null, false, true);
}
void connect(@NotNull DBRProgressMonitor monitor, Boolean autoCommit, @Nullable Integer txnLevel, boolean forceActiveObject, boolean addContext) throws DBCException
{
if (connection != null && addContext) {
log.error("Reopening not-closed connection");
close();
}
boolean connectionReadOnly = dataSource.getContainer().isConnectionReadOnly();
DBExecUtils.startContextInitiation(this);
try {
this.connection = dataSource.openConnection(monitor, purpose);
if (this.connection == null) {
throw new DBCException("Null connection returned");
}
monitor.subTask("Set connection defaults");
// Get defaults from preferences
if (autoCommit == null) {
autoCommit = dataSource.getContainer().isDefaultAutoCommit();
}
// Get default txn isolation level
if (txnLevel == null) {
txnLevel = dataSource.getContainer().getDefaultTransactionsIsolation();
}
try {
connection.setAutoCommit(autoCommit);
this.autoCommit = autoCommit;
} catch (Throwable e) {
log.debug("Can't set auto-commit state: " + e.getMessage()); //$NON-NLS-1$
}
{
// Cache auto-commit
try {
this.autoCommit = connection.getAutoCommit();
} catch (Throwable e) {
log.debug("Can't check auto-commit state", e); //$NON-NLS-1$
this.autoCommit = false;
}
}
if (!this.autoCommit && txnLevel != null) {
try {
this.connection.setTransactionIsolation(txnLevel);
this.transactionIsolationLevel = txnLevel;
} catch (Throwable e) {
log.debug("Can't set transaction isolation level", e); //$NON-NLS-1$
}
}
try {
this.initContextBootstrap(monitor, autoCommit);
} catch (DBCException e) {
log.error("Error while running context bootstrap", e);
}
try {
// Copy context state
this.dataSource.initializeContextState(monitor, this, forceActiveObject && !connectionReadOnly);
} catch (DBCException e) {
log.error("Error while initializing context state", e);
}
try {
// Commit transaction. We can perform init SQL which potentially may lock some resources
// Let's free them.
if (!this.autoCommit) {
try (JDBCSession session = openSession(monitor, DBCExecutionPurpose.META, "End transaction")) {
session.commit();
}
}
} catch (Throwable e) {
log.error("Error ending transaction after context initialize", e);
}
if (addContext) {
// Add self to context list
this.dataSource.addContext(this);
}
} finally {
DBExecUtils.finishContextInitiation(this);
}
}
public @NotNull Connection getConnection(DBRProgressMonitor monitor) throws SQLException
{
if (connection == null) {
try {
connect(monitor);
} catch (DBCException e) {
if (e.getCause() instanceof SQLException) {
throw (SQLException) e.getCause();
} else {
throw new SQLException(e);
}
}
}
return connection;
}
@NotNull
@Override
public JDBCSession openSession(@NotNull DBRProgressMonitor monitor, @NotNull DBCExecutionPurpose purpose, @NotNull String taskTitle)
{
return dataSource.createConnection(monitor, this, purpose, taskTitle);
}
@Override
public void checkContextAlive(DBRProgressMonitor monitor) throws DBException {
if (!JDBCUtils.isConnectionAlive(getDataSource(), getConnection())) {
throw new DBCException("Connection is dead");
}
}
@Override
public boolean isConnected()
{
return connection != null;
}
@NotNull
@Override
public InvalidateResult invalidateContext(@NotNull DBRProgressMonitor monitor, boolean closeOnFailure)
throws DBException
{
if (this.connection == null) {
connect(monitor);
return InvalidateResult.CONNECTED;
}
if (!JDBCUtils.isConnectionAlive(getDataSource(), getConnection())) {
Boolean prevAutocommit = autoCommit;
Integer txnLevel = transactionIsolationLevel;
boolean addNewContext = false;
if (closeOnFailure) {
close();
addNewContext = true;
}
connect(monitor, prevAutocommit, txnLevel, true, addNewContext);
return InvalidateResult.RECONNECTED;
}
return InvalidateResult.ALIVE;
}
@Override
public void close()
{
// [JDBC] Need sync here because real connection close could take some time
// while UI may invoke callbacks to operate with connection
synchronized (this) {
if (this.connection != null) {
this.dataSource.closeConnection(connection, purpose);
}
this.connection = null;
super.closeContext();
}
// Remove self from context list
this.dataSource.removeContext(this);
}
//////////////////////////////////////////////////////////////
// Transaction manager
//////////////////////////////////////////////////////////////
@Override
public DBPTransactionIsolation getTransactionIsolation()
throws DBCException
{
try {
if (transactionIsolationLevel == null) {
transactionIsolationLevel = getConnection().getTransactionIsolation();
}
return JDBCTransactionIsolation.getByCode(transactionIsolationLevel);
} catch (SQLException e) {
throw new JDBCException(e, dataSource);
}
}
@Override
public void setTransactionIsolation(@NotNull DBRProgressMonitor monitor, @NotNull DBPTransactionIsolation transactionIsolation)
throws DBCException
{
if (!(transactionIsolation instanceof JDBCTransactionIsolation)) {
throw new DBCException(ModelMessages.model_jdbc_exception_invalid_transaction_isolation_parameter);
}
JDBCTransactionIsolation jdbcTIL = (JDBCTransactionIsolation) transactionIsolation;
try {
getConnection().setTransactionIsolation(jdbcTIL.getCode());
transactionIsolationLevel = jdbcTIL.getCode();
} catch (SQLException e) {
throw new JDBCException(e, dataSource);
} finally {
QMUtils.getDefaultHandler().handleTransactionIsolation(this, transactionIsolation);
}
//QMUtils.getDefaultHandler().handleTransactionIsolation(getConnection(), jdbcTIL);
}
@Override
public boolean isAutoCommit()
throws DBCException
{
try {
if (autoCommit == null) {
autoCommit = getConnection().getAutoCommit();
}
return autoCommit;
}
catch (SQLException e) {
throw new JDBCException(e, dataSource);
}
}
@Override
public void setAutoCommit(@NotNull DBRProgressMonitor monitor, boolean autoCommit)
throws DBCException
{
monitor.subTask("Set JDBC connection auto-commit " + autoCommit);
try {
connection.setAutoCommit(autoCommit);
this.autoCommit = null;
}
catch (SQLException e) {
throw new JDBCException(e, dataSource);
} finally {
QMUtils.getDefaultHandler().handleTransactionAutocommit(this, autoCommit);
}
}
@Override
public DBCSavepoint setSavepoint(@NotNull DBRProgressMonitor monitor, String name)
throws DBCException
{
Savepoint savepoint;
try {
if (name == null) {
savepoint = getConnection().setSavepoint();
} else {
savepoint = getConnection().setSavepoint(name);
}
}
catch (SQLException e) {
throw new DBCException(e, dataSource);
}
return new JDBCSavepointImpl(this, savepoint);
}
@Override
public boolean supportsSavepoints()
{
try {
return getConnection().getMetaData().supportsSavepoints();
}
catch (SQLException e) {
// ignore
return false;
}
}
@Override
public void releaseSavepoint(@NotNull DBRProgressMonitor monitor, @NotNull DBCSavepoint savepoint)
throws DBCException
{
try {
if (savepoint instanceof JDBCSavepointImpl) {
getConnection().releaseSavepoint(((JDBCSavepointImpl) savepoint).getOriginal());
} else if (savepoint instanceof Savepoint) {
getConnection().releaseSavepoint((Savepoint) savepoint);
} else {
throw new SQLFeatureNotSupportedException(ModelMessages.model_jdbc_exception_bad_savepoint_object);
}
}
catch (SQLException e) {
throw new JDBCException(e, dataSource);
}
}
@Override
public void commit(@NotNull DBCSession session)
throws DBCException
{
try {
getConnection().commit();
}
catch (SQLException e) {
throw new JDBCException(e, dataSource);
}
finally {
if (session.isLoggingEnabled()) {
QMUtils.getDefaultHandler().handleTransactionCommit(this);
}
}
}
@Override
public void rollback(@NotNull DBCSession session, DBCSavepoint savepoint)
throws DBCException
{
try {
if (savepoint != null) {
if (savepoint instanceof JDBCSavepointImpl) {
getConnection().rollback(((JDBCSavepointImpl) savepoint).getOriginal());
} else if (savepoint instanceof Savepoint) {
getConnection().rollback((Savepoint) savepoint);
} else {
throw new SQLFeatureNotSupportedException(ModelMessages.model_jdbc_exception_bad_savepoint_object);
}
} else {
getConnection().rollback();
}
}
catch (SQLException e) {
throw new JDBCException(e, dataSource);
}
finally {
if (session.isLoggingEnabled()) {
QMUtils.getDefaultHandler().handleTransactionRollback(this, savepoint);
}
}
}
public void reconnect(DBRProgressMonitor monitor) throws DBCException {
close();
connect(monitor);
}
}