/*
$Header: /cvsroot/xorm/xorm/src/org/xorm/TransactionImpl.java,v 1.36 2004/05/17 22:55:47 wbiggs Exp $
This file is part of XORM.
XORM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
XORM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with XORM; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.xorm;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.TreeSet;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.jdo.InstanceCallbacks;
import javax.jdo.Transaction;
import javax.jdo.PersistenceManager;
import javax.jdo.JDODataStoreException;
import javax.jdo.JDOFatalDataStoreException;
import javax.jdo.JDOUserException;
import org.xorm.datastore.DatastoreDriver;
import org.xorm.datastore.DriverException;
import org.xorm.datastore.Row;
/**
* A transaction with ACID properties, to which may be attached
* any number of objects.
*/
public class TransactionImpl implements Transaction, I15d {
private int status = Status.STATUS_NO_TRANSACTION;
private Synchronization synchronization;
// Objects in hash are InterfaceInvocationHandlers
private HashSet objects = new HashSet();
// and here, RelationshipProxies
private HashSet relationships = new HashSet();
private InterfaceManager mgr;
private DatastoreDriver driver;
private Options jdoOptions;
public TransactionImpl(InterfaceManager mgr, DatastoreDriver driver, Options jdoOptions) {
this.mgr = mgr;
this.driver = driver;
this.jdoOptions = jdoOptions;
}
public PersistenceManager getPersistenceManager() {
return mgr;
}
InterfaceManager getInterfaceManager() {
return mgr;
}
public DatastoreDriver getDriver() {
return driver;
}
// Adds a proxy object to the transaction
// Assumes the object is marked as transactional
public void attach(Object o) {
InterfaceInvocationHandler handler = InterfaceInvocationHandler.getHandler(o);
objects.add(handler);
}
void attachRelationship(RelationshipProxy rp) {
relationships.add(rp);
}
public boolean contains(Object o) {
InterfaceInvocationHandler handler = InterfaceInvocationHandler.getHandler(o);
return objects.contains(handler);
}
public Object get(Class clazz, Object primaryKey) {
// TODO change implementation for performance
Iterator i = objects.iterator();
while (i.hasNext()) {
InterfaceInvocationHandler handler = (InterfaceInvocationHandler)
i.next();
if (handler.getClassMapping().getMappedClass().equals(clazz)
&& handler.getObjectId().equals(primaryKey)) {
return handler.getProxy();
}
}
return null;
}
public void detach(Object o) {
InterfaceInvocationHandler handler = InterfaceInvocationHandler.getHandler(o);
objects.remove(handler);
}
public void detachRelationship(RelationshipProxy rp) {
relationships.remove(rp);
}
public void begin() {
begin(false);
}
public void begin(boolean readOnly) {
// Avoid race conditions here
synchronized (this) {
if (isActive()) {
throw new JDOUserException(I18N.msg("E_begin_active_txn"));
}
status = Status.STATUS_ACTIVE;
}
try {
driver.begin(readOnly);
// } catch (DriverException e) {
} catch (Throwable e) {
e.printStackTrace();
status = Status.STATUS_NO_TRANSACTION;
throw new JDODataStoreException("Cannot begin transaction", e);
}
}
private void notifyExit(boolean commit) {
// Now tell everything the transaction's over.
Iterator i = objects.iterator();
while (i.hasNext()) {
if (((InterfaceInvocationHandler) i.next())
.exitTransaction(commit)) {
i.remove();
}
}
i = relationships.iterator();
while (i.hasNext()) {
((RelationshipProxy) i.next())
.exitTransaction(commit);
}
}
public void rollback() {
if (status == Status.STATUS_NO_TRANSACTION) {
throw new JDOUserException(I18N.msg("E_rollback_no_txn"));
}
status = Status.STATUS_COMMITTING; // Is this correct?
try {
driver.rollback();
//} catch (DriverException e) {
} catch (Throwable e) {
e.printStackTrace();
throw new JDOFatalDataStoreException("Rollback failed", e);
}
notifyExit(false);
reset();
if (synchronization != null) {
synchronization.afterCompletion(Status.STATUS_ROLLEDBACK);
}
}
public void commit() {
if (status == Status.STATUS_NO_TRANSACTION) {
throw new JDOUserException(I18N.msg("E_commit_no_txn"));
}
status = Status.STATUS_COMMITTING;
ArrayList driverExceptions = new ArrayList();
if (synchronization != null) {
synchronization.beforeCompletion();
}
// Iterate over objects and build the list of those which
// need to be inserted/updated
TreeSet tree = new TreeSet(new DependencyComparator());
Iterator i = objects.iterator();
while (i.hasNext()) {
InterfaceInvocationHandler handler = (InterfaceInvocationHandler)
i.next();
if (handler.isPersistent()) {
if (handler.isDirty() ||
handler.isNew() ||
handler.isDeleted()) {
tree.add(handler);
}
}
}
// Remove deleted many-to-many relationship rows
i = relationships.iterator();
while (i.hasNext()) {
RelationshipProxy rp = (RelationshipProxy) i.next();
if (rp.getRelationshipMapping().isMToN()) {
try {
Iterator j = rp.getDeletedRows().iterator();
while (j.hasNext()) {
driver.delete((Row) j.next());
j.remove();
}
} catch (DriverException e) {
driverExceptions.add(e);
}
}
}
// Now walk the list and take appropriate actions
i = tree.iterator();
while (i.hasNext()) {
InterfaceInvocationHandler handler = (InterfaceInvocationHandler)
i.next();
try {
if (handler.isNew()) {
if (!handler.isDeleted()) {
if (handler.getProxy() instanceof InstanceCallbacks) {
((InstanceCallbacks) handler.getProxy()).jdoPreStore();
}
Object oldID = handler.getObjectId();
driver.create(handler.getRow());
handler.refreshObjectId();
Object newID = handler.getObjectId();
notifyIDChangedAll(tree, oldID, newID);
// Create the 2nd level cache entry
mgr.addToCache(handler.getRow());
}
} else if (handler.isDeleted()) {
if (handler.getProxy() instanceof InstanceCallbacks) {
((InstanceCallbacks) handler.getProxy()).jdoPreDelete();
}
driver.delete(handler.getRow());
// Remove from the 2nd level cache
mgr.removeFromCache(handler.getRow());
} else if (handler.getRow().isDirty()) {
if (handler.getProxy() instanceof InstanceCallbacks) {
((InstanceCallbacks) handler.getProxy()).jdoPreStore();
}
driver.update(handler.getRow());
// Replace the 2nd level cache entry
mgr.addToCache(handler.getRow());
}
} catch (Throwable e) {
e.printStackTrace();
driverExceptions.add(e);
}
}
// After data objects are inserted, insert new relationship rows
i = relationships.iterator();
while (i.hasNext()) {
RelationshipProxy rp = (RelationshipProxy) i.next();
if (rp.getRelationshipMapping().isMToN()) {
try {
Iterator j = rp.getNewRows().iterator();
while (j.hasNext()) {
driver.create((Row) j.next());
j.remove();
}
} catch (Throwable e) {
e.printStackTrace();
driverExceptions.add(e);
}
}
}
if (!driverExceptions.isEmpty()) {
// An error occurred
rollback();
throw new JDODataStoreException("Errors writing to datastore, transaction rolled back", (Throwable[]) driverExceptions.toArray(new Throwable[0]));
}
try {
driver.commit();
status = Status.STATUS_COMMITTED;
} catch (Throwable e) {
e.printStackTrace();
try {
rollback();
throw new JDODataStoreException("Commit failed, rolled back instead", e);
} catch (JDOFatalDataStoreException e2) {
throw new JDOFatalDataStoreException("Commit failed, could not rollback", e);
}
}
// Tell the contained objects to perform state transitions
notifyExit(true);
if (synchronization != null) {
synchronization.afterCompletion(status);
}
reset();
}
private void notifyIDChangedAll(TreeSet tree, Object oldID, Object newID) {
Iterator i = tree.iterator();
while (i.hasNext()) {
InterfaceInvocationHandler handler = (InterfaceInvocationHandler)
i.next();
handler.notifyIDChanged(oldID, newID);
}
i = relationships.iterator();
while (i.hasNext()) {
RelationshipProxy rp = (RelationshipProxy)
i.next();
rp.notifyIDChanged(oldID, newID);
}
}
private void reset() {
status = Status.STATUS_NO_TRANSACTION;
objects = new HashSet();
relationships = new HashSet();
}
public boolean isActive() {
return status != Status.STATUS_NO_TRANSACTION;
}
public void setSynchronization(Synchronization synchronization) {
// Per JDO Spec 13.4.3, throw exception if called from callback
if (status == Status.STATUS_COMMITTING) {
throw new JDOUserException(I18N.msg("E_txn_committing"));
}
this.synchronization = synchronization;
}
public Synchronization getSynchronization() {
return synchronization;
}
void refreshAll() {
// Reloads all objects in the scope of the current transaction
Iterator i = objects.iterator();
while (i.hasNext()) {
InterfaceInvocationHandler handler = (InterfaceInvocationHandler)
i.next();
mgr.refresh(handler.getProxy());
}
}
// The JDO options get/set methods are wrapper pattern.
public void setNontransactionalRead(boolean value) {
jdoOptions.setNontransactionalRead(value);
}
public boolean getNontransactionalRead() {
return jdoOptions.getNontransactionalRead();
}
public void setNontransactionalWrite(boolean value) {
jdoOptions.setNontransactionalWrite(value);
}
public boolean getNontransactionalWrite() {
return jdoOptions.getNontransactionalWrite();
}
public void setOptimistic(boolean value) {
jdoOptions.setOptimistic(value);
}
public boolean getOptimistic() {
return jdoOptions.getOptimistic();
}
public void setRetainValues(boolean value) {
jdoOptions.setRetainValues(value);
}
public boolean getRetainValues() {
return jdoOptions.getRetainValues();
}
public void setRestoreValues(boolean restoreValues) {
jdoOptions.setRestoreValues(restoreValues);
}
public boolean getRestoreValues() {
return jdoOptions.getRestoreValues();
}
protected void finalize() throws Throwable {
if (isActive()) {
closeImpl();
}
}
/**
* Releases active transactional resources by rolling back the
* datastore transaction if necessary.
*/
void closeImpl() {
try {
rollback();
System.err.println(I18N.msg("W_finalize_active_txn"));
} catch(Throwable e) {
System.err.println(I18N.msg("W_finalize_active_txn_fail"));
//no logger, and throwing an exception in finalize is bad.
//Oh well, just System.err it.
e.printStackTrace();
status = Status.STATUS_NO_TRANSACTION;
}
}
}