/**********************************************************************
Copyright (c) 2007 Erik Bengtson and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contributors:
...
**********************************************************************/
package org.jpox;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import org.jpox.exceptions.JPOXDataStoreException;
import org.jpox.exceptions.JPOXException;
import org.jpox.exceptions.JPOXUserException;
import org.jpox.exceptions.TransactionActiveOnBeginException;
import org.jpox.exceptions.TransactionNotActiveException;
import org.jpox.transaction.HeuristicMixedException;
import org.jpox.transaction.HeuristicRollbackException;
import org.jpox.transaction.JPOXTransactionException;
import org.jpox.transaction.RollbackException;
import org.jpox.util.JPOXLogger;
import org.jpox.util.Localiser;
import org.jpox.util.StringUtils;
/**
* Implementation of a transaction for a datastore.
* {@link org.jpox.Transaction}
*
* @version $Revision: 1.34 $
*/
public class TransactionImpl implements Transaction
{
/** Localisation of messages. */
protected static final Localiser LOCALISER=Localiser.getInstance("org.jpox.Localisation");
/** Object Manager for this transaction. */
ObjectManager om;
/** Underlying transaction */
org.jpox.transaction.Transaction tx;
/** Whether the transaction is active. */
boolean active = false;
/** Flag for whether we are currently committing. */
boolean committing;
/** Synchronisation object, for committing and rolling back. */
Synchronization sync;
/** Whether retainValues is enabled. */
protected boolean retainValues;
/** Whether restoreValues is enabled. */
protected boolean restoreValues;
/** Whether the transaction is optimistic */
protected boolean optimistic;
/** Whether non-tx read is enabled. */
protected boolean nontransactionalRead;
/** Whether non-tx write is enabled. */
protected boolean nontransactionalWrite;
/** Whether the transaction is marked for rollback. JDO 2.0 section 13.4.5 */
protected boolean rollbackOnly = false;
Set listeners = new HashSet();
Map options = new HashMap();
/** start time of the transaction */
long beginTime;
/**
* Constructor for a transaction for the specified ObjectManager.
* @param om ObjectManager.
*/
TransactionImpl(ObjectManager om)
{
this.om = om;
PersistenceConfiguration config = om.getOMFContext().getPersistenceConfiguration();
optimistic = config.getOptimistic();
retainValues = config.getRetainValues();
restoreValues = config.getRestoreValues();
nontransactionalRead = config.getNontransactionalRead();
nontransactionalWrite = config.getNontransactionalWrite();
setOption("transaction.isolation", config.getTransactionIsolation());
setOption("transaction.serializeReadObjects", config.getUseUpdateLock());
}
/**
* Method to begin the transaction.
*/
public void begin()
{
om.getOMFContext().getTransactionManager().begin(om);
tx = om.getOMFContext().getTransactionManager().getTransaction(om);
internalBegin();
}
/**
* Method to begin the transaction.
*/
protected void internalBegin()
{
if (active)
{
throw new TransactionActiveOnBeginException(this);
}
active = true;
beginTime = System.currentTimeMillis();
if (om.getOMFContext().getTransactionManager().getTransactionRuntime() != null)
{
om.getOMFContext().getTransactionManager().getTransactionRuntime().transactionStarted();
}
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015000", om, "" + optimistic));
}
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionStarted();
}
om.postBegin();
}
/**
* Method to flush the transaction.
*/
public void flush()
{
try
{
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionFlushed();
}
}
catch (Throwable ex)
{
if (ex instanceof JPOXException)
{
throw (JPOXException)ex;
}
// Wrap all other exceptions in a JPOXTransactionException
throw new JPOXTransactionException(LOCALISER.msg("015005"), ex);
}
}
/**
* Method to allow the transaction to flush any resources.
*/
public void end()
{
try
{
flush();
}
finally
{
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionEnded();
}
}
}
/**
* Method to commit the transaction.
*/
public void commit()
{
if (!isActive())
{
throw new TransactionNotActiveException();
}
// JDO 2.0 section 13.4.5 rollbackOnly functionality
// It isn't clear from the spec if we are expected to do the rollback here.
// The spec simply says that we throw an exception. This is assumed as meaning that the users code will catch
// the exception and call rollback themselves. i.e we don't need to close the DB connection or set "active" to false.
if (rollbackOnly)
{
// Throw an exception since can only exit via rollback
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015020"));
}
throw new JPOXDataStoreException(LOCALISER.msg("015020")).setFatal();
}
long startTime = System.currentTimeMillis();
boolean success = false;
boolean canComplete = true; //whether the transaction can be completed
List errors = new ArrayList();
try
{
flush();
internalPreCommit();
internalCommit();
success = true;
}
catch (RollbackException e)
{
//catch only JPOXException because user exceptions can be raised
//in Transaction.Synchronization and they should cascade up to user code
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
errors.add(e);
}
catch (HeuristicRollbackException e)
{
//catch only JPOXException because user exceptions can be raised
//in Transaction.Synchronization and they should cascade up to user code
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
errors.add(e);
}
catch (HeuristicMixedException e)
{
//catch only JPOXException because user exceptions can be raised
//in Transaction.Synchronization and they should cascade up to user code
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
errors.add(e);
}
catch (JPOXUserException e)
{
//catch only JPOXUserException
//they must be cascade up to user code and transaction is still alive
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
canComplete = false;
throw e;
}
catch (JPOXException e)
{
//catch only JPOXException because user exceptions can be raised
//in Transaction.Synchronization and they should cascade up to user code
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
errors.add(e);
}
finally
{
if (canComplete)
{
try
{
if (!success)
{
rollback();
}
else
{
internalPostCommit();
}
}
catch (Throwable e)
{
errors.add(e);
}
tx = null;
}
}
if (errors.size() > 0)
{
throw new JPOXTransactionException(LOCALISER.msg("015007"), (Throwable[])errors.toArray(new Throwable[errors.size()]));
}
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015022", (System.currentTimeMillis() - startTime)));
}
}
/**
* Method to perform any pre-commit operations like flushing to the datastore, calling the users
* "beforeCompletion", and general preparation for the commit.
*/
protected void internalPreCommit()
{
committing = true;
try
{
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015001", om));
}
if (sync != null)
{
// JDO2 $13.4.3 Allow the user to perform any updates before we do loading of fields etc
sync.beforeCompletion();
}
// Perform any pre-commit operations
om.preCommit(); // This has to be after setting "committing"
}
finally
{
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionPreCommit();
}
}
}
/**
* Internal commit, JPOX invokes it's own transaction manager implementation, if
* an external transaction manager is not used.
*/
protected void internalCommit()
{
// optimistic transactions that don't have dirty
om.getOMFContext().getTransactionManager().commit(om);
}
/**
* Method to rollback the transaction.
*/
public void rollback()
{
if (!isActive())
{
throw new TransactionNotActiveException();
}
long startTime = System.currentTimeMillis();
try
{
boolean canComplete = true; //whether the transaction can be completed
committing = true;
try
{
try
{
flush();
}
finally
{
//even if flush fails, we ignore and go ahead cleaning up and rolling back everything ahead...
try
{
internalPreRollback();
}
catch (JPOXUserException e)
{
//catch only JPOXUserException
//they must be cascade up to user code and transaction is still alive
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(StringUtils.getStringFromStackTrace(e));
}
canComplete = false;
throw e;
}
finally
{
if (canComplete)
{
internalRollback();
}
}
}
}
finally
{
try
{
if( canComplete )
{
try
{
active = false;
if (om.getOMFContext().getTransactionManager().getTransactionRuntime() != null)
{
om.getOMFContext().getTransactionManager().getTransactionRuntime().transactionRolledBack(System.currentTimeMillis()-beginTime);
}
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionRolledBack();
}
}
finally
{
listeners.clear();
rollbackOnly = false; // Reset rollbackOnly flag
tx = null;
if (sync != null)
{
sync.afterCompletion(Status.STATUS_ROLLEDBACK);
}
}
}
}
finally
{
committing = false;
}
}
}
catch (JPOXUserException e)
{
throw e;
}
catch (JPOXException e)
{
throw new JPOXDataStoreException(LOCALISER.msg("015009"), e);
}
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015023", (System.currentTimeMillis() - startTime)));
}
}
/**
* Call om.preRollback() and listeners.
*/
protected void internalPreRollback()
{
try
{
if (JPOXLogger.TRANSACTION.isDebugEnabled())
{
JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015002", om));
}
om.preRollback();
}
finally
{
TransactionEventListener[] tel =
(TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionPreRollBack();
}
}
}
/**
* Internal rollback, JPOX invokes it's own transaction manager implementation, if
* an external transaction manager is not used.
*/
protected void internalRollback()
{
org.jpox.transaction.Transaction tx = om.getOMFContext().getTransactionManager().getTransaction(om);
if (tx != null)
{
om.getOMFContext().getTransactionManager().rollback(om);
}
}
/**
* Method to perform any post-commit operations like calling the users "afterCompletion"
* and general clean up after the commit.
*/
protected void internalPostCommit()
{
try
{
active = false;
if (om.getOMFContext().getTransactionManager().getTransactionRuntime() != null)
{
om.getOMFContext().getTransactionManager().getTransactionRuntime().transactionCommitted(System.currentTimeMillis()-beginTime);
}
//closeConnection();
TransactionEventListener[] tel = (TransactionEventListener[]) listeners.toArray(new TransactionEventListener[listeners.size()]);
for (int i=0; i<tel.length; i++)
{
tel[i].transactionCommitted();
}
}
finally
{
listeners.clear();
try
{
om.postCommit();
}
finally
{
committing = false;
if (sync != null)
{
sync.afterCompletion(Status.STATUS_COMMITTED);
}
}
}
}
/**
* Accessor for whether the transaction is active.
* @return Whether the transaction is active.
**/
public boolean isActive()
{
return active;
}
/**
* Accessor for whether the transaction is comitting.
* @return Whether the transaction is committing.
**/
public boolean isCommitting()
{
return committing;
}
// ------------------------------- Accessors/Mutators ---------------------------------------
/**
* Accessor for the nontransactionalRead flag for this transaction.
* @return Whether nontransactionalRead is set.
*/
public boolean getNontransactionalRead()
{
return nontransactionalRead;
}
/**
* Accessor for the nontransactionalWrite flag for this transaction.
* @return Whether nontransactionalWrite is set.
*/
public boolean getNontransactionalWrite()
{
return nontransactionalWrite;
}
/**
* Accessor for the Optimistic setting
* @return Whether optimistic transactions are in operation.
*/
public boolean getOptimistic()
{
return optimistic;
}
/**
* Accessor for the restoreValues flag for this transaction.
* @return Whether restoreValues is set.
*/
public boolean getRestoreValues()
{
return restoreValues;
}
/**
* Accessor for the retainValues flag for this transaction.
* @return Whether retainValues is set.
*/
public boolean getRetainValues()
{
return retainValues;
}
/**
* Accessor for the "rollback only" flag.
* @return The rollback only flag
* @since 1.1
*/
public boolean getRollbackOnly()
{
return rollbackOnly;
}
/**
* Accessor for the synchronization object to be notified on transaction completion.
* @return The synchronization instance ot be notified on transaction completion.
*/
public Synchronization getSynchronization()
{
return sync;
}
/**
* Mutator for the setting of nontransactional read.
* @param nontransactionalRead Whether to allow nontransactional read operations
*/
public void setNontransactionalRead(boolean nontransactionalRead)
{
this.nontransactionalRead = nontransactionalRead;
}
/**
* Mutator for the setting of nontransactional write.
* @param nontransactionalWrite Whether to allow nontransactional write operations
*/
public synchronized void setNontransactionalWrite(boolean nontransactionalWrite)
{
this.nontransactionalWrite = nontransactionalWrite;
}
/**
* Mutator for the optimistic transaction setting.
* @param optimistic The optimistic transaction setting.
*/
public synchronized void setOptimistic(boolean optimistic)
{
this.optimistic = optimistic;
}
/**
* Mutator for the setting of restore values.
* @param restoreValues Whether to restore values at commit
*/
public synchronized void setRestoreValues(boolean restoreValues)
{
this.restoreValues = restoreValues;
}
/**
* Mutator for the setting of retain values.
* @param retainValues Whether to retain values at commit
*/
public synchronized void setRetainValues(boolean retainValues)
{
this.retainValues = retainValues;
if (retainValues)
{
nontransactionalRead = true;
}
}
/**
* Mutator for the "rollback only" flag. Sets the transaction as for rollback only.
* @since 1.1
*/
public void setRollbackOnly()
{
// Only apply to active transactions
if (active)
{
rollbackOnly = true;
}
}
/**
* Mutator for the synchronization object to be notified on transaction completion.
* @param sync The synchronization object to be notified on transaction completion
*/
public synchronized void setSynchronization(Synchronization sync)
{
this.sync = sync;
}
public void addTransactionEventListener(TransactionEventListener listener)
{
this.listeners.add(listener);
}
public void removeTransactionEventListener(TransactionEventListener listener)
{
this.listeners.remove(listener);
}
public Map getOptions()
{
return options;
}
public void setOption(String option, int value)
{
options.put(option, new Integer(value));
}
public void setOption(String option, boolean value)
{
options.put(option, new Boolean(value));
}
public void setOption(String option, String value)
{
options.put(option, value);
}
}
|