| /* |
| * 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.transaction; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import jakarta.transaction.Synchronization; |
| |
| import org.eclipse.persistence.exceptions.EclipseLinkException; |
| import org.eclipse.persistence.exceptions.TransactionException; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.internal.sequencing.SequencingCallback; |
| import org.eclipse.persistence.internal.sequencing.SequencingCallbackFactory; |
| import org.eclipse.persistence.logging.*; |
| import org.eclipse.persistence.sessions.broker.SessionBroker; |
| import org.eclipse.persistence.sessions.SessionProfiler; |
| import org.eclipse.persistence.sessions.DatabaseSession; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Abstract Synchronization Listener class |
| * |
| * <b>Description</b>: This abstract class is paired with the |
| * AbstractTransactionController class. It contains most of the implementation |
| * logic to handle callback notifications from an external transaction |
| * manager to ensure consistency between the global transaction and the |
| * EclipseLink unit of work. It does not assume any particular specification |
| * or interface, but can be called by any implementation subclass. |
| * |
| * @see AbstractTransactionController |
| */ |
| public abstract class AbstractSynchronizationListener implements Synchronization { |
| |
| /** |
| * The external txn controller that is intimate with the transaction manager |
| * and knows how to do things like rolling back transactions, etc. |
| */ |
| protected AbstractTransactionController controller; |
| |
| /** |
| * The parent of the uow. |
| */ |
| protected AbstractSession session; |
| |
| /** |
| * The unit of work associated with the global txn that this listener is |
| * bound to. |
| * Note that unitOfWork is null in case it's a purely sequencing listener. |
| */ |
| protected UnitOfWorkImpl unitOfWork; |
| |
| /** |
| * The global transaction object. |
| */ |
| protected Object transaction; |
| |
| /** |
| * The global transaction key. |
| */ |
| protected Object transactionKey; |
| |
| /** |
| * sequencingCallback used in case listener has a single callback. |
| */ |
| protected SequencingCallback sequencingCallback; |
| |
| /** |
| * sequencingCallbackMap used in case listener has more than one callback: |
| * SessionBroker with at least two members requiring callbacks. |
| */ |
| protected Map<DatabaseSession, SequencingCallback> sequencingCallbackMap; |
| |
| /** |
| * INTERNAL: |
| */ |
| public AbstractSynchronizationListener() { |
| super(); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| protected AbstractSynchronizationListener(UnitOfWorkImpl unitOfWork, AbstractSession session, Object transaction, AbstractTransactionController controller) { |
| this.session = session; |
| this.unitOfWork = unitOfWork; |
| this.transaction = transaction; |
| this.controller = controller; |
| this.transactionKey = controller.getTransactionKey(transaction); |
| } |
| |
| /** |
| * INTERNAL: |
| * This method performs the logic that occurs at transaction |
| * completion time. This includes issuing the SQL, etc. |
| * This method executes within the transaction context of the caller of |
| * transaction.commit(), or in the case of container-managed transactions, |
| * in the context of the method for which the Container started the transaction. |
| */ |
| @Override |
| public void beforeCompletion() { |
| UnitOfWorkImpl uow = getUnitOfWork(); |
| // it's a purely sequencing listener - nothing to do in beforeCompletion. |
| if(unitOfWork == null) { |
| return; |
| } |
| try { |
| Object status = getTransactionController().getTransactionStatus(); |
| getTransactionController().logTxStateTrace(uow, "TX_beforeCompletion", status); |
| //CR# 3452053 |
| session.startOperationProfile(SessionProfiler.JtsBeforeCompletion); |
| |
| // In case jts transaction was internally started but completed |
| // directly by TransactionManager this flag is still set to true. |
| getSession().setWasJTSTransactionInternallyStarted(false); |
| |
| // If the uow is not active then somebody somewhere messed up |
| if (!uow.isActive()) { |
| throw TransactionException.inactiveUnitOfWork(uow); |
| } |
| |
| // Bail out if we don't think we should actually issue the SQL |
| if (!getTransactionController().canIssueSQLToDatabase_impl(status)) { |
| // Must force concurrency mgrs active thread if in nested transaction |
| if (getSession().isInTransaction()) { |
| getSession().getTransactionMutex().setActiveThread(Thread.currentThread()); |
| if(getUnitOfWork().wasTransactionBegunPrematurely()) { |
| getUnitOfWork().setWasTransactionBegunPrematurely(false); |
| } |
| getSession().rollbackTransaction(); |
| } |
| getSession().releaseJTSConnection(); |
| return; |
| } |
| |
| // Must force concurrency mgrs active thread if in nested transaction |
| if (getSession().isInTransaction()) { |
| getSession().getTransactionMutex().setActiveThread(Thread.currentThread()); |
| } |
| |
| // If sequencing callback for this transaction will be required |
| // in case it doesn't already exist it will be created on this very listener |
| // avoiding adding more listeners while processing a listener. |
| if(getTransactionController().isSequencingCallbackRequired()) { |
| getTransactionController().currentlyProcessedListeners.put(getTransactionKey(), this); |
| } |
| |
| // Send the SQL to the DB |
| uow.issueSQLbeforeCompletion(); |
| |
| // Fix up our merge state in the unit of work and the session |
| uow.setPendingMerge(); |
| |
| } catch (RuntimeException exception) { |
| // Log the exception if it has not already been logged, or is a non-EclipseLink exception |
| if (!(exception instanceof EclipseLinkException && ((EclipseLinkException)exception).hasBeenLogged())) { |
| uow.logThrowable(SessionLog.WARNING, SessionLog.TRANSACTION, exception); |
| } |
| |
| // Handle the exception according to transaction manager requirements |
| handleException(exception); |
| } finally { |
| if(getTransactionController().isSequencingCallbackRequired()) { |
| getTransactionController().currentlyProcessedListeners.remove(getTransactionKey()); |
| } |
| getSession().releaseJTSConnection(); |
| session.endOperationProfile(SessionProfiler.JtsBeforeCompletion); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * The method performs the logic that should be executed after the transaction |
| * has been completed. The status passed in indicates whether the transaction |
| * was committed or rolled back. This status flag may be different for different |
| * implementations. |
| * This method executes without a transaction context. |
| * |
| * @param status The status code of the transaction completion. |
| */ |
| public void afterCompletion(Object status) { |
| UnitOfWorkImpl uow = getUnitOfWork(); |
| // it's a purely sequencing listener - call sequencing callback if the transaction has committed. |
| if(uow == null) { |
| if(getTransactionController().isSequencingCallbackRequired()) { |
| if(getTransactionController().canMergeUnitOfWork_impl(status)) { |
| callSequencingCallback(); |
| } |
| } |
| } else { |
| try { |
| // Log the fact that we got invoked |
| getTransactionController().logTxStateTrace(uow, "TX_afterCompletion", status); |
| //Cr#3452053 |
| this.session.startOperationProfile(SessionProfiler.JtsAfterCompletion); |
| // The uow should still be active even in rollback case |
| if (!uow.isActive()) { |
| throw TransactionException.inactiveUnitOfWork(uow); |
| } |
| |
| // Only do merge if txn was committed |
| if (getTransactionController().canMergeUnitOfWork_impl(status)) { |
| if(getTransactionController().isSequencingCallbackRequired()) { |
| callSequencingCallback(); |
| } |
| if (uow.isMergePending()) { |
| // uow in PENDING_MERGE state, merge clones |
| uow.mergeClonesAfterCompletion(); |
| } |
| } else { |
| // call this method again because there may have been no beforeCompletion call |
| // if case transaction is to be rolled back. |
| getSession().releaseJTSConnection(); |
| uow.afterExternalTransactionRollback(); |
| } |
| |
| // Clean up by releasing the uow and client session |
| if (uow.shouldResumeUnitOfWorkOnTransactionCompletion() && getTransactionController().canMergeUnitOfWork_impl(status)){ |
| uow.synchronizeAndResume(); |
| uow.setSynchronized(false); |
| } else { |
| uow.release(); |
| // Release the session explicitly |
| if (getSession().isClientSession() || (getSession().isSessionBroker() && ((SessionBroker)getSession()).isClientSessionBroker())) { |
| getSession().release(); |
| } |
| } |
| } catch (RuntimeException exception) { |
| // Log the exception if it has not already been logged, or is a non-EclipseLink exception |
| if (!(exception instanceof EclipseLinkException && ((EclipseLinkException)exception).hasBeenLogged())) { |
| uow.logThrowable(SessionLog.WARNING, SessionLog.TRANSACTION, exception); |
| } |
| handleException(exception); |
| } finally { |
| getTransactionController().removeUnitOfWork(getTransactionKey()); |
| this.session.endOperationProfile(SessionProfiler.JtsAfterCompletion); |
| setUnitOfWork(null); |
| setSession(null); |
| } |
| } |
| if(getTransactionController().isSequencingCallbackRequired()) { |
| getTransactionController().removeSequencingListener(getTransactionKey()); |
| this.sequencingCallback = null; |
| this.sequencingCallbackMap = null; |
| } |
| setTransaction(null); |
| setTransactionKey(null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Do the appropriate thing for when an exception occurs during SQL issuance. |
| * The default thing to do is to simply mark the transaction to be rolled back, |
| * for those transaction managers that support this, and rethrow the exception. |
| * We hope that the exception will do the trick for those that do not allow |
| * marking rollback. |
| * |
| * This method may optionally be overridden by concrete subclass implementations. |
| * Different transaction manager vendors may have different reactions to exceptions |
| * that get signalled during the commit phase of synchronization. |
| */ |
| public void handleException(RuntimeException exception) { |
| // Don't do this just yet, since some may not be able to handle it |
| // getTransactionController().markTransactionForRollback(); |
| if (this.controller.getExceptionHandler() != null) { |
| this.controller.getExceptionHandler().handleException(exception); |
| return; |
| } |
| throw exception; |
| } |
| |
| protected AbstractTransactionController getTransactionController() { |
| return controller; |
| } |
| |
| protected void setTransactionController(AbstractTransactionController newController) { |
| controller = newController; |
| } |
| |
| protected Object getTransaction() { |
| return transaction; |
| } |
| |
| protected void setTransaction(Object transaction) { |
| this.transaction = transaction; |
| } |
| |
| protected Object getTransactionKey() { |
| return transactionKey; |
| } |
| |
| protected void setTransactionKey(Object transactionKey) { |
| this.transactionKey = transactionKey; |
| } |
| |
| protected AbstractSession getSession() { |
| return session; |
| } |
| |
| protected void setSession(AbstractSession session) { |
| this.session = session; |
| } |
| |
| protected UnitOfWorkImpl getUnitOfWork() { |
| return unitOfWork; |
| } |
| |
| protected void setUnitOfWork(UnitOfWorkImpl unitOfWork) { |
| this.unitOfWork = unitOfWork; |
| } |
| |
| protected void callSequencingCallback() { |
| if(sequencingCallback != null) { |
| sequencingCallback.afterCommit(null); |
| } else if (sequencingCallbackMap != null) { |
| Iterator<SequencingCallback> itCallback = sequencingCallbackMap.values().iterator(); |
| while(itCallback.hasNext()) { |
| itCallback.next().afterCommit(null); |
| } |
| } |
| } |
| |
| /** |
| * Return sequencingCallback corresponding to the passed session. |
| */ |
| public SequencingCallback getSequencingCallback(DatabaseSession dbSession, SequencingCallbackFactory sequencingCallbackFactory) { |
| if(getTransactionController().numSessionsRequiringSequencingCallback() == 1) { |
| if(sequencingCallback == null) { |
| sequencingCallback = sequencingCallbackFactory.createSequencingCallback(); |
| } |
| return sequencingCallback; |
| } else if(getTransactionController().numSessionsRequiringSequencingCallback() > 1) { |
| SequencingCallback callback = null; |
| if(sequencingCallbackMap == null) { |
| sequencingCallbackMap = new HashMap(getTransactionController().numSessionsRequiringSequencingCallback()); |
| } else { |
| callback = sequencingCallbackMap.get(dbSession); |
| } |
| if(callback == null) { |
| callback = sequencingCallbackFactory.createSequencingCallback(); |
| sequencingCallbackMap.put(dbSession, callback); |
| } |
| return callback; |
| } else { |
| // should never happen. |
| return null; |
| } |
| } |
| } |