| /* |
| * 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.transaction; |
| |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import javax.naming.Context; |
| import javax.naming.InitialContext; |
| import javax.naming.NamingException; |
| |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.ExceptionHandler; |
| import org.eclipse.persistence.exceptions.TransactionException; |
| import org.eclipse.persistence.internal.sequencing.SequencingCallback; |
| import org.eclipse.persistence.internal.sequencing.SequencingCallbackFactory; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl; |
| import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.sessions.DatabaseSession; |
| import org.eclipse.persistence.sessions.ExternalTransactionController; |
| import org.eclipse.persistence.sessions.broker.SessionBroker; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: Abstract implementation of an ExternalTransactionController. |
| * <p> |
| * <b>Description</b>: This class implements the ExternalTransactionController |
| * interface. Concrete implementations of this class are responsible for performing |
| * the coordination with the external transaction manager through whatever means |
| * available to that manager. Different transaction services may do this in slightly |
| * different ways, but most common ones (JTA and JTS/OTS) will use a synchronization |
| * interface. |
| * |
| * @see AbstractSynchronizationListener |
| * @see org.eclipse.persistence.sessions.ExternalTransactionController |
| */ |
| public abstract class AbstractTransactionController implements ExternalTransactionController { |
| |
| /** Table of external transaction object keys and unit of work values */ |
| protected ConcurrentMap unitsOfWork; |
| |
| /** The session this controller is responsible for controlling */ |
| protected AbstractSession session; |
| |
| /** Generates listener instances for synchronization */ |
| protected SynchronizationListenerFactory listenerFactory; |
| |
| /** PERF: Cache the active uow in a thread local. */ |
| protected ThreadLocal activeUnitOfWorkThreadLocal; |
| |
| /** Table of external transaction object keys and sequencing listeners values. */ |
| /** Non-null only in case sequencing callbacks are used: numSessionsRequiringSequencingCallback {@literal >} 0 */ |
| protected ConcurrentMap<Object, AbstractSynchronizationListener> sequencingListeners; |
| |
| /** Table of external transaction object keys and listeners that are currently in beforeCompletion. */ |
| /** Request for a new sequencing callback may be triggered by beforeCompletion of existing listener - */ |
| /** in this case avoid creating yet another listener for sequencing but rather use the listener */ |
| /** that has initiated the request */ |
| /** Non-null only in case sequencing callbacks are used: numSessionsRequiringSequencingCallback {@literal >} 0 */ |
| protected ConcurrentMap<Object, AbstractSynchronizationListener> currentlyProcessedListeners; |
| |
| /** Indicates how many sessions require sequencing callbacks: */ |
| /** 0 - sequencing callbacks not used; */ |
| /** 1 - the session is DatabaseSession or ServerSession and requires sequencing callbacks, */ |
| /** or the session is a session broker with only one member requiring sequencing callbacks. */ |
| /** more - the session is a session broker with several members requiring sequencing callbacks. */ |
| protected int numSessionsRequiringSequencingCallback; |
| |
| /** Allow exception in before/after completion to be wrapped. */ |
| protected ExceptionHandler exceptionHandler; |
| |
| /** |
| * INTERNAL: |
| * Return a new controller. |
| */ |
| protected AbstractTransactionController() { |
| this.unitsOfWork = new ConcurrentHashMap(); |
| this.activeUnitOfWorkThreadLocal = new ThreadLocal(); |
| } |
| |
| /** |
| * Return the exception handler used to handle or wrap exceptions thrown in before/after completion. |
| */ |
| @Override |
| public ExceptionHandler getExceptionHandler() { |
| return exceptionHandler; |
| } |
| |
| /** |
| * Set an exception handler to handle or wrap exceptions thrown in before/after completion. |
| */ |
| @Override |
| public void setExceptionHandler(ExceptionHandler exceptionHandler) { |
| this.exceptionHandler = exceptionHandler; |
| } |
| |
| /** |
| * INTERNAL: |
| * Associate the given unit of work and EclipseLink session with the active external |
| * transaction. This may be done by whatever means supported by the transaction |
| * manager (i.e. through a synchronization protocol). |
| * |
| * @param unitOfWork The unit of work that is to be bound to the active transaction |
| * @param session The session ancestor of the unit of work |
| */ |
| public void bindToCurrentTransaction(UnitOfWorkImpl unitOfWork, AbstractSession session) { |
| Object status = getTransactionStatus(); |
| logTxStateTrace(unitOfWork, "TX_bind", status); |
| try { |
| Object txn = getTransaction(); |
| if (txn == null) { |
| // If no external transaction is active then start one through the uow |
| unitOfWork.beginTransaction(); |
| txn = getTransaction(); |
| } |
| |
| // If there still is no active txn then something is wrong |
| if (txn == null) { |
| throw TransactionException.externalTransactionNotActive(); |
| } |
| |
| // Create and register the synchronization listener |
| AbstractSynchronizationListener listener = getListenerFactory().newSynchronizationListener(unitOfWork, session, txn, this); |
| |
| registerSynchronization_impl(listener, txn); |
| unitOfWork.setSynchronized(true); |
| |
| } catch (Exception exception) { |
| throw TransactionException.errorBindingToExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Begin an external transaction. |
| * |
| * @param session The session for which the transaction is being begun. |
| */ |
| @Override |
| public void beginTransaction(AbstractSession session) { |
| try { |
| Object status = getTransactionStatus(); |
| logTxStateTrace(session, "TX_begin", status); |
| |
| // Make sure that we are in a state that we can actually start |
| // a transaction (e.g. ensure one is not already in progress) |
| if (canBeginTransaction_impl(status)) { |
| logTxTrace(session, "TX_beginningTxn", null); |
| beginTransaction_impl(); |
| session.setWasJTSTransactionInternallyStarted(true); |
| } |
| } catch (Exception exception) { |
| throw TransactionException.errorBeginningExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Commit the external transaction. |
| * |
| * @param session The session for which the transaction is being committed. |
| */ |
| @Override |
| public void commitTransaction(AbstractSession session) { |
| try { |
| Object status = getTransactionStatus(); |
| logTxStateTrace(session, "TX_commit", status); |
| |
| if (canCommitTransaction_impl(status)) { |
| logTxTrace(session, "TX_committingTxn", null); |
| |
| session.setWasJTSTransactionInternallyStarted(false); |
| commitTransaction_impl(); |
| } |
| } catch (Exception exception) { |
| throw TransactionException.errorCommittingExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Roll back the external transaction. |
| * |
| * @param session The session for which the transaction is being rolled back. |
| */ |
| @Override |
| public void rollbackTransaction(AbstractSession session) { |
| try { |
| Object status = getTransactionStatus(); |
| logTxStateTrace(session, "TX_rollback", status); |
| |
| session.setWasJTSTransactionInternallyStarted(false); |
| |
| // Only roll back if there is a transaction to roll back |
| if ((canRollbackTransaction_impl(status)) && (getTransaction() != null)) { |
| logTxTrace(session, "TX_rollingBackTxn", null); |
| rollbackTransaction_impl(); |
| } |
| } catch (Exception exception) { |
| throw TransactionException.errorRollingBackExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Mark the external transaction for rollback. |
| */ |
| @Override |
| public void markTransactionForRollback() { |
| try { |
| markTransactionForRollback_impl(); |
| } catch (Exception exception) { |
| throw TransactionException.errorMarkingTransactionForRollback(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the active external transaction object, or null if |
| * none is active. This may be in whatever form the transaction system uses. |
| */ |
| public Object getTransaction() { |
| try { |
| return getTransaction_impl(); |
| } catch (Exception exception) { |
| throw TransactionException.errorGettingExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return a key for the specified external transaction object. |
| * The key is just something that can be inserted into a hashtable (must support |
| * hashCode() and equals() methods). |
| */ |
| public Object getTransactionKey(Object transaction) { |
| try { |
| return getTransactionKey_impl(transaction); |
| } catch (Exception exception) { |
| throw TransactionException.errorGettingExternalTransaction(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the transaction status. This may be any type of status or value, |
| * depending upon the transaction system. |
| */ |
| public Object getTransactionStatus() { |
| try { |
| return getTransactionStatus_impl(); |
| } catch (Exception exception) { |
| throw TransactionException.errorGettingExternalTransactionStatus(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Used the EJB 3.0 to determine if a transaction is in a state where an EntityManager can |
| * be closed |
| */ |
| public boolean noTransactionOrRolledBackOrCommited() { |
| try { |
| Object status = getTransactionStatus(); |
| return canBeginTransaction_impl(status) || canMergeUnitOfWork_impl(status) || isRolledBack_impl(status); |
| } catch (Exception exception) { |
| throw TransactionException.errorGettingExternalTransactionStatus(exception); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Return true if the transaction is in the rolled back state. |
| */ |
| public abstract boolean isRolledBack_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Return true if there is a unit of work associated with the active external |
| * transaction. Return false if no transaction is current, or if no uow has |
| * been associated with the active transaction yet. |
| */ |
| public boolean hasActiveUnitOfWork() { |
| return this.lookupActiveUnitOfWork() != null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the active unit of work for the current external transaction. |
| * If no transaction is active then return null. If a transaction is active |
| * but no unit of work has been bound to it then create and return one. |
| */ |
| @Override |
| public UnitOfWorkImpl getActiveUnitOfWork() { |
| Object transaction = getTransaction(); |
| if (transaction == null) { |
| return null; |
| } |
| |
| UnitOfWorkImpl activeUnitOfWork = lookupActiveUnitOfWork(transaction); |
| if (activeUnitOfWork == null) { |
| // Txn is active but no UoW has been associated with it yet, so create one. |
| activeUnitOfWork = getSession().acquireUnitOfWork(); |
| Object transactionKey = getTransactionKey(transaction); |
| addUnitOfWork(transactionKey, activeUnitOfWork); |
| activeUnitOfWork.setTransaction(transaction); |
| this.activeUnitOfWorkThreadLocal.set(activeUnitOfWork); |
| } |
| return activeUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the unit of work associated with the active external transaction. |
| * Return null if no transaction is active, or if no uow has been associated with |
| * the active transaction yet. |
| */ |
| public UnitOfWorkImpl lookupActiveUnitOfWork() { |
| return lookupActiveUnitOfWork(getTransaction()); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the unit of work associated with the active external transaction. |
| * Return null if no transaction is active, or if no uow has been associated with |
| * the active transaction yet. |
| */ |
| public UnitOfWorkImpl lookupActiveUnitOfWork(Object transaction) { |
| if (transaction == null) { |
| return null; |
| } |
| Object transactionKey = getTransactionKey(transaction); |
| |
| // PERF: Cache the active unit of work in a thread local. |
| // This is just a heuristic, so uses == and no tx-key and direct access as extremely high throughput. |
| UnitOfWorkImpl activeUnitOfWork = (UnitOfWorkImpl)this.activeUnitOfWorkThreadLocal.get(); |
| if (activeUnitOfWork != null) { |
| if (transaction == activeUnitOfWork.getTransaction()) { |
| return activeUnitOfWork; |
| } |
| } |
| activeUnitOfWork = (UnitOfWorkImpl)getUnitsOfWork().get(transactionKey); |
| if (activeUnitOfWork != null) { |
| activeUnitOfWork.setTransaction(transaction); |
| } |
| this.activeUnitOfWorkThreadLocal.set(activeUnitOfWork); |
| return activeUnitOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add a UnitOfWork object to the Hashtable keyed on the external transaction object. |
| */ |
| public void addUnitOfWork(Object transactionKey, UnitOfWorkImpl activeUnitOfWork) { |
| this.activeUnitOfWorkThreadLocal.set(null); |
| getUnitsOfWork().put(transactionKey, activeUnitOfWork); |
| } |
| |
| /** |
| * INTERNAL: |
| * Remove the unit of work associated with the transaction passed in. |
| */ |
| public void removeUnitOfWork(Object transactionKey) { |
| if (transactionKey != null) { |
| getUnitsOfWork().remove(transactionKey); |
| } |
| this.activeUnitOfWorkThreadLocal.set(null); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the manager's session. |
| */ |
| @Override |
| public AbstractSession getSession() { |
| return session; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the manager's session. |
| */ |
| @Override |
| public void setSession(AbstractSession session) { |
| this.session = session; |
| initializeSequencingListeners(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the hashtable keyed on the external transaction objects with values |
| * that are the associated units of work. |
| */ |
| public Map getUnitsOfWork() { |
| return unitsOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the table of transactions to units of work. |
| */ |
| protected void setUnitsOfWork(ConcurrentMap unitsOfWork) { |
| this.unitsOfWork = unitsOfWork; |
| } |
| |
| /** |
| * INTERNAL: |
| * Get the factory used to generate synchronization listeners. |
| */ |
| public SynchronizationListenerFactory getListenerFactory() { |
| return listenerFactory; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the factory used to generate synchronization listeners. This should be |
| * set if a listener other than the default one is being used. |
| */ |
| public void setListenerFactory(SynchronizationListenerFactory factory) { |
| listenerFactory = factory; |
| } |
| |
| /** |
| * INTERNAL: |
| * Associate the given unit of work and EclipseLink session with the current external |
| * transaction. This method is offered only for backwards compatibility. |
| */ |
| @Override |
| public void registerSynchronizationListener(UnitOfWorkImpl uow, AbstractSession session) throws DatabaseException { |
| this.bindToCurrentTransaction(uow, session); |
| } |
| |
| /** |
| * PUBLIC: |
| * Look up a given name in JNDI. This can be used by a subclass or even an |
| * application to look up transaction artifacts that are required for the |
| * implementation. |
| * <p> |
| * The lookup assumes that it is taking place on the server side, and that the |
| * InitialContext can be used without requiring any special properties. |
| * |
| * @param jndiName The name to look up |
| * @return The object bound in JNDI to the specified name |
| * @exception TransactionException Thrown in case of lookup failure |
| */ |
| public Object jndiLookup(String jndiName) { |
| Context context = null; |
| Object jndiObject = null; |
| try { |
| context = new InitialContext(); |
| jndiObject = context.lookup(jndiName); |
| } catch (NamingException ex) { |
| throw TransactionException.jndiLookupException(jndiName, ex); |
| } finally { |
| if (context != null) { |
| try { |
| context.close(); |
| } catch (Exception ex2) { |
| /* ignore */ |
| } |
| } |
| } |
| return jndiObject; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initializes sequencing listeners. |
| * There are two methods calling this method: |
| * 1. setSession method - this could lead to initialization of sequencing listeners |
| * only if sequencing already connected (that would happen if setSession is called |
| * after session.login, which is normally not the case). |
| * 2. in the very end of connecting sequencing or adding descriptors to sequencing, |
| * after it's determined whether sequencing callbacks (and therefore listeners) |
| * will be required. |
| * |
| * In SessionBroker case each member's sequencing may call this method. |
| * Note that the number of sessions requiring callbacks may never decrease, |
| * therefore if isSequencingCallbackRequired method has returned true once, |
| * it will always return true after that (unless clearSequencingListeners method is called). |
| */ |
| @Override |
| public void initializeSequencingListeners() { |
| if(session == null) { |
| return; |
| } |
| AbstractSession parentSession = session; |
| while(parentSession.getParent() != null) { |
| parentSession = parentSession.getParent(); |
| } |
| int newNumSessionsRequiringSequencingCallback = 0; |
| // top parentSession must be DatabaseSessionImpl |
| if(parentSession.isBroker()) { |
| // it could be either SessionBroker |
| newNumSessionsRequiringSequencingCallback = ((SessionBroker)parentSession).howManySequencingCallbacks(); |
| } else { |
| // or DatabaseSessionImpl or ServerSession |
| if(((DatabaseSessionImpl)parentSession).isSequencingCallbackRequired()) { |
| newNumSessionsRequiringSequencingCallback = 1; |
| } |
| } |
| // number of required sessions in not allowed to decrease. |
| if (newNumSessionsRequiringSequencingCallback > numSessionsRequiringSequencingCallback) { |
| // keep the old map if already exists, never remove existing map |
| if (this.sequencingListeners == null) { |
| this.sequencingListeners = new ConcurrentHashMap(); |
| } |
| // keep the old map if already exists, never remove existing map |
| if (this.currentlyProcessedListeners == null) { |
| this.currentlyProcessedListeners = new ConcurrentHashMap(); |
| } |
| this.numSessionsRequiringSequencingCallback = newNumSessionsRequiringSequencingCallback; |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns sequencingCallback for the current active external transaction. |
| * DatabaseSession is passed for the sake of SessionBroker case. |
| * This method requires active external transaction. |
| */ |
| @Override |
| public SequencingCallback getActiveSequencingCallback(DatabaseSession dbSession, SequencingCallbackFactory sequencingCallbackFactory) { |
| Object transaction = getTransaction(); |
| // This method requires active external transaction. |
| if (transaction == null) { |
| throw TransactionException.externalTransactionNotActive(); |
| } |
| Object transactionKey = getTransactionKey(transaction); |
| |
| AbstractSynchronizationListener listener = sequencingListeners.get(transactionKey); |
| if(listener == null) { |
| // In case this request was triggered from beforeCompletion method of existing listener - |
| // find this listener. |
| listener = currentlyProcessedListeners.get(transactionKey); |
| if(listener == null) { |
| // Create and register the new synchronization listener with uow==session==null. |
| listener = getListenerFactory().newSynchronizationListener(null, null, transaction, this); |
| try { |
| registerSynchronization_impl(listener, transaction); |
| } catch (Exception exception) { |
| throw TransactionException.errorBindingToExternalTransaction(exception); |
| } |
| } |
| sequencingListeners.put(transactionKey, listener); |
| } |
| return listener.getSequencingCallback(dbSession, sequencingCallbackFactory); |
| } |
| |
| /** |
| * INTERNAL: |
| * Clears sequencing listeners. |
| * Called by initializeSequencingListeners and by sequencing on disconnect. |
| */ |
| @Override |
| public void clearSequencingListeners() { |
| this.numSessionsRequiringSequencingCallback = 0; |
| this.sequencingListeners = null; |
| this.currentlyProcessedListeners = null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates whether sequencing callback may be required. |
| */ |
| public boolean isSequencingCallbackRequired() { |
| return this.numSessionsRequiringSequencingCallback > 0; |
| } |
| |
| /** |
| * INTERNAL: |
| * Indicates how many sessions require sequencing callbacks. |
| */ |
| public int numSessionsRequiringSequencingCallback() { |
| return this.numSessionsRequiringSequencingCallback; |
| } |
| |
| /** |
| * INTERNAL: |
| * Clears sequencingCallbacks. |
| * Called by initializeSequencingCallbacks and by sequencing on disconnect. |
| */ |
| public void removeSequencingListener(Object transactionKey) { |
| if (transactionKey != null) { |
| sequencingListeners.remove(transactionKey); |
| } |
| } |
| |
| /* |
| * INTERNAL: |
| * Helper method to log trace statements to the transaction channel. |
| */ |
| public void logTxTrace(AbstractSession session, String msgInd, Object[] args) { |
| session.log(SessionLog.FINER, SessionLog.TRANSACTION, msgInd, args); |
| } |
| |
| /* |
| * INTERNAL: |
| * Helper method to log transaction state to the transaction channel. |
| */ |
| public void logTxStateTrace(AbstractSession session, String msgInd, Object status) { |
| if (session.shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) { |
| String statusString = statusToString_impl(status); |
| Object[] args = { statusString }; |
| session.log(SessionLog.FINER, SessionLog.TRANSACTION, msgInd, args); |
| } |
| } |
| |
| //--------------------------------------------------------------------------------- |
| // The following methods must be implemented by subclass implementations. |
| //--------------------------------------------------------------------------------- |
| |
| /** |
| * INTERNAL: |
| * Register the specified synchronization listener with the given active |
| * transaction. |
| * |
| * @param listener The synchronization listener created for this transaction |
| * @param txn The active transaction for which notification is being requested |
| */ |
| protected abstract void registerSynchronization_impl(AbstractSynchronizationListener listener, Object txn) throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Return the active external transaction for the calling thread, or null if |
| * none is currently active for this thread. |
| * |
| * @return The active transaction object, or null if no transaction is active |
| */ |
| protected abstract Object getTransaction_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Return a key for the specified external transaction object. |
| * The key is just something that can be inserted into a hashtable (must support |
| * hashCode() and equals() methods). |
| * |
| * @param transaction The transaction to which the returned key applies (may be null) |
| * @return A key for the passed in transaction, or null if no transaction specified |
| */ |
| protected abstract Object getTransactionKey_impl(Object transaction) throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Return the transaction status. This may be any type of status or value, |
| * depending upon the transaction system. |
| * |
| * @return The current transaction status |
| */ |
| protected abstract Object getTransactionStatus_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Begin an external transaction. Do this in a way appropriate to the |
| * transaction subsystem. |
| */ |
| protected abstract void beginTransaction_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Commit the external transaction. Do this in a way appropriate to the |
| * transaction subsystem. |
| */ |
| protected abstract void commitTransaction_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Roll back the external transaction. Do this in a way appropriate to the |
| * transaction subsystem. |
| */ |
| protected abstract void rollbackTransaction_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Mark the external transaction for rollback. Do this in a way appropriate to the |
| * transaction subsystem. |
| */ |
| protected abstract void markTransactionForRollback_impl() throws Exception; |
| |
| /** |
| * INTERNAL: |
| * Return true if the status indicates that a transaction can be started. This |
| * would normally mean that no transaction is currently active. |
| * The status is interpreted by the transaction subsystem. |
| * |
| * @param status The current transaction status |
| * @return true if the current state allows for a transaction to be started |
| */ |
| protected abstract boolean canBeginTransaction_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Return true if the status indicates that a transaction can be committed. This |
| * would normally mean that a transaction is currently active. |
| * The status is interpreted by the transaction subsystem. |
| * |
| * @param status The current transaction status |
| * @return true if the current state allows for a transaction to be committed |
| */ |
| protected abstract boolean canCommitTransaction_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Return true if the status indicates that a transaction can be rolled back. This |
| * would normally mean that a transaction is currently active. |
| * The status is interpreted by the transaction subsystem. |
| * |
| * @param status The current transaction status |
| * @return true if the current state allows for a transaction to be rolled back |
| */ |
| protected abstract boolean canRollbackTransaction_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Return true if the status indicates that the SQL should be issued to the db. |
| * This would normally mean that a transaction was active and not being rolled |
| * back or marked for rollback. |
| * The status is interpreted by the transaction subsystem. |
| * |
| * @param status The current transaction status |
| * @return true if the current state allows for the SQL to be sent to the database |
| */ |
| protected abstract boolean canIssueSQLToDatabase_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Return true if the status indicates that the unit of work should be merged |
| * into the shared cache. This would normally mean that the transaction was |
| * committed successfully. |
| * The status is interpreted by the transaction subsystem. |
| * |
| * @param status The current transaction status |
| * @return true if the current state dictates that the unit of work should be merged |
| */ |
| protected abstract boolean canMergeUnitOfWork_impl(Object status); |
| |
| /** |
| * INTERNAL: |
| * Convert the status to a string for tracing. |
| */ |
| protected abstract String statusToString_impl(Object status); |
| } |