/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: TransactionContext.java,v 1.15 2005/02/07 20:48:22 wguttmn Exp $
*/
package org.exolab.castor.persist;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Hashtable;
import java.util.Enumeration;
import javax.transaction.Status;
import javax.transaction.xa.Xid;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.DbMetaInfo;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.TransactionAbortedException;
import org.exolab.castor.jdo.ObjectNotFoundException;
import org.exolab.castor.jdo.ObjectDeletedException;
import org.exolab.castor.jdo.LockNotGrantedException;
import org.exolab.castor.jdo.QueryException;
import org.exolab.castor.jdo.DuplicateIdentityException;
import org.exolab.castor.jdo.ClassNotPersistenceCapableException;
import org.exolab.castor.jdo.ObjectNotPersistentException;
import org.exolab.castor.jdo.ObjectModifiedException;
import org.exolab.castor.persist.spi.InstanceFactory;
import org.exolab.castor.persist.spi.CallbackInterceptor;
import org.exolab.castor.mapping.AccessMode;
import org.exolab.castor.persist.spi.PersistenceQuery;
import org.exolab.castor.util.Messages;
/**
* A transaction context is required in order to perform operations
* against the database. The transaction context is mapped to an
* API transaction or an XA transaction. The only way to begin a
* new transaction is through the creation of a new transaction context.
* A transaction context is created from an implementation class directly
* or through {@link XAResourceImpl}.
*
* @author <a href="arkin@intalio.com">Assaf Arkin</a>
* @version $Revision: 1.15 $ $Date: 2005/02/07 20:48:22 $
*/
public abstract class TransactionContext
{
/**
* IMPLEMENTATION NOTES:
*
* An object is considered persistent only if it was queried or created
* within the context of this transaction. An object is not persistent
* if it was queried or created by another transactions. An object is
* not persistent if it was queried with read-only access.
*
* A read lock is implicitly obtained on any object that is queried,
* and a write lock on any object that is queried in exclusive mode,
* created or deleted in this transaction. The lock can be upgraded to
* a write lock.
*
* The validity of locks is dependent on the underlying persistence
* engine the transaction mode. Without persistence engine locks
* provide a strong locking mechanism, preventing objects from being
* deleted or modified in one transaction while another transaction
* is looking at them. With a persistent engine in exclusive mode, locks
* exhibit the same behavior. With a persistent engine in read/write
* mode or a persistent engine that does not support locking (e.g. LDAP)
* an object may be deleted or modified concurrently through a direct
* access mechanism.
*/
/**
* The <a href="http://jakarta.apache.org/commons/logging/">Jakarta
* Commons Logging</a> instance used for all logging.
*/
private static Log _log = LogFactory.getFactory().getInstance (TransactionContext.class);
/*
* An object is consider as Transitent if it is not partcipate in
* the current transaction.
*/
public static int OBJECT_STATE_TRANSIENT = 0;
/*
* This state is not yet used by castor
*/
public static int OBJECT_STATE_HOLLOW = 1;
/*
* An object is consider as READ_ONLY if and only if it is load
* thru this transactionContext as read only object
*/
public static int OBJECT_STATE_READ_ONLY = 2;
/*
* An object is consider as Persistent if the object is loaded
* thru this transactionContext
*/
public static int OBJECT_STATE_PERSISTENT = 3;
/*
* An object is consider as PERSISTENT_NEW if the object is
* created in this transactionContext
*/
public static int OBJECT_STATE_PERSISTENT_NEW = 4;
/*
* An object is consider as PERSISTENT_DELETED if the object is
* loaded thru this transactionContext and deleted
*/
public static int OBJECT_STATE_PERSISTENT_DELETED = 5;
/*
* An object is consider as PERSISTENT_NEW_DELETED if the object is
* created in this transactionContext and deleted
*/
public static int OBJECT_STATE_PERSISTENT_NEW_DELETED = 6;
/**
* Set while transaction is waiting for a lock.
*
* @see #getWaitOnLock
* @see ObjectLock
*/
private ObjectLock _waitOnLock;
/**
* Collection of objects accessed during this transaction.
* Actually the vector contains instances of {@link ObjectEntry}.
* @see #addObjectEntry
*/
private final Vector _objects = new Vector();
/**
* A list of all the engines associated with this transaction and
* all the OIDs loaded from them. The persistence engine is used
* as the key, the value is a hashtable. In that hashtable {@link OID}
* is the key and {@link ObjectEntry} is the value.
*/
private final Hashtable _engineOids = new Hashtable();
/**
* Collection of objects loaded in read-only mode during this transaction.
* They are not persistent anymore, but we have to keep them in order
* to provide uniqueness of objects. E.g., if one depenent object
* contains reference to another one, the latter shouldn't be loaded twice.
* In the hashtable {@link OID} is the key and {@link ObjectEntry}
* is the value.
* @see #addObjectEntry
*/
private final Hashtable _readOnlyObjects = new Hashtable();
/**
* The transaction status. See {@link Status} for list of valid values.
*/
private int _status;
/**
* The timeout waiting to acquire a new lock. Specified in seconds.
*/
private int _lockTimeout = 30;
/**
* The Xid of this transaction is generated from an XA resource.
*/
private final Xid _xid;
/**
* The timeout of this transaction. Specified in seconds.
*/
private int _txTimeout = 30;
/**
* FIFO linked list of objects deleted in this transaction.
*/
private ObjectEntry _deletedList;
/**
* The database to which this transaction belongs.
*/
private Database _db;
/**
* True if user prefer all reachable object to be stored automatically.
* False if user want only dependent object to be stored.
*/
private boolean _autoStore;
/**
* The default callback interceptor for the data object in
* this transaction.
*/
private CallbackInterceptor _callback;
/**
* The instance factory to that creates new instances of data object
*/
private InstanceFactory _instanceFactory;
/**
* Creating
*
*/
private boolean _creating;
/**
* A list of listeners which will be informed about various transaction
* states.
*/
private ArrayList _synchronizeList = new ArrayList();
/**
* Create a new transaction context. This method is used by the
* explicit transaction model.
*/
public TransactionContext( Database db )
{
_xid = null;
_status = -1;
_db = db;
}
/*
* These constructors should be removed. It is no longer
* used from within the Castor code.
public TransactionContext( Database db, Xid xid )
{
_xid = xid;
_status = -1;
_db = db;
}
/*
* These constructors should be removed. It is no longer
* used from within the Castor code.
public TransactionContext( Database db, Xid xid , int status)
{
_xid = xid;
_status = status;
_db = db;
}
*/
/*
* This constructor is used in the case of
* global transaction.
*/
public TransactionContext( Database db, javax.transaction.Transaction transaction )
throws javax.transaction.SystemException
{
_xid = null;
_db = db;
//this should be removed when we move
//to separate implementations of TransactionContext
//for explicit demarcation and global transactions.
//In the latter case, I think we should just wrap
//the global transaction and pass getStatus() through to the
//wrapped transaction
_status = transaction.getStatus();
}
/**
* Register a listener which wants to synchronize its state
* to the state of the transaction.
*/
public void addTxSynchronizable( TxSynchronizable synchronizable ) {
_synchronizeList.add( synchronizable );
}
/**
* @see #addTxSynchronizable
*/
public void removeTxSynchronizable( TxSynchronizable synchronizable ) {
_synchronizeList.remove( synchronizable );
}
/**
* Inform all registered listeners that the transaction was committed.
*/
protected void txcommitted() {
for (int i=0;i<_synchronizeList.size();i++) {
TxSynchronizable sync = (TxSynchronizable)_synchronizeList.get(i);
try {
sync.committed(this);
}
catch (Exception e) {
}
}
}
/**
* Inform all registered listeners that the transaction was rolled back.
*/
protected void txrolledback() {
for (int i=0;i<_synchronizeList.size();i++) {
TxSynchronizable sync = (TxSynchronizable)_synchronizeList.get(i);
try {
sync.rolledback(this);
}
catch (Exception e) {
}
}
}
/**
* Enable or disable autoStore.
* If enabled, all new objects, which is reachable from other
* object that is quried, loaded, created in the transaction,
* will be created when the transaction is committed.
*/
public void setAutoStore( boolean autoStore ) {
_autoStore = autoStore;
}
/**
* Test if autoStore options is enabled or not.
*/
public boolean isAutoStore() {
return _autoStore;
}
/**
* Overrides the default callback interceptor by a custom
* interceptor for this database source.
* <p>
* The interceptor is a callback that notifies data objects
* on persistent state events.
* <p>
* If callback interceptor is not overrided, events will be
* sent to data object that implements the org.exolab.castor.jdo.Persistent
* interface.
*
* @param callback The callback interceptor, null if disabled
*/
public void setCallback( CallbackInterceptor callback ) {
_callback = callback;
}
/**
* Overrides the default instance factory by a custom one for
* this database source.
* <p>
* The factory is used to obatain a new instance of data object
* when it is needed during loading.
*
* @param instanceFactory The instanceFactory to be used, null if disable
*/
public void setInstanceFactory( InstanceFactory factory ) {
_instanceFactory = factory;
}
public PersistenceInfoGroup getScope() {
return _db.getScope();
}
/**
* Sets the timeout of this transaction. The timeout is specified
* in seconds.
*/
public void setTransactionTimeout( int timeout ) {
_txTimeout = timeout;
}
/**
* Returns the timeout of this transaction. The timeout is specified
* in seconds.
*/
public int getTransactionTimeout()
{
return _txTimeout;
}
/**
* Returns the timeout waiting to acquire a lock. The timeout is
* specified in seconds.
*/
public int getLockTimeout()
{
return _lockTimeout;
}
/**
* Sets the timeout waiting to acquire a lock. The timeout is
* specified in seconds.
*/
public void setLockTimeout( int timeout )
{
_lockTimeout = ( timeout >= 0 ? timeout : 0 );
}
/**
* Sets the status of the current transaction to STATUS_ATIVE
*/
public void setStatusActive() {
_status = Status.STATUS_ACTIVE;
}
/**
* The derived class must implement this method and return an open
* connection for the specified engine. The connection should be
* created only one for a given engine in the same transaction.
*
* @param engine The persistence engine
* @return An open connection
* @throws PersistenceException An error occured talking to the
* persistence engine
*/
public abstract Object getConnection( LockEngine engine )
throws PersistenceException;
/**
* Returns meta-data related to the RDBMS used.
* @param engine LockEngine instance used.
* @return A DbMetaInfo instance describing var. features of the underlying RDBMS.
*/
public abstract DbMetaInfo getConnectionInfo(LockEngine engine)
throws PersistenceException;
/**
* The derived class must implement this method and commit all the
* connections used in this transaction. If the transaction could
* not commit fully or partially, this method will throw an {@link
* TransactionAbortedException}, causing a rollback to occur as
* the next step.
*
* @throws TransactionAbortedException The transaction could not
* commit fully or partially and should be rolled back
*/
protected abstract void commitConnections()
throws TransactionAbortedException;
/**
* The derived class must implement this method and close all the
* connections used in this transaction.
* @throws TransactionAbortedException The transaction could not
* close all the connections
*/
protected abstract void closeConnections()
throws TransactionAbortedException;
/**
* The derived class must implement this method and rollback all
* the connections used in this transaction. The connections may
* be closed, as they will not be reused in this transaction.
* This operation is guaranteed to succeed.
*/
protected abstract void rollbackConnections();
public synchronized Object fetch( LockEngine engine, ClassMolder molder,
Object identity, AccessMode suggestedAccessMode )
throws ObjectNotFoundException, LockNotGrantedException, PersistenceException {
ObjectEntry entry = null;
OID oid;
AccessMode accessMode;
if ( identity == null )
throw new PersistenceException("Identities can't be null!");
oid = new OID( engine, molder, identity );
accessMode = molder.getAccessMode( suggestedAccessMode );
if ( accessMode == AccessMode.ReadOnly )
entry = getReadOnlyObjectEntry( oid );
if ( entry == null )
entry = getObjectEntry( engine, oid );
if ( entry != null ) {
// If the object has been loaded in this transaction from a
// different engine this is an error. If the object has been
// deleted in this transaction, it cannot be re-loaded. If the
// object has been created in this transaction, it cannot be
// re-loaded but no error is reported.
if ( entry.engine != engine )
throw new PersistenceException( Messages.format("persist.multipleLoad", molder.getName(), identity ) );
if ( entry.deleted ) {
return null;
}
// ssa, multi classloader feature
// ssa, FIXME : are the two following statement equivalent ?
// if ( ! molder.getJavaClass().isAssignableFrom( entry.object.getClass() ) )
if ( ! molder.isAssignableFrom( entry.object.getClass() ) )
throw new PersistenceException( Messages.format("persist.typeMismatch", molder.getName(), identity ) );
if ( entry.created )
return entry.object;
if ( ( accessMode == AccessMode.Exclusive ||
accessMode == AccessMode.DbLocked ) && ! entry.oid.isDbLock() ) {
// If we are in exclusive mode and object has not been
// loaded in exclusive mode before, then we have a
// problem. We cannot return an object that is not
// synchronized with the database, but we cannot
// synchronize a live object.
throw new PersistenceException( Messages.format("persist.lockConflict", molder.getName(), identity ) );
}
return entry.object;
}
return null;
}
/**
* Load an object for use within the transaction. Multiple access
* to the same object within the transaction will return the same
* object instance (except for read-only access).
* <p>
* This method is similar to {@link #fetch} except that it will
* load the object only once within a transaction and always
* return the same instance.
* <p>
* If the object is loaded for read-only then no lock is acquired
* and updates to the object are not reflected at commit time.
* If the object is loaded for read-write then a read lock is
* acquired (unless timeout or deadlock detected) and the object
* is stored at commit time. The object is then considered persistent
* and may be deleted or upgraded to write lock. If the object is
* loaded for exclusive access then a write lock is acquired and the
* object is synchronized with the persistent copy.
* <p>
* Attempting to load the object twice in the same transaction, once
* with exclusive lock and once with read-write lock will result in
* an exception.
*
* @param engine The persistence engine
* @param molder The class persistence molder
* @param identity The object's identity
* @param objectToBeLoaded The object to fetch (single instance per transaction)
* @param suggestedAccessMode The access mode (see {@link AccessMode})
* the values in persistent storage
* @throws LockNotGrantedException Timeout or deadlock occured
* attempting to acquire lock on object
* @throws ObjectNotFoundException The object was not found in
* persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @return object being loaded
*/
public synchronized Object load( LockEngine engine, ClassMolder molder,
Object identity, Object objectToBeLoaded,
AccessMode suggestedAccessMode )
throws ObjectNotFoundException, LockNotGrantedException, PersistenceException {
return load( engine, molder, identity, objectToBeLoaded, suggestedAccessMode, null );
}
/**
* Load an object for use within the transaction. Multiple access
* to the same object within the transaction will return the same
* object instance (except for read-only access).
* <p>
* This method work the same as {@link #load(LockEngine,ClassMolder,Object,Object,AccessMode)},
* except a QueryResults can be specified.
* <p>
* @param engine The persistence engine
* @param molder The class persistence molder
* @param identity The object's identity
* @param objectToBeLoaded The object to fetch (single instance per transaction)
* @param suggestedAccessMode The access mode (see {@link AccessMode})
* the values in persistent storage
* @param results The QueryResult that the data to be loaded from.
* @throws LockNotGrantedException Timeout or deadlock occured
* attempting to acquire lock on object
* @throws ObjectNotFoundException The object was not found in
* persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @return object being loaded
*/
public synchronized Object load( LockEngine engine, ClassMolder molder,
Object identity, Object objectToBeLoaded, AccessMode suggestedAccessMode,
QueryResults results )
throws ObjectNotFoundException, LockNotGrantedException, PersistenceException {
ObjectEntry entry = null;
Object object = null;
OID oid;
if ( identity == null )
throw new PersistenceException("Identities can't be null!");
oid = new OID( engine, molder, identity );
if ( objectToBeLoaded != null
&& !molder.getJavaClass( _db.getClassLoader() ).isAssignableFrom( objectToBeLoaded.getClass() ) )
throw new PersistenceException( Messages.format("persist.typeMismatch", molder.getName(), objectToBeLoaded.getClass() ) );
AccessMode accessMode = molder.getAccessMode( suggestedAccessMode );
if ( accessMode == AccessMode.ReadOnly )
entry = getReadOnlyObjectEntry( oid );
if ( entry == null )
entry = getObjectEntry( engine, oid );
if ( entry != null ) {
// If the object has been loaded, but the instance sugguested to
// be loaded into is not the same as the loaded instance,
// error is reported.
if ( objectToBeLoaded != null && objectToBeLoaded != entry.object )
throw new PersistenceException( Messages.format("persist.multipleLoad", molder.getName(), identity ) );
// If the object has been loaded in this transaction from a
// different engine this is an error. If the object has been
// deleted in this transaction, it cannot be re-loaded. If the
// object has been created in this transaction, it cannot be
// re-loaded but no error is reported.
if ( entry.engine != engine )
throw new PersistenceException( Messages.format("persist.multipleLoad", molder.getName(), identity ) );
if ( entry.deleted )
throw new ObjectNotFoundException( "Object is deleted" + molder.getName() + identity );
// ssa, multi classloader feature
// ssa, FIXME : Are the two following statements equivalent ?
// if ( ! molder.getJavaClass().isAssignableFrom( entry.object.getClass() ) )
if ( ! molder.getJavaClass( _db.getClassLoader() ).isAssignableFrom( entry.object.getClass() ) )
throw new PersistenceException( Messages.format("persist.typeMismatch", molder.getName(), entry.object.getClass() ) );
if ( entry.created )
return entry.object;
if ( ( accessMode == AccessMode.Exclusive ||
accessMode == AccessMode.DbLocked ) && ! entry.oid.isDbLock() ) {
throw new PersistenceException( Messages.format("persist.lockConflict", molder.getName(), identity ) );
}
return entry.object;
}
// Load (or reload) the object through the persistence engine with the
// requested lock. This might report failure (object no longer exists),
// hold until a suitable lock is granted (or fail to grant), or
// report error with the persistence engine.
try {
// ssa, multi classloader feature
// ssa, FIXME : No better way to do that ?
//object = molder.newInstance();
if ( objectToBeLoaded != null )
object = objectToBeLoaded;
else {
if ( _instanceFactory != null ) {
object = _instanceFactory.newInstance( molder.getName(), _db.getClassLoader() );
} else {
object = molder.newInstance( _db.getClassLoader() );
}
}
entry = addObjectEntry( oid, object );
oid = engine.load( this, oid, object, suggestedAccessMode, _lockTimeout, results );
// rehash the object entry, because oid might have changed!
entry = rehash( object, oid );
} catch ( ObjectNotFoundException except ) {
removeObjectEntry( object );
throw except;
} catch ( LockNotGrantedException except ) {
// | it is an important one......
removeObjectEntry( object );
throw except;
} catch ( ClassNotPersistenceCapableException except ) {
removeObjectEntry( object );
// maybe we should remove it, when castor become stable
throw new PersistenceException( Messages.format("persist.nested",except) );
} catch (InstantiationException e) {
removeObjectEntry( object );
throw new PersistenceException(e.getMessage(), e);
} catch (IllegalAccessException e) {
removeObjectEntry( object );
throw new PersistenceException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
removeObjectEntry( object );
throw new PersistenceException(e.getMessage(), e);
}
// Need to copy the contents of this object from the cached
// copy and deal with it based on the transaction semantics.
// If the mode is read-only we release the lock and forget about
// it in the contents of this transaction. Otherwise we record
// the object in this transaction.
try {
if ( _callback != null ) {
_callback.using( object, _db );
_callback.loaded( object, toDatabaseAccessMode( accessMode ) );
} else if ( molder.getCallback() != null ) {
molder.getCallback().using( object, _db );
molder.getCallback().loaded( object, toDatabaseAccessMode( accessMode ) );
}
} catch ( Exception except ) {
release( object );
//removeObjectEntry( object );
//engine.forgetObject( this, oid );
//if ( molder.getCallback() != null )
// molder.getCallback().releasing( object, false );
//if ( except instanceof PersistenceException )
// throw (PersistenceException) except;
throw new PersistenceException( Messages.format("persist.nested", except) );
}
if ( accessMode == AccessMode.ReadOnly ) {
makeReadOnly( object );
}
return object;
}
/**
* Perform a query using the query mechanism and in the specified
* access mode. The query is performed in this transaction, and
* the returned query results can only be used while this
* transaction is open. It is assumed that the query mechanism is
* compatible with the persistence engine.
*
* @param engine The persistence engine
* @param query A query against the persistence engine
* @param accessMode The access mode
* @return A query result iterator
* @throws QueryException An invalid query
* @throws PersistenceException An error reported by the
* persistence engine
*/
public synchronized QueryResults query( LockEngine engine,
PersistenceQuery query, AccessMode accessMode, boolean scrollable )
throws QueryException, PersistenceException {
// Need to execute query at this point. This will result in a
// new result set from the query, or an exception.
query.execute( getConnection( engine ), accessMode, scrollable);
return new QueryResults( this, engine, query, accessMode, _db );
}
public synchronized QueryResults query( LockEngine engine,
PersistenceQuery query, AccessMode accessMode )
throws QueryException, PersistenceException
{
return query( engine, query, accessMode, false );
}
/**
* Walk a data object tree starting from the specified object, and
* mark all object to be created.
* <p>
* @param engine The persistence engine
* @param object The object to persist
* @return The object's OID
* @throws DuplicateIdentityException An object with this identity
* already exists in persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @throws ClassNotPersistenceCapableException The class is not
* persistent capable
*/
public synchronized void markCreate( LockEngine engine, ClassMolder molder,
Object object, OID depended )
throws DuplicateIdentityException, PersistenceException {
OID oid;
Object identity;
ObjectEntry entry;
if ( object == null )
throw new NullPointerException();
// Make sure the object has not beed persisted in this transaction.
identity = molder.getIdentity( this, object );
entry = getObjectEntry( object );
// if autoStore is specified, we relieve user life a little bit here
// so that if an object create automatically and user create it
// again, it won't receive exception
if ( _autoStore && entry != null && entry.object == object )
return;
if ( entry != null && !entry.deleted )
throw new PersistenceException( Messages.format("persist.objectAlreadyPersistent", object.getClass().getName(), entry.oid.getIdentity() ) );
// Create the object. This can only happen once for each object in
// all transactions running on the same engine, so after creation
// add a new entry for this object and use this object as the view
oid = new OID( engine, molder, depended, identity );
entry = getObjectEntry( engine, oid );
if ( identity != null && entry != null ) {
if ( ! entry.deleted || entry.object != object )
throw new DuplicateIdentityException( Messages.format( "persist.duplicateIdentity", object.getClass().getName(), identity ) );
else {
// If the object was already deleted in this transaction,
// just undelete it.
// Remove the entry from a FIFO linked list of deleted entries.
if ( _deletedList != null ) {
ObjectEntry deleted;
if ( _deletedList == entry )
_deletedList = entry.nextDeleted;
else {
deleted = _deletedList;
while ( deleted.nextDeleted != null && deleted.nextDeleted != entry )
deleted = deleted.nextDeleted;
if ( deleted.nextDeleted == entry )
deleted.nextDeleted = entry.nextDeleted;
else
throw new PersistenceException( Messages.format("persist.deletedNotFound", identity) );
}
}
}
}
try {
entry = addObjectEntry( oid, object );
entry.creating = true;
if ( _callback != null ) {
_callback.creating( entry.object, _db );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().creating( entry.object, _db );
}
engine.markCreate( this, oid, object );
} catch ( LockNotGrantedException lneg ) {
// yip: do we need LockNotGrantedException, or should we
// removed them?
removeObjectEntry( object );
throw new DuplicateIdentityException("");
} catch ( PersistenceException pe ) {
removeObjectEntry( object );
throw pe;
} catch ( Exception e ) {
removeObjectEntry( object );
throw new PersistenceException(Messages.format("persist.nested",e));
}
}
/**
* Creates a new object in persistent storage. The object will
* be persisted only if the transaction commits. If an identity
* is provided then duplicate identity check happens in this
* method, if no identity is provided then duplicate identity
* check occurs when the transaction completes and the object
* is not visible in this transaction.
*
* @param engine The persistence engine
* @param molder The molder of the creating class
* @param object The object to persist
* @param depended The master object's OID if exist
* @return The object's OID
* @throws DuplicateIdentityException An object with this identity
* already exists in persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @throws ClassNotPersistenceCapableException The class is not
* persistent capable
*/
public synchronized void create( LockEngine engine, ClassMolder molder,
Object object, OID depended )
throws DuplicateIdentityException, PersistenceException {
boolean walk = false;
if ( !_creating ) {
walk = true;
_creating = true;
}
// markCreate will walk the object tree starting from the specified
// object and mark all the object to be created.
try {
markCreate( engine, molder, object, depended );
} catch (DuplicateIdentityException e) {
if (walk) _creating = false;
throw e;
} catch (PersistenceException e) {
if (walk) _creating = false;
throw e;
}
// if exception throws in markCreate state, we simply let the exception
// to pass to upper level.
// After the marking is done, we will actually create the object.
// However, because some objects contains foreign key are key
// generated, such object should be created after some other.
// We iterate all object and creating object according the priority.
int priority = 0;
int nextPrior = 0;
for ( boolean nextCreate=walk; nextCreate; priority=nextPrior ) {
Enumeration enumeration = _objects.elements();
nextCreate = false;
while ( enumeration.hasMoreElements() ) {
ObjectEntry enumEntry = (ObjectEntry) enumeration.nextElement();
try {
if ( enumEntry.creating && !enumEntry.deleted ) {
if ( enumEntry.molder.getPriority() <= priority ) {
// Must perform creation after object is recorded in transaction
// to prevent circular references.
OID oid = enumEntry.engine.create
( this, enumEntry.oid, enumEntry.object );
if ( oid.getIdentity() == null )
throw new IllegalStateException("oid.getIdentity() is null after create!");
// rehash the object entry, in case of oid changed
ObjectEntry entry = rehash( enumEntry.object, oid );
entry.created = true;
entry.creating = false;
// yip: we got problem here. We would run into
// problem if user try to create inside the
// callback. We must think a workaround to
// fixes this problem for backward compatiblity
if ( _callback != null ) {
_callback.using( entry.object, _db );
_callback.created( entry.object );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().using( entry.object, _db );
enumEntry.molder.getCallback().created( entry.object );
}
} else {
nextPrior = Math.min( priority+1, enumEntry.molder.getPriority() );
nextCreate = true;
}
}
} catch ( Exception except ) {
// _log.error ("Problem", except);
if ( _callback != null ) {
_callback.releasing( enumEntry.object, false );
} else if ( molder.getCallback() != null ) {
molder.getCallback().releasing( enumEntry.object, false );
}
removeObjectEntry( enumEntry.object );
if ( except instanceof DuplicateIdentityException )
throw (DuplicateIdentityException) except;
if ( except instanceof PersistenceException )
throw (PersistenceException) except;
throw new PersistenceException( Messages.format("persist.nested",except) );
}
}
}
if ( walk ) {
// after we create the objects, some cache may invalid because the
// relation are cached on both side. So, we updateCache if it is
// marked to be update from the markCreate state
Enumeration enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
ObjectEntry enumEntry = (ObjectEntry) enumeration.nextElement();
if ( enumEntry.created && enumEntry.updateCacheNeeded ) {
enumEntry.engine.updateCache( this, enumEntry.oid, enumEntry.object );
enumEntry.updateCacheNeeded = false;
}
}
_creating = false;
}
}
/**
* Update a new object in persistent storage and returns the
* object's OID. The object will be persisted only if the
* transaction commits. If an identity is provided then duplicate
* identity check happens in this method, if no identity is
* provided then duplicate identity check occurs when the
* transaction completes and the object is not visible in this
* transaction.
* <p>
* Update will also mark object to be created if the TIMESTAMP
* equals to NO_TIMESTAMP.
*
* @param engine The persistence engine
* @param molder The object's molder
* @param object The object to persist
* @param depended The master objects of the specified object to be
* created if exisit
* @return true if the object is marked to be created
* @throws DuplicateIdentityException An object with this identity
* already exists in persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @throws ClassNotPersistenceCapableException The class is not
* persistent capable
* @throws ObjectModifiedException Dirty checking mechanism may immediately
* report that the object was modified in the database during the long
* transaction.
*/
public boolean markUpdate( LockEngine engine, ClassMolder molder, Object object, OID depended )
throws DuplicateIdentityException, ObjectModifiedException,
ClassNotPersistenceCapableException, PersistenceException {
Object identity;
OID oid;
ObjectEntry entry;
if ( object == null )
throw new NullPointerException();
identity = molder.getActualIdentity( this, object );
if ( molder.isDefaultIdentity( identity ) )
identity = null;
oid = new OID( engine, molder, depended, identity );
entry = getObjectEntry( engine, oid );
if ( _autoStore && entry != null && entry.object == object )
return false;
if ( entry != null ) {
if ( entry.deleted )
throw new ObjectDeletedException( Messages.format("persist.objectDeleted", object.getClass(), identity ) );
else
throw new DuplicateIdentityException( "update object which is already in the transaction" );
}
try {
entry = addObjectEntry( oid, object );
entry.creating = entry.engine.update( this, oid, object, null, 0 );
// yip: there is one issue here. Because object might be marked
// to be created in the update process. However, we have no easy
// way to fire jdoCreating() events from here.
} catch ( DuplicateIdentityException lneg ) {
removeObjectEntry( object );
throw lneg;
} catch ( PersistenceException pe ) {
removeObjectEntry( object );
throw pe;
} catch ( Exception e ) {
removeObjectEntry( object );
throw new PersistenceException(Messages.format("persist.nested",e));
}
if ( !entry.creating ) {
try {
if ( _callback != null ) {
_callback.using( object, _db );
_callback.updated( object );
} else if ( molder.getCallback() != null ) {
molder.getCallback().using( object, _db );
molder.getCallback().updated( object );
}
} catch ( Exception except ) {
release( object );
if ( except instanceof PersistenceException )
throw (PersistenceException) except;
throw new PersistenceException( except.getMessage(), except );
}
return false;
} else {
return true;
}
}
/**
* Update a new object in persistent storage and returns the
* object's OID. The object will be persisted only if the
* transaction commits. If an identity is provided then duplicate
* identity check happens in this method, if no identity is
* provided then duplicate identity check occurs when the
* transaction completes and the object is not visible in this
* transaction.
* <p>
* Update will also mark object to be created if the TIMESTAMP
* equals to NO_TIMESTAMP.
*
* @param engine The persistence engine
* @param molder The object's molder
* @param object The object to persist
* @param depended The master objects of the specified object to be
* created if exisit
* @throws DuplicateIdentityException An object with this identity
* already exists in persistent storage
* @throws PersistenceException An error reported by the
* persistence engine
* @throws ClassNotPersistenceCapableException The class is not
* persistent capable
* @throws ObjectModifiedException Dirty checking mechanism may immediately
* report that the object was modified in the database during the long
* transaction.
*/
public synchronized void update( LockEngine engine, ClassMolder molder, Object object, OID depended )
throws DuplicateIdentityException, ObjectModifiedException,
ClassNotPersistenceCapableException, PersistenceException {
boolean walk = false;
if ( !_creating ) {
walk = true;
_creating = true;
}
try {
markUpdate( engine, molder, object, depended );
} catch (DuplicateIdentityException e) {
if (walk) _creating = false;
throw e;
} catch (PersistenceException e) {
if (walk) _creating = false;
throw e;
}
// markUpdate will actually update object loaded/create from preious
// transaction, or it might marked some object to be created.
// However, because some objects contains foreign key are key
// generated, such object should be created after some other.
// We iterate all object and creating object according the priority.
int priority = 0;
int nextPrior = 0;
for ( boolean nextCreate=walk; nextCreate; priority=nextPrior ) {
Enumeration enumeration = _objects.elements();
nextCreate = false;
while ( enumeration.hasMoreElements() ) {
ObjectEntry enumEntry = (ObjectEntry) enumeration.nextElement();
try {
if ( enumEntry.creating && !enumEntry.deleted ) {
if ( enumEntry.molder.getPriority() <= priority ) {
if ( _callback != null ) {
_callback.creating( enumEntry.object, _db );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().creating( enumEntry.object, _db );
}
// Must perform creation after object is recorded in transaction
// to prevent circular references.
OID oid = enumEntry.engine.create
( this, enumEntry.oid, enumEntry.object );
if ( oid.getIdentity() == null )
throw new IllegalStateException("oid.getIdentity() is null after create!");
// rehash the object entry, in case of oid changed
ObjectEntry entry = rehash( enumEntry.object, oid );
entry.created = true;
entry.creating = false;
if ( _callback != null ) {
_callback.using( entry.object, _db );
_callback.created( entry.object );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().using( entry.object, _db );
enumEntry.molder.getCallback().created( entry.object );
}
} else {
nextPrior = Math.min( priority+1, enumEntry.molder.getPriority() );
nextCreate = true;
}
}
} catch ( Exception except ) {
if ( _callback != null ) {
_callback.releasing( enumEntry.object, false );
} else if ( molder.getCallback() != null ) {
molder.getCallback().releasing( enumEntry.object, false );
}
removeObjectEntry( enumEntry.object );
if ( except instanceof DuplicateIdentityException )
throw (DuplicateIdentityException) except;
if ( except instanceof PersistenceException )
throw (PersistenceException) except;
except.printStackTrace();
throw new PersistenceException( Messages.format("persist.nested",except) );
}
}
}
if ( walk ) {
// after we create the objects, some cache may invalid because the
// relation are cached on both side. So, we updateCache if it is
// marked to be update from the markCreate state
Enumeration enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
ObjectEntry enumEntry = (ObjectEntry) enumeration.nextElement();
if ( enumEntry.created && enumEntry.updateCacheNeeded ) {
enumEntry.engine.updateCache( this, enumEntry.oid, enumEntry.object );
enumEntry.updateCacheNeeded = false;
}
}
_creating = false;
}
}
/**
* Deletes the object from persistent storage. The deletion will
* take effect only if the transaction is committed, but the
* object is no longer viewable for the current transaction and
* locks for access from other transactions will block until this
* transaction completes. A write lock is acquired in order to
* assure the object can be deleted.
*
* @param object The object to delete from persistent storage
* @throws ObjectNotPersistentException The object has not been
* queried or created in this transaction
* @throws LockNotGrantedException Timeout or deadlock occured
* attempting to acquire lock on object
* @throws PersistenceException An error reported by the
* persistence engine
*/
public synchronized void delete( Object object )
throws ObjectNotPersistentException, LockNotGrantedException, PersistenceException {
ObjectEntry entry;
if ( object == null )
throw new PersistenceException("Object to be deleted is null!");
// Get the entry for this object, if it does not exist
// the object has never been persisted in this transaction
entry = getObjectEntry( object );
if ( entry == null )
throw new ObjectNotPersistentException( Messages.format("persist.objectNotPersistent", object.getClass().getName()) );
// Cannot delete same object twice
if ( entry.deleted )
throw new ObjectDeletedException( Messages.format("persist.objectDeleted", object.getClass().getName(), entry.oid.getIdentity() ) );
try {
if ( _callback != null ) {
_callback.removing( entry.object );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().removing( entry.object );
}
} catch ( Exception except ) {
throw new PersistenceException( Messages.format("persist.nested",except) );
}
// Must acquire a write lock on the object in order to delete it,
// prevents object form being deleted while someone else is
// looking at it.
try {
entry.deleted = true;
entry.engine.softLock( this, entry.oid, _lockTimeout );
// Mark object as deleted. This will prevent it from being viewed
// in this transaction and will handle it properly at commit time.
// The write lock will prevent it from being viewed in another
// transaction.
entry.engine.markDelete( this, entry.oid, object, _lockTimeout );
// Add the entry to a FIFO linked list of deleted entries.
if ( _deletedList == null )
_deletedList = entry;
else {
ObjectEntry deleted;
deleted = _deletedList;
while ( deleted.nextDeleted != null )
deleted = deleted.nextDeleted;
deleted.nextDeleted = entry;
}
try {
if ( _callback != null ) {
_callback.removed( entry.object );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().removed( entry.object );
}
} catch ( Exception except ) {
throw new PersistenceException( Messages.format("persist.nested",except) );
}
} catch ( ObjectDeletedException except ) {
// Object has been deleted outside this transaction,
// forget about it
removeObjectEntry( object );
}
}
/**
* Acquire a write lock on the object. Read locks are implicitly
* available when the object is queried. A write lock is only
* granted for objects that are created or deleted or for objects
* loaded in exclusive mode - this method can obtain such a lock
* explicitly. If the object already has a write lock in this
* transaction or a read lock in this transaction but no read lock
* in any other transaction, a write lock is obtained. If this
* object has a read lock in any other transaction this method
* will block until the other transaction will release its lock.
* If the specified timeout has elapsed or a deadlock has been
* detected, an exception will be thrown but the current lock will
* be retained.
*
* @param object The object to lock
* @param timeout Timeout waiting to acquire lock, specified in
* seconds, zero for no waiting, negative to use the default
* timeout for this transaction
* @throws ObjectNotPersistentException The object has not been
* queried or created in this transaction
* @throws LockNotGrantedException Timeout or deadlock occured
* attempting to acquire lock on object
* @throws PersistenceException An error reported by the
* persistence engine
*/
public synchronized void writeLock( Object object, int timeout )
throws ObjectNotPersistentException, LockNotGrantedException, PersistenceException {
ObjectEntry entry;
if ( object == null )
throw new PersistenceException("Object to acquire lock is null!");
// Get the entry for this object, if it does not exist
// the object has never been persisted in this transaction
entry = getObjectEntry( object );
if ( entry == null )
throw new ObjectNotPersistentException( Messages.format("persist.objectNotPersistent", object.getClass().getName()) );
if ( entry.deleted )
throw new ObjectDeletedException( Messages.format("persist.objectDeleted", object.getClass(), entry.oid.getIdentity() ) );
try {
entry.engine.writeLock( this, entry.oid, timeout );
} catch ( ObjectDeletedException except ) {
// Object has been deleted outside this transaction,
// forget about it
removeObjectEntry( object );
throw new ObjectNotPersistentException( Messages.format("persist.objectNotPersistent", object.getClass().getName()) );
} catch ( LockNotGrantedException except ) {
// Can't get lock, but may still keep running
throw except;
}
}
public synchronized void markModified( Object object, boolean updatePersist, boolean updateCache ) {
ObjectEntry entry;
entry = getObjectEntry( object );
if ( entry != null ) {
if ( updatePersist )
entry.updatePersistNeeded = true;
if ( updateCache )
entry.updateCacheNeeded = true;
if ( updatePersist || updateCache ) {
// | really need to schedule lock on updateCache object.
// because it's possible that preStore already called
// and, updateLock must be called in prepare state, but not
// commited state.
//entry.engine.softLock( this, entry.oid, _lockTimeout );
}
} else {
// report or ignore?
}
}
/**
* Acquire a write lock on the object. Read locks are implicitly
* available when the object is queried. A write lock is only
* granted for objects that are created or deleted or for objects
* loaded in exclusive mode - this method can obtain such a lock
* explicitly. If the object already has a write lock in this
* transaction or a read lock in this transaction but no read lock
* in any other transaction, a write lock is obtained. If this
* object has a read lock in any other transaction this method
* will block until the other transaction will release its lock.
* If the specified timeout has elapsed or a deadlock has been
* detected, an exception will be thrown but the current lock will
* be retained.
*
* @param object The object to lock
* @param timeout Timeout waiting to acquire lock, specified in
* seconds, zero for no waiting, negative to use the default
* timeout for this transaction
* @throws ObjectNotPersistentException The object has not been
* queried or created in this transaction
* @throws LockNotGrantedException Timeout or deadlock occured
* attempting to acquire lock on object
*/
public synchronized void softLock( Object object, int timeout )
throws LockNotGrantedException, ObjectNotPersistentException {
ObjectEntry entry;
if ( object == null )
throw new ObjectNotPersistentException("Object to acquire lock is null!");
// Get the entry for this object, if it does not exist
// the object has never been persisted in this transaction
entry = getObjectEntry( object );
if ( entry == null )
throw new ObjectNotPersistentException( Messages.format( "persist.objectNotPersistent", object.getClass().getName()) );
if ( entry.deleted )
throw new ObjectDeletedException( Messages.format("persist.objectDeleted", object.getClass().getName(), entry.oid.getIdentity() ) );
try {
entry.engine.softLock( this, entry.oid, timeout );
} catch ( ObjectDeletedWaitingForLockException except ) {
// Object has been deleted outside this transaction,
// forget about it
removeObjectEntry( object );
throw except;
} catch ( LockNotGrantedException except ) {
// Can't get lock, but may still keep running
throw except;
}
}
/**
* Releases the lock granted on the object. The object is removed
* from this transaction and will not participate in transaction
* commit/abort. Any changes done to the object are lost.
*
* @param object The object to release the lock
* @throws ObjectNotPersistentException The object was not queried
* or created in this transaction
* @throws PersistenceException An error occured talking to the
* persistence engine
*/
public synchronized void release( Object object )
throws ObjectNotPersistentException, PersistenceException {
ObjectEntry entry;
if ( object == null )
throw new PersistenceException("Object to release lock is null!");
// Get the entry for this object, if it does not exist
// the object has never been persisted in this transaction
entry = getObjectEntry( object );
if ( entry == null || entry.deleted )
throw new ObjectNotPersistentException( Messages.format("persist.objectNotPersistent", object.getClass().getName().getClass()) );
// Release the lock, forget about the object in this transaction
entry.engine.releaseLock( this, entry.oid );
removeObjectEntry( object );
if ( _callback != null ) {
_callback.releasing( object, false );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().releasing( object, false );
}
}
/**
* Prepares the transaction prior to committing it. Indicates
* whether the transaction is read-only (i.e. no modified objects),
* can commit, or an error has occured and the transaction must
* be rolled back. This method performs actual storage into the
* persistence storage. Multiple calls to this method can be done,
* and do not release locks, allowing <tt>checkpoint</tt> to
* occur.
*
* @return True if the transaction can commit, false if the
* transaction is read only
* @throws IllegalStateException Method called if transaction is
* not in the proper state to perform this operation
* @throws TransactionAbortedException The transaction has been
* aborted due to inconsistency, duplicate object identity, error
* with the persistence engine or any other reason
*/
public synchronized boolean prepare()
throws TransactionAbortedException
{
Vector todo;
Vector done;
Enumeration enumeration;
ObjectEntry entry;
if ( _status == Status.STATUS_MARKED_ROLLBACK )
throw new TransactionAbortedException( "persist.markedRollback" );
if ( _status != Status.STATUS_ACTIVE )
throw new IllegalStateException( Messages.message( "persist.noTransaction" ) );
try {
// No objects in this transaction -- this is a read only transaction
// Put it into the try block to close connections
if ( _objects.size() == 0 ) {
_status = Status.STATUS_PREPARED;
return false;
}
done = new Vector();
while ( _objects.size() != done.size() ) {
todo = new Vector();
enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( ! done.contains( entry ) ) {
todo.addElement( entry );
}
}
enumeration = todo.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( !entry.deleted && !entry.creating ) {
ClassMolder molder;
Object identities;
OID oid;
oid = entry.engine.preStore( this, entry.oid, entry.object, _lockTimeout );
if ( oid != null ) {
entry.oid = oid;
entry.updateCacheNeeded = true;
}
}
done.addElement( entry );
}
}
// preStore will actually walk all existing object and it might marked
// some object to be created (and to be removed).
// Because some objects contains foreign key are key generated, such
// object should be created after some other. We iterate all objects and
// creating object according the priority.
int priority = 0;
int nextPrior = 0;
for ( boolean nextCreate=true; nextCreate; priority=nextPrior ) {
enumeration = _objects.elements();
nextCreate = false;
while ( enumeration.hasMoreElements() ) {
ObjectEntry enumEntry = (ObjectEntry) enumeration.nextElement();
try {
if ( enumEntry.creating && !enumEntry.deleted && !enumEntry.created ) {
if ( enumEntry.molder.getPriority() <= priority ) {
if ( _callback != null ) {
_callback.creating( enumEntry.object, _db );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().creating( enumEntry.object, _db );
}
// Must perform creation after object is recorded in transaction
// to prevent circular references.
OID oid = enumEntry.engine.create
( this, enumEntry.oid, enumEntry.object );
if ( oid.getIdentity() == null )
throw new IllegalStateException("oid.getIdentity() is null after create!");
// rehash the object entry, in case of oid changed
entry = rehash( enumEntry.object, oid );
entry.created = true;
// | entry.updateCacheNeeded = true;
if ( _callback != null ) {
_callback.using( entry.object, _db );
_callback.created( entry.object );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().using( entry.object, _db );
enumEntry.molder.getCallback().created( entry.object );
}
} else {
nextPrior = Math.min( priority+1, enumEntry.molder.getPriority() );
nextCreate = true;
}
}
} catch ( Exception except ) {
if ( _callback != null ) {
_callback.releasing( enumEntry.object, false );
} else if ( enumEntry.molder.getCallback() != null ) {
enumEntry.molder.getCallback().releasing( enumEntry.object, false );
}
removeObjectEntry( enumEntry.object );
if ( except instanceof DuplicateIdentityException )
throw (DuplicateIdentityException) except;
if ( except instanceof PersistenceException )
throw (PersistenceException) except;
throw new PersistenceException( Messages.format("persist.nested",except) );
}
}
}
// Process all modified objects
enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( !entry.deleted && !entry.creating && entry.updatePersistNeeded )
entry.engine.store( this, entry.oid, entry.object );
if ( !entry.deleted && !entry.creating && entry.updateCacheNeeded )
entry.engine.softLock( this, entry.oid, _lockTimeout );
// do the callback
if ( !entry.deleted && _callback != null ) {
try {
_callback.storing( entry.object, entry.updateCacheNeeded );
} catch ( Exception except ) {
throw new TransactionAbortedException(
Messages.format("persist.nested", except), except );
}
} else if ( !entry.deleted && entry.molder.getCallback() != null ) {
try {
entry.molder.getCallback().storing( entry.object, entry.updateCacheNeeded );
// updatePersistNeeded implies updateCacheNeeded
} catch ( Exception except ) {
throw new TransactionAbortedException(
Messages.format("persist.nested", except), except );
}
}
}
_status = Status.STATUS_PREPARING;
// patch for bug 973 Dependent order deletion problem
// Search maximum priority in _deletedList
entry = _deletedList;
int maxPriority = 0;
while ( entry != null ) {
maxPriority = Math.max(maxPriority, entry.molder.getPriority());
entry = entry.nextDeleted;
}
// Process all deleted objects last in FIFO order.
// Take priority into account
for (int i = maxPriority; i >= 0;i--) {
entry = _deletedList;
while ( entry != null ) {
if (entry.molder.getPriority() == i) {
entry.engine.delete( this, entry.oid );
}
entry = entry.nextDeleted;
}
}
_deletedList = null;
_status = Status.STATUS_PREPARED;
return true;
} catch ( Exception except ) {
_status = Status.STATUS_MARKED_ROLLBACK;
if ( except instanceof TransactionAbortedException )
throw (TransactionAbortedException) except;
// Any error is reported as transaction aborted
throw new TransactionAbortedException( Messages.format("persist.nested", except), except );
}
}
/**
* Commits all changes and closes the transaction releasing all
* locks on all objects. All objects are now transient. Must be
* called after a call to {@link #prepare} has returned successfully.
*
* @throws TransactionAbortedException The transaction has been
* aborted due to inconsistency, duplicate object identity, error
* with the persistence engine or any other reason
* @throws IllegalStateException This method has been called
* without calling {@link #prepare} first
*/
public synchronized void commit()
throws TransactionAbortedException
{
Enumeration enumeration;
ObjectEntry entry;
// Never commit transaction that has been marked for rollback
if ( _status == Status.STATUS_MARKED_ROLLBACK )
throw new TransactionAbortedException( "persist.markedRollback" );
if ( _status != Status.STATUS_PREPARED )
throw new IllegalStateException( Messages.message( "persist.missingPrepare" ) );
try {
_status = Status.STATUS_COMMITTING;
// Go through all the connections opened in this transaction,
// commit and close them one by one.
commitConnections();
} catch ( Exception except ) {
// Any error that happens, we're going to rollback the transaction.
_status = Status.STATUS_MARKED_ROLLBACK;
throw new TransactionAbortedException( Messages.format("persist.nested", except), except );
}
// Assuming all went well in the connection department,
// no deadlocks, etc. clean all the transaction locks with
// regards to the persistence engine.
enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( entry.deleted ) {
// Object has been deleted inside transaction,
// engine must forget about it.
entry.engine.forgetObject( this, entry.oid );
entry.molder.setFieldsNull( entry.object );
} else {
// Object has been created/accessed inside the
// transaction, release its lock.
if ( entry.updateCacheNeeded ) {
entry.engine.updateCache( this, entry.oid, entry.object );
}
entry.engine.releaseLock( this, entry.oid );
}
if ( _callback != null ) {
_callback.releasing( entry.object, true );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().releasing( entry.object, true );
}
}
// Call txcommited() before objects are removed to allow
// TxSynchronizable to iterate through the objects.
txcommitted();
// Forget about all the objects in this transaction,
// and mark it as completed.
_objects.removeAllElements();
_engineOids.clear();
_readOnlyObjects.clear();
_status = Status.STATUS_COMMITTED;
}
/**
* Expose an enumeration of the commited object entries to allow
* TxSynchronizable to iterate through the objects.
*
* @return Enumeration of commited object entries.
*/
public Enumeration getObjectEntries(){
return _objects.elements();
}
/*
* Rolls back all changes and closes the transaction releasing all
* locks on all objects. All objects are now transient, if they were
* queried in this transaction. This method may be called at any point
* during the transaction.
*
* @throws IllegalStateException Method called if transaction is
* not in the proper state to perform this operation
*/
public synchronized void rollback()
{
Enumeration enumeration;
ObjectEntry entry;
if ( _status != Status.STATUS_ACTIVE && _status != Status.STATUS_PREPARED &&
_status != Status.STATUS_MARKED_ROLLBACK )
throw new IllegalStateException( Messages.message( "persist.noTransaction" ) );
// Go through all the connections opened in this transaction,
// rollback and close them one by one.
rollbackConnections();
// un-delete object first
enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( entry.deleted )
entry.deleted = false;
}
// Clean the transaction locks with regards to the
// database engine
enumeration = _objects.elements();
while ( enumeration.hasMoreElements() ) {
entry = (ObjectEntry) enumeration.nextElement();
try {
if ( entry.creating ) {
} else if ( entry.created ) {
// Object has been created in this transaction,
// it no longer exists, forget about it in the engine.
entry.engine.revertObject( this, entry.oid, entry.object );
entry.engine.forgetObject( this, entry.oid );
} else {
// Object has been queried (possibly) deleted in this
// transaction and release the lock.
//if ( entry.updateCacheNeeded || entry.updatePersistNeeded )
entry.engine.revertObject( this, entry.oid, entry.object );
entry.engine.releaseLock( this, entry.oid );
}
if ( _callback != null ) {
_callback.releasing( entry.object, false );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().releasing( entry.object, false );
}
} catch ( Exception except ) {
// maybe we should remove it, when castor become stable
except.printStackTrace();
}
}
// Forget about all the objects in this transaction,
// and mark it as completed.
_objects.removeAllElements();
_engineOids.clear();
_readOnlyObjects.clear();
while ( _deletedList != null ) {
entry = _deletedList;
_deletedList = entry.nextDeleted;
entry.nextDeleted = null;
}
txrolledback();
_status = Status.STATUS_ROLLEDBACK;
}
/**
* Closes all Connections.
* Must be called before the end of the transaction in EJB environment or after commit in standalone case.
*
* @throws TransactionAbortedException The transaction has been
* aborted due to inconsistency, duplicate object identity, error
* with the persistence engine or any other reason
* @throws IllegalStateException This method has been called
* after the end of the transaction.
*/
public synchronized void close()
throws TransactionAbortedException
{
if ( _status != Status.STATUS_ACTIVE &&
_status != Status.STATUS_MARKED_ROLLBACK ) {
throw new IllegalStateException( Messages.message( "persist.missingEnd" ) );
}
try {
// Go through all the connections opened in this transaction,
// close them one by one.
closeConnections();
} catch ( Exception except ) {
// Any error that happens, we're going to rollback the transaction.
_status = Status.STATUS_MARKED_ROLLBACK;
throw new TransactionAbortedException( Messages.format("persist.nested", except), except );
}
}
/**
* Returns true if the object is persistent in this transaction.
*
* @param object The object
* @return True if persistent in transaction
*/
public boolean isPersistent( Object object )
{
ObjectEntry entry;
entry = getObjectEntry( object );
return ( ( entry != null ) && ( ! entry.deleted ) );
}
/**
* Returns true if the object is previously queried/loaded/update/create
* in this transaction
*/
public boolean isRecorded( Object object )
{
ObjectEntry entry;
entry = getObjectEntry( object );
return entry != null;
}
public boolean isDepended( OID master, Object dependent ) {
ObjectEntry entry;
OID depends;
entry = getObjectEntry( dependent );
if ( entry == null )
return false;
depends = entry.oid.getDepends();
if ( depends == null )
return false;
return depends.equals( master );
}
/**
* Returns the object's identity. If the identity was determined when
* the object was created, or if the object was retrieved, that identity
* is returned. If the identity has been modified, this will not be
* reflected until the transaction commits. Null is returned if the
* identity is null, the object does not have any identity, or the
* object is not persistent.
*
* @param object The object
* @return The object's identity, or null
*/
public Object getIdentity( Object object )
{
ObjectEntry entry;
entry = getObjectEntry( object );
if ( entry != null )
return entry.oid.getIdentity();
return null;
}
/**
* Returns the status of this transaction.
*/
public int getStatus()
{
return _status;
}
/**
* Returns true if the transaction is open.
*/
public boolean isOpen()
{
return ( _status == Status.STATUS_ACTIVE || _status == Status.STATUS_MARKED_ROLLBACK );
}
protected Xid getXid()
{
return _xid;
}
/**
* Indicates which lock this transaction is waiting for. When a
* transaction attempts to acquire a lock it must indicate which
* lock it attempts to acquire in order to perform dead-lock
* detection. This method is called by {@link ObjectLock}
* before entering the temporary lock-acquire state.
*
* @param lock The lock which this transaction attempts to acquire
*/
void setWaitOnLock( ObjectLock lock )
{
_waitOnLock = lock;
}
/**
* Returns the lock which this transaction attempts to acquire.
*
* @return The lock which this transaction attempts to acquire
*/
ObjectLock getWaitOnLock()
{
return _waitOnLock;
}
/**
* True if and only if the specified object is loaded or created
* in this transaction and is deleted.
*/
public boolean isDeleted( Object object ) {
ObjectEntry entry;
entry = getObjectEntry( object );
if ( entry != null )
return entry.deleted;
return false;
}
public boolean isDeletedByOID( OID oid ) {
ObjectEntry entry;
entry = getObjectEntry( oid.getLockEngine(), oid );
if ( entry != null )
return entry.deleted;
return false;
}
public int getObjectState( Object object ) {
ObjectEntry entry;
entry = getObjectEntry( object );
if ( entry != null ) {
if ( entry.created && !entry.deleted )
return OBJECT_STATE_PERSISTENT_NEW;
if ( entry.created && entry.deleted )
return OBJECT_STATE_PERSISTENT_NEW_DELETED;
if ( entry.deleted )
return OBJECT_STATE_PERSISTENT_DELETED;
else
return OBJECT_STATE_PERSISTENT;
} else {
// see if it is readonly
Iterator values = _readOnlyObjects.values().iterator();
while ( values.hasNext() ) {
if ( object == ((ObjectEntry) values.next()).object )
return OBJECT_STATE_READ_ONLY;
}
return OBJECT_STATE_TRANSIENT;
}
}
/**
* Get the current application ClassLoader.
*
* @return the current ClassLoader's instance. <code>null</code> if none
* has been provided
*/
public ClassLoader getClassLoader ()
{
return _db.getClassLoader();
}
/**
* Marks an object for deletion. Used during the preparation stage to
* delete an attached relation by marking the object for deletion
* (if not already deleted) and preparing it a second time.
*/
void markDelete( LockEngine engine, Class type, Object identity )
throws LockNotGrantedException, PersistenceException
{
ObjectEntry entry;
entry = getObjectEntry( engine, new OID( engine, engine.getClassMolder( type ), identity ) );
if ( entry != null && ! entry.deleted ) {
try {
if ( entry.molder != null && _callback != null ) {
// yip: why do we need to check for entry.molder? Isn't it implied
_callback.removing( entry.object );
} else if ( entry.molder != null && entry.molder.getCallback() != null ) {
entry.molder.getCallback().removing( entry.object );
}
} catch ( Exception except ) {
throw new PersistenceException( Messages.format("persist.nested", except) );
}
try {
entry.deleted = true;
entry.engine.softLock( this, entry.oid, _lockTimeout );
entry.engine.markDelete( this, entry.oid, null, _lockTimeout );
// Add the entry to a FIFO linked list of deleted entries.
if ( _deletedList == null )
_deletedList = entry;
else {
ObjectEntry deleted;
deleted = _deletedList;
while ( deleted.nextDeleted != null )
deleted = deleted.nextDeleted;
deleted.nextDeleted = entry;
}
try {
if ( _callback != null ) {
_callback.removed( entry.object );
} else if ( entry.molder.getCallback() != null ) {
entry.molder.getCallback().removed( entry.object );
}
} catch ( Exception except ) {
throw new PersistenceException( Messages.format("persist.nested",except) );
}
} catch ( ObjectDeletedException except ) {
// Object has been deleted outside this transaction,
// forget about it
removeObjectEntry( entry.object );
}
}
}
/**
* Adds a new entry recording the use of the object in this
* transaction. This is a copy of the object that is only visible
* (or deleted) in the context of this transaction. The object is
* not persisted if it has not been recorded in this transaction.
*
* @param object The object to record
* @param oid The object's OID
* @param engine The persistence engine used to create this object
*/
ObjectEntry addObjectEntry( OID oid, Object object )
{
if ( oid == null )
throw new IllegalArgumentException("oid cannot be null");
if ( object == null )
throw new IllegalArgumentException("object cannot be null");
ObjectEntry entry;
Hashtable engineOids;
LockEngine engine = oid.getLockEngine();
ClassMolder molder = oid.getMolder();
removeObjectEntry( object );
entry = new ObjectEntry( engine, molder, oid, object );
entry.oid = oid;
_objects.addElement( entry );
engineOids = (Hashtable) _engineOids.get( engine );
if ( engineOids == null ) {
engineOids = new Hashtable();
_engineOids.put( engine, engineOids );
}
engineOids.put( oid, entry );
return entry;
}
/**
* Retrieves the object entry for the specified object.
*
* @param engine The persistence engine used to create this object
* @param oid The object's OID
* @return The object entry
*/
ObjectEntry getObjectEntry( LockEngine engine, OID oid )
{
Hashtable engineOids;
engineOids = (Hashtable) _engineOids.get( engine );
if ( engineOids == null )
return null;
return (ObjectEntry) engineOids.get( oid );
}
ObjectEntry rehash( Object object, OID oid ) {
ObjectEntry entry = getObjectEntry( object );
if ( entry == null )
throw new IllegalArgumentException("ObjectEntry to be rehash is not found");
Hashtable engineOids = (Hashtable) _engineOids.get( entry.engine );
engineOids.remove( entry.oid );
engineOids.put( oid, entry );
entry.oid = oid;
return entry;
}
/**
* Returns the entry for the object from the object. If the entry
* does not exist, the object is not persistent in this
* transaction. An entry will be returned even if the object has
* been deleted in this transaction.
*
* @param object The object to locate
* @return The object's entry or null if not persistent
*/
ObjectEntry getObjectEntry( Object object )
{
ObjectEntry entry;
if(object instanceof LazyCGLIB) {
// TODO [WG] We still might have an option for some serious optimization here if the instance has not been materialized yet.
Class clazz = ((LazyCGLIB)object).interceptedClass();
Object identity = ((LazyCGLIB)object).interceptedIdentity();
for ( Enumeration enumeration = _objects.elements(); enumeration.hasMoreElements(); ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( clazz == entry.object.getClass() ) {
if( identity == null ) {
if( entry.oid.getIdentity() == null)
return entry;
} else {
if( identity.equals(entry.oid.getIdentity()) )
return entry;
}
}
}
} else {
for ( Enumeration enumeration = _objects.elements(); enumeration.hasMoreElements(); ) {
entry = (ObjectEntry) enumeration.nextElement();
if ( object == entry.object )
return entry;
}
}
return null;
}
/**
* Expire object from the cache. Objects expired from the cache will be
* read from persistent storage, as opposed to being read from the
* cache, during subsequent load/query operations.
*
* @param engine The persistence engine
* @param molder The class persistence molder
* @param identity The object's identity
*/
public synchronized void expireCache( LockEngine engine, ClassMolder molder, Object identity )
throws PersistenceException, LockNotGrantedException
{
ObjectEntry entry = null;
Object object = null;
OID oid;
if ( identity == null )
throw new PersistenceException("Identities can't be null!");
oid = new OID( engine, molder, identity );
entry = getObjectEntry( engine, oid );
if ( entry == null ) {
try {
// the call to engine.expireCache may result in a
// recursive call to this.expireCache, therefore,
// an entry is added to the object list to prevent
// infinite loops due to bi-directional references
entry = addObjectEntry( oid, identity );
if ( engine.expireCache( this, oid, _lockTimeout ) ) {
engine.releaseLock( this, oid );
}
removeObjectEntry(identity);
} catch ( LockNotGrantedException except ) {
removeObjectEntry(identity);
throw except;
}
}
}
public boolean isCached( LockEngine engine, ClassMolder molder, Class cls, Object identity )
throws PersistenceException
{
OID oid;
if ( identity == null )
throw new PersistenceException("Identities can't be null!");
oid = new OID( engine, molder, identity );
return engine.isCached (cls, oid);
}
/**
* Removes the entry for an object and returns it. The object is
* no longer part of the transaction.
*
* @param object The object to remove
* @return The removed entry
*/
ObjectEntry removeObjectEntry( Object object )
{
int size;
ObjectEntry entry;
size = _objects.size();
for ( int i = 0; i < size; i++ ) {
entry = (ObjectEntry) _objects.elementAt( i );
if ( entry.object == object ) {
_objects.removeElementAt( i );
( (Hashtable) _engineOids.get( entry.engine ) ).remove( entry.oid );
return entry;
}
}
return null;
}
/**
* Makes the object read-only: move it to the hashtable of readonly objects
* The object must be already in the transaction.
* Readonly objects should be unique in bounds of the transaction,
* otherwise they may be loaded twice (e.g.: one dependent object
* contains reference to another).
*/
void makeReadOnly( Object object )
{
ObjectEntry entry;
entry = removeObjectEntry( object );
if ( entry == null )
throw new IllegalStateException( Messages.format( "persist.internal",
"Attempt to make read-only object that is not in transaction" ) );
_readOnlyObjects.put( entry.oid, entry );
entry.engine.releaseLock( this, entry.oid );
}
ObjectEntry getReadOnlyObjectEntry( OID oid )
{
return (ObjectEntry) _readOnlyObjects.get( oid );
}
public boolean isReadOnly( Object object ) {
return (getObjectState(object) == OBJECT_STATE_READ_ONLY);
}
/**
* Converts AccessMode constant to Database short constant
*/
static short toDatabaseAccessMode( AccessMode mode )
{
if ( mode == AccessMode.Shared )
return Database.Shared;
if ( mode == AccessMode.ReadOnly )
return Database.ReadOnly;
if ( mode == AccessMode.DbLocked )
return Database.DbLocked;
if ( mode == AccessMode.Exclusive )
return Database.Exclusive;
// never happens
return -1;
}
public Database getDatabase() {
return _db;
}
/**
* A transaction records all objects accessed during the lifetime
* of the transaction in this record (queries and created). A
* single entry exist for each object accessible using the object
* or it's OID as identities. The entry records the database engine used
* to persist the object, the object's OID, the object itself, and
* whether the object has been deleted in this transaction,
* created in this transaction, or modified. Objects identified as
* read only are not update when the transaction commits.
*/
public static final class ObjectEntry
{
/**
* The engine with which the object was loaded/created.
*/
final LockEngine engine;
/**
* ClassMolder which the object was loaded/created.
* It maybe different from engine.getClassMolder().
* RelationCollection generally share the same java
* class, but representing relations of different
* pair of object.
*/
final ClassMolder molder;
/**
* The object.
*/
final Object object;
/**
* The OID of the object.
*/
OID oid;
/**
* True if the object has been marked for deletion.
*/
boolean deleted;
/**
* True if the object has been created in this transaction.
*/
boolean created;
/**
* True if the object is indicated to be created.
*/
boolean creating;
/**
* True if the object has been modified in the transaction,
* stored during the preparation stage and is write locked.
*/
// boolean modified;
/**
* True if the object has been modified and the cache should
* be updated at commit time
*/
boolean updateCacheNeeded;
/**
* True if the object has been modified and the persistence
* storage should be updated
*/
boolean updatePersistNeeded;
/**
* Link to the next deleted object in a FIFO list of deleted
* objects.
*/
ObjectEntry nextDeleted;
/**
* Allow TxSynchronizable to access deleted flag.
*
* @return True if the object has been deleted in this transaction.
*/
public boolean isDeleted() {
return deleted;
}
/**
* Allow TxSynchronizable to access created flag.
*
* @return True if the object has been created in this transaction.
*/
public boolean isCreated() {
return created;
}
/**
* Allow TxSynchronizable to access flag showing if the object has
* been modified and the cache has been updated.
*
* @return True if the object has been modified and the cache has
* been updated at commit time.
*/
public boolean isUpdateCacheNeeded() {
return updateCacheNeeded;
}
/**
* Allow TxSynchronizable to access flag showing if the object has
* been modified and the persistence storage has been updated.
*
* @return True if the object has been modified and the persistence
* storage has been updated.
*/
public boolean isUpdatePersistNeeded() {
return updatePersistNeeded;
}
/**
* Allow TxSynchronizable to access OID of the object.
*
* @return The OID of the object.
*/
public OID getOid() {
return oid;
}
/**
* Allow TxSynchronizable to access the object.
*
* @return The object.
*/
public Object getObject() {
return object;
}
ObjectEntry( LockEngine engine, ClassMolder molder, OID oid, Object object ) {
this.engine = engine;
this.molder = molder;
this.oid = oid;
this.object = object;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append( oid );
sb.append('\t'); sb.append("deleted: "); sb.append(deleted);
sb.append('\t'); sb.append("creating: "); sb.append(creating);
sb.append('\t'); sb.append("created: "); sb.append(created);
sb.append('\t'); sb.append("deleted: "); sb.append(deleted);
return sb.toString();
}
}
}