blob: 8f029162d822fbbc9763a929d4660ba4a9e8c695 [file] [log] [blame]
/*
* Copyright (c) 1998, 2020 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 jakarta.persistence.EntityTransaction;
import jakarta.persistence.PersistenceException;
import jakarta.persistence.TransactionRequiredException;
import jakarta.transaction.Synchronization;
import jakarta.transaction.Transaction;
import org.eclipse.persistence.internal.jpa.EntityManagerImpl;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.transaction.AbstractTransactionController;
import org.eclipse.persistence.exceptions.TransactionException;
/**
* INTERNAL:
* JTA transaction wrapper.
* Allows the EntityManager to transparently use JTA vs local transactions.
*/
public class JTATransactionWrapper extends TransactionWrapperImpl implements TransactionWrapper{
//This is a quick reference for the external Transaction Controller
protected AbstractTransactionController txnController;
//flag that allows lazy initialization of the persistence context while still registering
// with the transaction for after completion.
private boolean isJoined = false;
public JTATransactionWrapper(EntityManagerImpl entityManager) {
super(entityManager);
this.txnController = (AbstractTransactionController)entityManager.getDatabaseSession().getExternalTransactionController();
}
/**
* INTERNAL:
* This method will be used to check for a transaction and throws exception if none exists.
* If this method returns without exception then a transaction exists.
* This method must be called before accessing the localUOW.
*/
@Override
public Object checkForTransaction(boolean validateExistence){
Object transaction = this.txnController.getTransaction();
if (validateExistence && (transaction == null)){
throwCheckTransactionFailedException();
}
return transaction;
}
/**
* INTERNAL:
* Internal clear the underlying data structures that this transaction owns.
*/
@Override
public void clear(){
this.localUOW.release();
this.localUOW = null;
}
/**
* An ENtityTransaction cannot be used at the same time as a JTA transaction
* throw an exception
*/
@Override
public EntityTransaction getTransaction(){
throw new IllegalStateException(TransactionException.entityTransactionWithJTANotAllowed().getMessage());
}
/**
* INTERNAL:
* Mark the current transaction so that the only possible
* outcome of the transaction is for the transaction to be
* rolled back.
* This is an internal method and if the txn is not active will do nothing
*/
@Override
public void setRollbackOnlyInternal() {
if(txnController.getTransaction() != null) {
txnController.markTransactionForRollback();
}
}
protected void throwUserTransactionException() {
throw TransactionException.entityTransactionWithJTANotAllowed();
}
protected void throwCheckTransactionFailedException() {
throw new TransactionRequiredException(TransactionException.externalTransactionNotActive().getMessage());
}
@Override
public boolean isJoinedToTransaction(UnitOfWorkImpl uow) {
if (this.entityManager.hasActivePersistenceContext()) {
return uow.getParent().hasExternalTransactionController() && uow.isSynchronized();
}
//We don't need to check if there is an active trans, as we now register with it when join is called
//until we get an active context
return isJoined;
}
@Override
public void registerIfRequired(UnitOfWorkImpl uow){
//EM already validated that there is a JTA transaction.
if (this.entityManager.hasActivePersistenceContext()) {
//we have a context initialized, so have it register with the transaction
uow.registerWithTransactionIfRequired();
} else if (!isJoined) {
// JPA 3.2.4
// In general, a persistence context will be synchronized to the database as described below. However, a
// persistence context of type SynchronizationType.UNSYNCHRONIZED or an application-managed
// persistence context that has been created outside the scope of the current transaction will only be
// synchronized to the database if it has been joined to the current transaction by the application's use of
// the EntityManager joinTransaction method.
// ..
// If there is no transaction active
// or if the persistence context has not been joined to the current transaction, the persistence provider must
// not flush to the database.
// if (syncType == null {
// App managed, so we need to start the active persistence Context. Or do we?
// } else if (syncType.equals(SynchronizationType.SYNCHRONIZED)) {
// need to ensure we do not init the context until we need too
// } else {
// this is unsynchronized, so we need to start the active persistence Context. Or do we?
// }
Object txn = checkForTransaction(true);
// duplicating what is done in
// TransactionController.registerSynchronizationListener(this, this.parent);
// This will need to change if jakarta.transaction dependencies are to be removed from JPA. See TransactionImpl
try {
((Transaction)txn).registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {}
@Override
public void afterCompletion(int status) {
//let the wrapper know the listener is no longer registered to an active transaction
isJoined = false;
}
});
} catch (Exception e) {
throw new PersistenceException(TransactionException.errorBindingToExternalTransaction(e).getMessage(), e);
}
isJoined = true;
}
}
}