blob: 47699a5868064c8454f4887eacaf77037a0fc3cf [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.jpa.transaction;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.util.Vector;
import java.sql.*;
import javax.transaction.xa.XAResource;
import jakarta.transaction.*;
import org.eclipse.persistence.exceptions.TransactionException;
import org.eclipse.persistence.internal.jpa.jdbc.ConnectionProxyHandler;
import org.eclipse.persistence.internal.jpa.jdbc.DataSourceImpl;
/**
* Implementation of JTA Transaction class. The guts of the tx logic
* is contained in this class.
*
* Currently support is limited to enlisting only a single tx data source
*/
@Deprecated
public class TransactionImpl implements Transaction {
// Set by client-induced rollback marking
boolean markedForRollback;
// Used to maintain the tx status
int status;
// Collection of Synchronization listeners
Vector listeners;
// The transactional connection we use
Connection connection;
static Class<?> proxyClass = Proxy.getProxyClass(Connection.class.getClassLoader(), new Class[] { Connection.class });
// The enlisted data source
DataSourceImpl dataSource;
/***** Static constants *****/
// Cribbed from java.transaction.Status
public static final int STATUS_ACTIVE = 0;
public static final int STATUS_MARKED_ROLLBACK = 1;
public static final int STATUS_PREPARED = 2;
public static final int STATUS_COMMITTED = 3;
public static final int STATUS_ROLLEDBACK = 4;
public static final int STATUS_UNKNOWN = 5;
public static final int STATUS_NO_TRANSACTION = 6;
public static final int STATUS_PREPARING = 7;
public static final int STATUS_COMMITTING = 8;
public static final int STATUS_ROLLING_BACK = 9;
// Set this to true for debugging of afterCompletion exceptions
public static boolean DUMP_AFTER_COMPLETION_ERRORS = true;
/************************/
/***** Internal API *****/
/************************/
private void debug(String s) {
System.out.println(s);
}
/*
* Constructor invoked and new instance created on tx begin
*/
public TransactionImpl() {
markedForRollback = false;
status = STATUS_ACTIVE;
listeners = new Vector();
}
/*
* Lazily allocate the connection. This will be used
* by the data source if in a transaction.
*/
public Connection getConnection(DataSourceImpl ds, String user, String password) throws SQLException {
// We don't have a datasource connection yet, so allocate one
if (connection == null) {
debug("TxImpl - allocating new connection");
dataSource = ds;
connection = ds.internalGetConnection(user, password);
connection.setAutoCommit(false);
} else {
// We already have a connection. Make sure the data sources are the same.
if (ds.getName() != dataSource.getName()) {
throw TransactionException.multipleResourceException();
}
}
// return connection;
// Allocate and return a proxy for the connection
debug("TxImpl - creating connection proxy");
Connection proxyConnection = null;
try {
InvocationHandler handler = new ConnectionProxyHandler(connection);
proxyConnection = (Connection)proxyClass.getConstructor(new Class[] { InvocationHandler.class }).newInstance(new Object[] { handler });
} catch (Exception ex) {
throw TransactionException.internalProxyException(ex);
}
return proxyConnection;
}
/*
* Invoke afterCompletion callbacks.
* If DUMP_AFTER_COMPLETION_ERRORS flag is set then dump
* the exceptions to System.out, otherwise swallow them.
*
* NOTE: In either case it will not affect the outcome
* of the transaction.
*/
public void invokeAfterCompletion() {
// Call all of the afterCompletion callbacks
debug("TxImpl - invoking afterCompletion");
int i;
int j;
for (i = 0, j = listeners.size(); i < j; i++) {
try {
((Synchronization)listeners.elementAt(i)).afterCompletion(status);
} catch (Throwable t) {
if (DUMP_AFTER_COMPLETION_ERRORS) {
t.printStackTrace(System.out);
}
}
}
}
/*
* Rollback the transaction on the connection.
*/
public void rollbackConnection() throws SQLException {
if (connection != null) {
debug("TxImpl - rolling back connection");
status = STATUS_ROLLING_BACK;
connection.rollback();
status = STATUS_ROLLEDBACK;
}
}
/*
* Commit the transaction on the connection.
*/
public void commitConnection() throws SQLException {
if (connection != null) {
debug("TxImpl - committing connection");
status = STATUS_COMMITTING;
connection.commit();
status = STATUS_COMMITTED;
}
}
/*
* Clean up after everything is over
*/
public void cleanup() {
debug("TxImpl - cleanup");
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
}
// Ignore
connection = null;
}
status = STATUS_NO_TRANSACTION;
}
/*************************************/
/***** Supported Transaction API *****/
/*************************************/
@Override
public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException {
Exception error = null;
debug("TxImpl - commit");
// Make sure we are allowed to proceed
switch (status) {
case STATUS_ACTIVE:// This is the normal case - do nothing
break;
case STATUS_MARKED_ROLLBACK: {
// Tx was marked for rollback by the user, error
error = new ExceptionFactory().txMarkedForRollbackException();
break;
}
default:// Tx in some other state, error
throw new ExceptionFactory().invalidStateException(status);
}
// Call beforeCompletion callback.
if (error == null) {
try {
debug("TxImpl - invoking beforeCompletion");
int i;
int j;
for (i = 0, j = listeners.size(); i < j; i++) {
((Synchronization)listeners.elementAt(i)).beforeCompletion();
}
} catch (Exception ex) {
error = ex;
status = STATUS_ROLLING_BACK;
debug("TxImpl - error in beforeCompletion: " + ex);
}
}
// Now if we didn't get any errors then commit the connection
if ((error == null) && (status == STATUS_ACTIVE)) {
try {
commitConnection();
} catch (Exception ex) {
error = ex;
}
} else {
try {
rollbackConnection();
} catch (Exception ex) {
error = ex;
}
}
// Whether we were successful or not, call afterCompletion and clean up
invokeAfterCompletion();
cleanup();
// Throw any error that may have occurred at any point in the commit
if (error != null) {
throw new ExceptionFactory().newSystemException(error);
}
}
@Override
public int getStatus() throws SystemException {
return status;
}
@Override
public void registerSynchronization(Synchronization synchronization) throws RollbackException, IllegalStateException, SystemException {
debug("TxImpl - registering sync listener: " + synchronization);
listeners.add(synchronization);
}
@Override
public void rollback() throws IllegalStateException, SystemException {
Exception error = null;
debug("TxImpl - rollback");
try {
rollbackConnection();
} catch (Exception ex) {
error = ex;
}
// Call afterCompletion callback and clean up
invokeAfterCompletion();
cleanup();
// Throw any error that may have occurred while rolling back
if (error != null) {
throw new ExceptionFactory().newSystemException(error);
}
}
@Override
public void setRollbackOnly() throws IllegalStateException, SystemException {
debug("TxImpl - setRollbackOnly");
status = STATUS_MARKED_ROLLBACK;
}
/*****************************************/
/***** NOT supported Transaction API *****/
/*****************************************/
@Override
public boolean enlistResource(XAResource xaresource) throws RollbackException, IllegalStateException, SystemException {
return false;
}
@Override
public boolean delistResource(XAResource xaresource, int i) throws IllegalStateException, SystemException {
return false;
}
}