/**
* Created on Mar 12, 2006
*
* $Id$
* $Revision$
*/
package org.springmodules.transaction.jini;
import java.rmi.RemoteException;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import net.jini.core.lease.LeaseDeniedException;
import net.jini.core.transaction.CannotAbortException;
import net.jini.core.transaction.CannotCommitException;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionFactory;
import net.jini.core.transaction.UnknownTransactionException;
import net.jini.core.transaction.server.NestableTransactionManager;
import net.jini.core.transaction.server.TransactionManager;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.CannotCreateTransactionException;
import org.springframework.transaction.InvalidIsolationLevelException;
import org.springframework.transaction.NestedTransactionNotSupportedException;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.ResourceHolderSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springmodules.jini.JiniUtils;
/**
* Jini PlatformTransactionManager . Requires a Jini TransactionManager service
* to perform to create the actual transactions using jini transactional context
* (transactionalContext property). For JavaSpaces for example, the context will
* be represented by the java space.
* Does <strong>not</strong> support nested transaction propagation yet.
*
* @author Costin Leau
*
*
* TODO: make it Serializable ? TODO: the default timeout (-1) is similar to
* Lease.ANY which might create a tx with infinite timeout which results in
* deadlock.
*
*/
public class JiniTransactionManager extends AbstractPlatformTransactionManager
implements InitializingBean {
// TransactionManager used for creating the actual transaction
private transient TransactionManager transactionManager;
// the jini participant - can be javaspace or any other service that wants
// to take part in the transaction
private Object transactionalContext;
public JiniTransactionManager() {
}
public JiniTransactionManager(TransactionManager transactionManager,
Object transactionalContext) {
this.transactionManager = transactionManager;
this.transactionalContext = transactionalContext;
afterPropertiesSet();
}
/**
* @see org.springmodules.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() {
if (transactionManager == null)
throw new IllegalArgumentException(
"transactionManager property is required");
if (transactionalContext == null)
throw new IllegalArgumentException(
"transactionalContext property is required");
if (transactionManager instanceof NestableTransactionManager)
setNestedTransactionAllowed(true);
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doGetTransaction()
*/
protected Object doGetTransaction() throws TransactionException {
JiniTransactionObject txObject = new JiniTransactionObject();
// txObject.setNestedTransactionAllowed
// txObject.setJiniHolder(transactionalContext);
// set the jini holder is one is found
if (TransactionSynchronizationManager.hasResource(transactionalContext)) {
JiniHolder jiniHolder = (JiniHolder) TransactionSynchronizationManager
.getResource(transactionalContext);
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound tx data [" + jiniHolder
+ "] for Jini resource " + transactionalContext);
}
txObject.setJiniHolder(jiniHolder, false);
}
return txObject;
}
protected RuntimeException convertJiniException(Exception e) {
return JiniUtils.convertJiniException(e);
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doBegin(java.lang.Object,
* org.springmodules.transaction.TransactionDefinition)
*/
protected void doBegin(Object transaction, TransactionDefinition definition)
throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) transaction;
if (logger.isDebugEnabled())
logger.debug("Beginning Jini transaction " + txObject.toString());
try {
doJiniBegin(txObject, definition);
}
catch (NotSupportedException ex) {
// assume nested transaction not supported
throw new NestedTransactionNotSupportedException(
"Jini implementation does not support nested transactions",
ex);
}
catch (UnsupportedOperationException ex) {
// assume nested transaction not supported
throw new NestedTransactionNotSupportedException(
"Jini implementation does not support nested transactions",
ex);
}
catch (SystemException ex) {
throw new CannotCreateTransactionException("Jini failure on begin",
ex);
}
}
/**
* Performs the transaction begin.
*
* @param txObject
* @param definition
* @throws NotSupportedException
* @throws SystemException
*/
protected void doJiniBegin(JiniTransactionObject txObject,
TransactionDefinition definition) throws NotSupportedException,
SystemException {
// create the tx
try {
if (txObject.getJiniHolder() == null) {
if (logger.isDebugEnabled())
logger.debug("creating new jini tx for "
+ getTransactionalContext());
Transaction.Created txCreated = TransactionFactory.create(
transactionManager, definition.getTimeout());
JiniHolder jiniHolder = new JiniHolder(txCreated);
jiniHolder.setTimeoutInSeconds(definition.getTimeout());
txObject.setJiniHolder(jiniHolder, true);
}
txObject.getJiniHolder().setSynchronizedWithTransaction(true);
applyIsolationLevel(txObject, definition.getIsolationLevel());
// check for timeout just in case
// applyTimeout(txObject, definition.getTimeout());
// Bind the session holder to the thread.
if (txObject.isNewJiniHolder()) {
TransactionSynchronizationManager.bindResource(
getTransactionalContext(), txObject.getJiniHolder());
}
}
catch (LeaseDeniedException e) {
throw new CannotCreateTransactionException("lease denied", e);
}
catch (RemoteException e) {
throw new CannotCreateTransactionException("remote exception", e);
}
}
protected void applyIsolationLevel(JiniTransactionObject txObject,
int isolationLevel) throws InvalidIsolationLevelException,
SystemException {
if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
throw new InvalidIsolationLevelException(
"JiniTransactionManager does not support custom isolation levels");
}
}
protected void applyTimeout(JiniTransactionObject txObject, int timeout)
throws NotSupportedException {
// TODO: maybe use a LeaseRenewalManager
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
throw new NotSupportedException(
"JiniTransactionManager does not support custom timeouts");
}
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doCommit(org.springmodules.transaction.support.DefaultTransactionStatus)
*/
protected void doCommit(DefaultTransactionStatus status)
throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) status
.getTransaction();
if (logger.isDebugEnabled())
logger.debug("Committing Jini transaction " + txObject.toString());
try {
txObject.getTransaction().commit();
}
catch (UnknownTransactionException e) {
throw convertJiniException(e);
}
catch (CannotCommitException e) {
throw convertJiniException(e);
}
catch (RemoteException e) {
throw convertJiniException(e);
}
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#isExistingTransaction(java.lang.Object)
*/
protected boolean isExistingTransaction(Object transaction)
throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) transaction;
return txObject.hasTransaction();
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doRollback(org.springmodules.transaction.support.DefaultTransactionStatus)
*/
protected void doRollback(DefaultTransactionStatus status)
throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) status
.getTransaction();
if (logger.isDebugEnabled())
logger.debug("Rolling back Jini transaction" + txObject.toString());
try {
txObject.getTransaction().abort();
}
catch (UnknownTransactionException e) {
throw convertJiniException(e);
}
catch (CannotAbortException e) {
throw convertJiniException(e);
}
catch (RemoteException e) {
throw convertJiniException(e);
}
}
protected void doCleanupAfterCompletion(Object transaction) {
JiniTransactionObject txObject = (JiniTransactionObject) transaction;
// Remove the session holder from the thread.
if (txObject.isNewJiniHolder()) {
if (logger.isDebugEnabled()) {
logger.debug("Removing per-thread Jini transaction for "
+ getTransactionalContext());
}
TransactionSynchronizationManager
.unbindResource(getTransactionalContext());
}
txObject.getJiniHolder().clear();
}
protected void doSetRollbackOnly(DefaultTransactionStatus status)
throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) status
.getTransaction();
if (status.isDebug()) {
logger.debug("Setting Jini transaction on txContext ["
+ getTransactionalContext() + "] rollback-only");
}
txObject.setRollbackOnly();
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doResume(java.lang.Object,
* java.lang.Object)
*/
protected void doResume(Object transaction, Object suspendedResources)
throws TransactionException {
JiniHolder jiniHolder = (JiniHolder) suspendedResources;
TransactionSynchronizationManager.bindResource(
getTransactionalContext(), jiniHolder);
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#doSuspend(java.lang.Object)
*/
protected Object doSuspend(Object transaction) throws TransactionException {
JiniTransactionObject txObject = (JiniTransactionObject) transaction;
txObject.setJiniHolder(null, false);
JiniHolder jiniHolder = (JiniHolder) TransactionSynchronizationManager
.unbindResource(getTransactionalContext());
return jiniHolder;
}
/**
* @return Returns the transactionManager.
*/
public TransactionManager getTransactionManager() {
return transactionManager;
}
/**
* @param transactionManager
* The transactionManager to set.
*/
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* @see org.springmodules.transaction.support.AbstractPlatformTransactionManager#useSavepointForNestedTransaction()
*/
protected boolean useSavepointForNestedTransaction() {
return false;
}
/**
* Jini Transaction object. Used as transaction object by
* JiniTransactionManager.
*
* TODO: can SmartTransactionObject be implemented?
*/
private static class JiniTransactionObject {
private JiniHolder jiniHolder;
private boolean newJiniHolder;
public boolean hasTransaction() {
return (jiniHolder != null && jiniHolder.hasTransaction());
}
public void setJiniHolder(JiniHolder jiniHolder,
boolean newSessionHolder) {
this.jiniHolder = jiniHolder;
this.newJiniHolder = newSessionHolder;
}
public JiniHolder getJiniHolder() {
return jiniHolder;
}
public boolean isNewJiniHolder() {
return newJiniHolder;
}
public boolean isRollbackOnly() {
return (jiniHolder != null && jiniHolder.isRollbackOnly());
}
public void setRollbackOnly() {
if (jiniHolder != null)
jiniHolder.setRollbackOnly();
}
public Transaction getTransaction() {
if (hasTransaction())
return jiniHolder.txCreated.transaction;
return null;
}
}
// is ResourceHolder really required
public static class JiniHolder extends ResourceHolderSupport {
private Transaction.Created txCreated;
public JiniHolder(Transaction.Created txCreated) {
this.txCreated = txCreated;
}
/**
* @return Returns the txCreated.
*/
public Transaction.Created getTxCreated() {
return txCreated;
}
public boolean hasTransaction() {
return (txCreated != null && txCreated.transaction != null);
}
}
/**
* @return Returns the transactionalContext.
*/
public Object getTransactionalContext() {
return transactionalContext;
}
/**
* @param transactionalContext
* The transactionalContext to set.
*/
public void setTransactionalContext(Object txResource) {
this.transactionalContext = txResource;
}
}