org.firebirdsql.jdbc.InternalTransactionCoordinator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jaybird Show documentation
Show all versions of jaybird Show documentation
JDBC Driver for the Firebird RDBMS
/*
* Firebird Open Source JDBC Driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* LGPL License for more details.
*
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
* can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc;
import org.firebirdsql.gds.TransactionParameterBuffer;
import org.firebirdsql.gds.ng.LockCloseable;
import org.firebirdsql.gds.ng.StatementType;
import org.firebirdsql.jaybird.xca.FBLocalTransaction;
import org.firebirdsql.jaybird.xca.FBManagedConnection;
import org.firebirdsql.util.SQLExceptionChainBuilder;
import java.sql.SQLException;
import java.util.*;
import static java.util.Objects.requireNonNull;
import static org.firebirdsql.jaybird.fb.constants.TpbItems.isc_tpb_autocommit;
/**
* Transaction coordinator for the {@link org.firebirdsql.jdbc.FBConnection} class.
*/
public final class InternalTransactionCoordinator implements FBObjectListener.StatementListener,
FBObjectListener.BlobListener {
private final FBConnection connection;
private AbstractTransactionCoordinator coordinator;
InternalTransactionCoordinator(FBConnection connection) {
this.connection = requireNonNull(connection, "connection");
}
private LockCloseable withLock() {
return connection.withLock();
}
/**
* Switches the auto commit coordinator if currently in a different auto commit mode.
*
* @param autoCommit
* Value for auto commit
* @throws SQLException
* If {@code autoCommit} is {@code true} and the connection is currently enlisted in a
* distributed transaction
*/
void switchTransactionCoordinator(boolean autoCommit) throws SQLException {
if (coordinator != null && getAutoCommit() == autoCommit) {
return;
}
setTransactionCoordinator(false, autoCommit);
}
/**
* Sets the transaction coordinator.
*
* In most cases {@link #switchTransactionCoordinator(boolean)} should be called.
*
*
* @param managedConnection
* Whether this is a managed connection
* @param autoCommit
* Value for auto commit
* @throws SQLException
* If {@code autoCommit} is {@code true}, and {@code managedConnection} is {@code false} and
* the connection is currently enlisted in a distributed transaction
*/
void setTransactionCoordinator(boolean managedConnection, boolean autoCommit) throws SQLException {
FBManagedConnection mc = connection.getManagedConnection();
InternalTransactionCoordinator.AbstractTransactionCoordinator coordinator;
if (managedConnection && mc.inDistributedTransaction()) {
coordinator = new ManagedTransactionCoordinator(connection);
} else if (autoCommit) {
if (mc.inDistributedTransaction()) {
throw new SQLException("Connection enlisted in distributed transaction",
SQLStateConstants.SQL_STATE_INVALID_TX_STATE);
}
if (connection.isUseFirebirdAutoCommit()) {
coordinator = new FirebirdAutoCommitCoordinator(connection, connection.getLocalTransaction());
} else {
coordinator = new AutoCommitCoordinator(connection, connection.getLocalTransaction());
}
} else {
coordinator = new LocalTransactionCoordinator(connection, connection.getLocalTransaction());
}
setCoordinator(coordinator);
}
public boolean getAutoCommit() throws SQLException {
try (LockCloseable ignored = withLock()) {
return coordinator != null && coordinator.isAutoCommit();
}
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.executionStarted(stmt);
}
}
@Override
public FBConnection getConnection() throws SQLException {
return connection;
}
@Override
public void statementClosed(FBStatement stmt) throws SQLException {
coordinator.statementClosed(stmt);
}
@Override
public void statementCompleted(FBStatement stmt) throws SQLException {
statementCompleted(stmt, true);
}
@Override
public void statementCompleted(FBStatement stmt, boolean success) throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.statementCompleted(stmt, success);
}
}
@Override
public void executionCompleted(FirebirdBlob blob) throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.executionCompleted(blob);
}
}
@Override
public void executionStarted(FirebirdBlob blob) throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.executionStarted(blob);
}
}
public void ensureTransaction() throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.ensureTransaction();
}
}
public void commit() throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.commit();
}
}
public void rollback() throws SQLException {
try (LockCloseable ignored = withLock()) {
coordinator.rollback();
}
}
void handleConnectionClose() throws SQLException {
coordinator.handleConnectionClose();
}
private void setCoordinator(AbstractTransactionCoordinator coordinator) throws SQLException {
try (LockCloseable ignored = withLock()) {
if (this.coordinator != null) {
SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>();
try {
this.coordinator.completeStatements(CompletionReason.COMMIT);
} catch (SQLException ex) {
chain.append(ex);
} finally {
try {
this.coordinator.internalCommit();
} catch (SQLException ex) {
chain.append(ex);
}
}
if (chain.hasException()) {
throw chain.getException();
}
coordinator.setStatements(this.coordinator.getStatements());
}
this.coordinator = coordinator;
}
}
public abstract static class AbstractTransactionCoordinator implements FBObjectListener.StatementListener,
FBObjectListener.BlobListener {
protected final FBLocalTransaction localTransaction;
protected final FBConnection connection;
protected final Collection statements = new HashSet<>();
protected AbstractTransactionCoordinator(FBConnection connection, FBLocalTransaction localTransaction) {
this.localTransaction = localTransaction;
this.connection = connection;
}
/**
* Get the connection which owns this coordinator.
*
* @return instance of {@link FBConnection}
*/
@Override
public final FBConnection getConnection() throws SQLException {
return connection;
}
protected final Collection getStatements() {
return statements;
}
protected final void setStatements(Collection statements) {
this.statements.addAll(statements);
}
protected void completeStatements(CompletionReason reason) throws SQLException {
SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>();
// we have to loop through the array, since the
// statement.completeStatement() call causes the
// ConcurrentModificationException
FBStatement[] statementsToComplete = statements.toArray(new FBStatement[0]);
for (FBStatement statement : statementsToComplete) {
try {
statement.completeStatement(reason);
} catch (SQLException ex) {
chain.append(ex);
}
}
// clear the statements (usually needed only for those that
// were not removed in the statement.completeStatement() call
statements.clear();
if (chain.hasException()) {
throw chain.getException();
}
}
final void internalCommit() throws SQLException {
if (localTransaction != null && localTransaction.inTransaction()) {
localTransaction.commit();
}
}
final void internalRollback() throws SQLException {
if (localTransaction != null && localTransaction.inTransaction()) {
localTransaction.rollback();
}
}
public void ensureTransaction() throws SQLException {
configureFirebirdAutoCommit();
if (!localTransaction.inTransaction()) {
localTransaction.begin();
}
}
private void configureFirebirdAutoCommit() throws SQLException {
// Handle Firebird autocommit support
if (connection.isUseFirebirdAutoCommit()) {
TransactionParameterBuffer tpb = connection.getManagedConnection().getTransactionParameters();
if (connection.getAutoCommit()) {
if (!tpb.hasArgument(isc_tpb_autocommit)) {
tpb.addArgument(isc_tpb_autocommit);
}
} else {
tpb.removeArgument(isc_tpb_autocommit);
}
}
}
public abstract void commit() throws SQLException;
public abstract void rollback() throws SQLException;
abstract void handleConnectionClose() throws SQLException;
boolean isAutoCommit() throws SQLException {
return false;
}
@Override
public final void statementCompleted(FBStatement stmt) throws SQLException {
statementCompleted(stmt, true);
}
}
static class AutoCommitCoordinator extends AbstractTransactionCoordinator {
public AutoCommitCoordinator(FBConnection connection, FBLocalTransaction localTransaction) {
super(connection, localTransaction);
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
List tempList = new ArrayList<>(statements);
SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>();
// complete all open statements for the connection (there should be only one anyway)
for (Iterator iter = tempList.iterator(); iter.hasNext(); ) {
FBStatement tempStatement = iter.next();
// enable re-entrancy for the same statement
if (tempStatement == stmt) {
iter.remove();
continue;
}
// Autocommit, so reason of completion is COMMIT
try {
tempStatement.completeStatement(CompletionReason.COMMIT);
} catch (SQLException e) {
chain.append(e);
}
}
statements.removeAll(tempList);
if (chain.hasException()) {
throw chain.getException();
}
if (!statements.contains(stmt)) {
statements.add(stmt);
}
ensureTransaction();
}
@Override
public void statementClosed(FBStatement stmt) throws SQLException {
stmt.completeStatement();
connection.notifyStatementClosed(stmt);
}
@Override
public void statementCompleted(FBStatement stmt, boolean success) throws SQLException {
statements.remove(stmt);
try {
if (!localTransaction.inTransaction()) {
return;
}
if (success) {
localTransaction.commit();
} else {
localTransaction.rollback();
}
} catch (SQLException ex) {
try {
internalRollback();
} catch (SQLException ex2) {
ex.setNextException(ex2);
}
throw ex;
}
}
@Override
public void executionCompleted(FirebirdBlob blob) throws SQLException {
}
@Override
public void executionStarted(FirebirdBlob blob) throws SQLException {
ensureTransaction();
}
@Override
public void commit() throws SQLException {
throw new FBSQLException("Calling commit() in auto-commit mode is not allowed.");
}
@Override
public void rollback() throws SQLException {
throw new FBSQLException("Calling rollback() in auto-commit mode is not allowed.");
}
@Override
void handleConnectionClose() throws SQLException {
if (localTransaction.inTransaction()) {
try {
completeStatements(CompletionReason.COMMIT);
} finally {
internalCommit();
}
}
}
@Override
boolean isAutoCommit() {
return true;
}
}
static class LocalTransactionCoordinator extends AbstractTransactionCoordinator {
public LocalTransactionCoordinator(FBConnection connection, FBLocalTransaction localTransaction) {
super(connection, localTransaction);
}
@Override
public void commit() throws SQLException {
try {
completeStatements(CompletionReason.COMMIT);
} finally {
internalCommit();
}
}
@Override
public void rollback() throws SQLException {
try {
completeStatements(CompletionReason.ROLLBACK);
} finally {
internalRollback();
}
}
@Override
void handleConnectionClose() throws SQLException {
if (localTransaction.inTransaction()) {
rollback();
}
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
if (!statements.contains(stmt)) {
statements.add(stmt);
}
ensureTransaction();
}
@Override
public void statementClosed(FBStatement stmt) throws SQLException {
stmt.completeStatement();
connection.notifyStatementClosed(stmt);
}
@Override
public void statementCompleted(FBStatement stmt, boolean success) throws SQLException {
statements.remove(stmt);
}
@Override
public void executionCompleted(FirebirdBlob blob) throws SQLException {
}
@Override
public void executionStarted(FirebirdBlob blob) throws SQLException {
ensureTransaction();
}
}
static class FirebirdAutoCommitCoordinator extends LocalTransactionCoordinator {
public FirebirdAutoCommitCoordinator(FBConnection connection, FBLocalTransaction localTransaction) {
super(connection, localTransaction);
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
List tempList = new ArrayList<>(statements);
SQLExceptionChainBuilder chain = new SQLExceptionChainBuilder<>();
// complete all open statements for the connection (there should be only one anyway)
for (Iterator iter = tempList.iterator(); iter.hasNext(); ) {
FBStatement tempStatement = iter.next();
// enable re-entrancy for the same statement
if (tempStatement == stmt) {
iter.remove();
continue;
}
// Firebird Autocommit doesn't really commit, but we use CompletionReason.COMMIT to get desired behavior
// for holdable result sets, and the performance win of not having a server roundtrip to close
// the cursor. This might lead to a slight cursor 'leak' until statement or connection close
try {
tempStatement.completeStatement(CompletionReason.COMMIT);
} catch (SQLException e) {
chain.append(e);
}
}
statements.removeAll(tempList);
if (chain.hasException()) {
throw chain.getException();
}
if (!statements.contains(stmt)) {
statements.add(stmt);
}
ensureTransaction();
}
@Override
public void statementCompleted(FBStatement stmt, boolean success) throws SQLException {
statements.remove(stmt);
if (stmt.getStatementType() == StatementType.DDL.getStatementTypeCode()) {
try {
if (!localTransaction.inTransaction()) {
return;
}
if (success) {
localTransaction.commit();
} else {
localTransaction.rollback();
}
} catch (SQLException ex) {
try {
internalRollback();
} catch (SQLException ex2) {
ex.setNextException(ex2);
}
throw ex;
}
}
}
@Override
public void commit() throws SQLException {
throw new FBSQLException("Calling commit() in auto-commit mode is not allowed.");
}
@Override
public void rollback() throws SQLException {
throw new FBSQLException("Calling rollback() in auto-commit mode is not allowed.");
}
@Override
void handleConnectionClose() throws SQLException {
if (localTransaction.inTransaction()) {
try {
completeStatements(CompletionReason.COMMIT);
} finally {
internalCommit();
}
}
}
@Override
boolean isAutoCommit() {
return true;
}
}
static class ManagedTransactionCoordinator extends LocalTransactionCoordinator {
/**
* Create instance of this class for the specified connection.
*
* @param connection
* connection to coordinate.
*/
public ManagedTransactionCoordinator(FBConnection connection) {
super(connection, null);
}
@Override
public void ensureTransaction() throws SQLException {
// do nothing, we are in managed environment.
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
// NO TRANSACTION MANAGEMENT HERE - empty method
}
@Override
public void commit() throws SQLException {
// do nothing, we are in managed environment.
}
@Override
public void rollback() throws SQLException {
// do nothing, we are in managed environment.
}
@Override
public void executionStarted(FirebirdBlob blob) throws SQLException {
}
@Override
void handleConnectionClose() throws SQLException {
// do nothing, we are in a managed environment
}
}
static class MetaDataTransactionCoordinator extends AbstractTransactionCoordinator {
private final InternalTransactionCoordinator tc;
public MetaDataTransactionCoordinator(InternalTransactionCoordinator tc) {
super(tc.connection, tc.connection.getLocalTransaction());
this.tc = tc;
}
@Override
public void ensureTransaction() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public void commit() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public void rollback() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
void handleConnectionClose() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public void executionStarted(FBStatement stmt) throws SQLException {
tc.ensureTransaction();
}
@Override
public void statementClosed(FBStatement stmt) throws SQLException {
stmt.completeStatement();
}
@Override
public void statementCompleted(FBStatement stmt, boolean success) throws SQLException {
}
@Override
public void executionCompleted(FirebirdBlob blob) throws SQLException {
}
@Override
public void executionStarted(FirebirdBlob blob) throws SQLException {
}
boolean isAutoCommit() throws SQLException {
return tc.getAutoCommit();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy