/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.internal.jpa.transaction; import javax.persistence.EntityTransaction; import javax.persistence.PersistenceException; import javax.persistence.TransactionRequiredException; import javax.transaction.Synchronization; import javax.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. */ 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. */ 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 */ 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 */ public void setRollbackOnlyInternal() { if(txnController.getTransaction() != null) { txnController.markTransactionForRollback(); } } protected void throwUserTransactionException() { throw TransactionException.entityTransactionWithJTANotAllowed(); } protected void throwCheckTransactionFailedException() { throw new TransactionRequiredException(TransactionException.externalTransactionNotActive().getMessage()); } 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; } 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 javax.transaction dependencies are to be removed from JPA. See TransactionImpl try { ((Transaction)txn).registerSynchronization(new Synchronization() { public void beforeCompletion() {} 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; } } }