Skip to content

An XA Filesystem

November 27, 2008

A long post, sorry!  Most is pasted code though.

Update: If you get to the bottom of this post, you may be interested in this update.

One of the underlying design tenets of my application is that everything should happen, or nothing should happen. This covers all the database access, as well as file access, and there will be a lot of the latter. This means transactions for databases and transactions for filesystems, and since both database changes and filesystem changes will be required together, this means XA transactions across the database(s) and the filesystem(s).

There are two big problems with the above requirement: firstly, DDL is rarely transactional (PostgreSQL and Informix can do it, but since choice of RDBMS is my customer’s, not mine, I can’t rely on them); and secondly, filesystems aren’t transactional either.

The DDL issue is a major one, and one not easily resolved. Given that it won’t be transactional, is it worth pursuing transactions in the other areas? I think so – the effort of writing recovery code is so much more otherwise. The layer which abstracts the application from the database can be written to understand the capabilities of the RDBMS – so if transactional DDL is supported, then it can be used.

That’s for another day though, today I’m concerned with filesystem transactions. Luckily here some work has been done. Filesystem transactions in Java are supported by the Commons Transaction Apache project. I used this really nice step-by-step tutorial to get up and running.


package txtest;

import org.apache.commons.transaction.file.FileResourceManager;
import org.apache.commons.transaction.util.Log4jLogger;
import org.apache.log4j.Logger;
import org.junit.Test;

public class CommonsTxTest {
    private static final Logger logger = Logger.getLogger(CommonsTxTest.class);

    @Test
    public void test() {
        try {
            FileResourceManager fileResourceManager = new FileResourceManager(
                    "/Users/mdneale/txtest/store",
                    "/Users/mdneale/txtest/work",
                    false,
                    new Log4jLogger(logger));

            fileResourceManager.start();

            String txId = fileResourceManager.generatedUniqueTxId();

            fileResourceManager.startTransaction(txId);
            fileResourceManager.createResource(txId, "new.file");
            fileResourceManager.commitTransaction(txId);

            fileResourceManager.stop(FileResourceManager.SHUTDOWN_MODE_NORMAL);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

And it works. Running this short program creates the new file “new.file” in the folder /Users/mdneale/txtest/store. Now swap the line:

fileResourceManager.commitTransaction(txId);

for:

fileResourceManager.rollbackTransaction(txId);

Remove the file “new.file” that was created by the last run of the program, and run the program again. This time the file doesn’t appear. So far, so good.

The next step is XA. I’m going to explain here how to interface the Commons Transaction File Resource Manager to a J2EE Transaction Manager so that the Transaction Manager can control the resource as part of an XA Transaction.  It shouldn’t be difficult to convert it for a non-J2EE Transaction Manager if necessary.

XA

The interface between the Transaction Manager and the Resource Manager is the XAResource interface. I won’t write here what you can read elsewhere – this is a good article on the Java Transaction API (JTA) including how the XAResource interface fits into the picture, and the Sun JTA specification is also very good.

The XAResource interface has ten methods for us to implement – shown below.  If you want to skip the explanation and just cut to the code, it’s pasted at the end of the post.

  • XAResource#start – start work for a transaction branch
  • XAResource#end – end work on a transaction branch
  • XAResource#prepare – prepare transaction branch for a commit (i.e. vote on the expected outcome of the commit)
  • XAResource#commit – commit a transaction branch
  • XAResource#rollback – rollback a transaction branch
  • XAResource#getTransactionTimeout – get current transaction timeout from the Resource Manager
  • XAResource#setTransactionTimeout – set current transaction timeout
  • XAResource#isSameRM – check whether two XAResources actually refer to the same underlying Resource Manager
  • XAResource#forget – forget any work done by the Resource Manager for a transaction
  • XAResource#recover – get a list of prepared transactions, for use during failure recovery

We’ll take them one at a time.

XAResource#start(Xid xid, int flags)

This method starts work for a particular transaction branch, using the transaction Id given in the argument xid.  So the first iteration of this method would just invoke FileResourceManager#startTransaction.  However, the start method receives a flags argument which gives us a few more options.

The first flag we have to handle is TMJOIN.  If we have multiple instances of our XAResource class (for example for multiple connections), but in fact they’re all talking to the same FileResourceManager, then it would make sense for them to all be working within same underlying transaction, and not multiple transactions.  To support this there is a method XAResource#isSameRM which allows the Transaction Manager to detect whether an XAResource it has is really for the same FileResourceManager as another it has.  If it is then it will invoke start with the flag TMJOIN, to join an existing transaction.  In this case we should verify that the transaction represented by xid already exists and is already active, and then do nothing, as FileResourceManager#startTransaction has already been invoked previously.

The second flag we have to handle is TMRESUME.  It is possible to suspend the transaction (by calling XAResource#end with the flag TMSUSPEND) and later resume it by again calling start with the flag TMRESUME.  This is difficult here as suspending and resuming is in the context of a specific Connection – i.e. operations performed through the Connection are part of an XA transaction after the call to start, and then we can suspend the XA transaction so that operations performed through the Connection are not transactional, and finally we can resume again.

Commons Transaction does not support the concept of a Connection.  It’s probably worth writing a Connection class to sit alongside this XAResource and once that is done TMSUSPEND and TMRESUME can be implemented, but for now, we’ll skip implementing suspend and resume.

XAResource#end(Xid xid, int flags)

This method ends work for a particular transaction – it doesn’t commit or rollback, it just indicates that we’re done performing operations.  We’ve already discussed the TMSUSPEND flag, which we’re not going to implement for now.  All that leaves are two flags TMSUCCESS and TMFAIL.  TMSUCCESS means we’re finished with the work, and it all went okay – for this flag we don’t need to do anything.  TMFAIL means that there was an error.  To implement this flag we’ll call FileResourceManager#markTransactionForRollback, so that the transaction can’t be committed.

Note that the reason there is nothing to do for TMSUCCESS is again because we don’t have the concept of a Connection.  In reality end should disassociate the transaction from the Connection, so that any further actions through the Connection are not part of the XA Transaction.

XAResource#prepare(Xid xid)

Here we just need to call FileResourceManager#prepareTransaction so that it can vote on the outcome of the transaction.

XAResource#commit(Xid xid, boolean onePhase)

This is called by the Transaction Manager after all the transactional branches have been prepared successfully, to tell the resource to commit.  Here we just need to call FileResourceManager#commitTransaction.

One other feature needs to be supported.  The Transaction Manager may set the onePhase argument, indicating that in fact this doesn’t need to be a two-phase commit (probably as there are no other participants) and we can save resources by performing a one-phase commit.  This doesn’t actually make any difference to our calls to FileResourceManager for the commit, however there is a subtle difference as in a one-phase commit scenario the commit might actually fail (with a two-phase commit a commit should only fail if there is a major failure with the resource).  Therefore, if onePhase is set, and the commit fails, then we can issue a rollback by invoking FileResourceManager#rollbackTransaction.

XAResource#rollback(Xid xid)

This is called by the Transaction Manager to tell the resource manager to roll back the transaction.  Here we just need to call FileResourceManager#rollbackTransaction.

XAResource#getTransactionTimout()

Gets the current transaction timeout value from the FileResourceManager.  We can call FileResourceManager#getDefaultTransactionTimeout for this, we just need to convert from milliseconds returned by FileResourceManager, to seconds expected by XAResource.

XAResource#setTransactionTimeout(int seconds)

Sets the current transaction timeout value for the FileResourceManager by calling FileResourceManager#setDefaultTransactionTimeout. As with the get operation, a seconds to milliseconds conversion is required.  Note also that according to XAResource, setTransactionTimeout can be passed 0 to make us reset the FileResourceManager timeout value back to the default, and we can’t support this facility with FileResourceManager.

XAResource#isSameRM(XAResource xares)

This was mentioned in the description of the start method, it checks whether the FileResourceManager we are representing by this XAResource instance is the same as the one represented by the XAResource object passed to the method.

XAResource#forget(Xid xid)

This method is called to ask the FileResourceManager to forget about work it has done for a transaction branch.  This simply rolls back the transaction.

XAResource#recover(int flag)

This method is a problem.  This method should return all prepared transaction branches, for use by the Transaction Manager on recovery from failure.  Unfortunately, the way FileResourceManager works is to automatically try and recover when it is restarted, by rolling forward or rolling back the transactions it finds.  These actions won’t be coordinated with other Resource Managers.  This means more work will have to be performed manually by administrators after failure.  This is one area that will have to be worked on.

Testing It

To test the class I wrote an EJB which creates a file on two separate FileResourceManager instances, and inserts a row into two separate MySQL databases.  I deployed it on Glassfish v2 and invoking the EJB successfully created both files and inserted both rows.  I then altered the EJB so that it failed and threw an exception and sure enough, all resources rolled back successfully.

I won’t post the EJB code here, as it is trivial, however note that you need to manually register the XAFileResources with the Glassfish Transaction Manager.  In Glassfish, the following code achieves this:

Context ctx = new InitialContext();
TransactionManager transactionManager = (TransactionManager)ctx.lookup("java:appserver/TransactionManager");
Transaction transaction = transactionManager.getTransaction();

XAFileResource fileResource = new XAFileResource(fileResourceManager);
transaction.enlistResource(fileResource);

Next, I published the EJB as a web service and deployed it on two Glassfish instances.  I then wrote a third coordinating EJB running on a third Glassfish instance and a command-line client.  The WS-TX support in Glassfish meant that the now four FileResourceManagers and four databases were all coordinated to commit or rollback, across web services, which is exactly what I want.

Xid

The implementation of the Xid (the XA transaction Id) that must be passed to almost all the methods on the class has a particular constraint related to the FileResourceManager.  The FileResourceManager creates a directory on the disk with the name of the transaction Id, therefore the Xid implementation must have a toString() method that returns a sensible String version of the Id it holds.

Also note in the XAFileResource code that we actually pass xid.toString() to all the FileResourceManager methods, rather than xid.  This is because somewhere deep in the Commons Transaction code, it attempts to cast the object to a String (rather than call its toString method), hence passing anything other than a String causes a ClassCastException.  It’s possible that there is a bug fix for this – I haven’t looked.

Final Comments

The code is pasted below, and it I hope serves as a proof-of-concept.  Architecturally, perhaps we need to build the full solution with Connection classes, XADataSources, Resource Adapter etc, however this is going to depend upon how we decide to deploy this, and for my purposes right now, this solution will suffice.

I’m interested in comments – but please bare in mind two things – first that this is prototype and rushed (though I did clean it up a bit), and so I’m aware for instance that the error codes returned aren’t necesarily correct, and that, as stated in the comment at the top of the class, we need to add a lot of state checking to check the transaction is in the right state to perform a particular action.

The second thing to bare in mind is that as per prototype, it hasn’t been through anything more than a month or two’s use in development, and certainly hasn’t been through anything like a full test cycle. You’re welcome to use it (a nod would be nice if you do),
but treat it as it is and test it, and personally I would look at it, understand it, then start again (which is probably just what I should do, but won’t as I don’t have the time!)


package txtest;

import javax.transaction.Status;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.apache.commons.transaction.file.FileResourceManager;
import org.apache.commons.transaction.file.ResourceManagerException;
import org.apache.log4j.Logger;

/**
 * A JTA XAResource for Apache Commons Transaction FileResourceManager
 *
 * The xids provided to methods on this class are passed to the FileResourceManager to use
 * as its transaction id.  Internally, the FileResourceManager casts the transaction Id to a String, so we
 * must pass in a String (from the documentation it's possible that this is a bug).
 * Therefore, the Xid class passed to the methods of this class must have a toString
 * implementation that returns a string representation of the Xid, and this String is passed to FileResourceManager.
 *
 * There are other options - one is to write a method in this class to convert a Xid to a String and to pass
 * that to the FileResourceManager, and the
 * other is to use the method FileResourceManager#generatedUniqueTxId to generate a Transaction Id for the
 * transaction in the start method and store a mapping between the Xid and Transaction Id inside this
 * class.  The downside to both of these is that elsewhere calls will be being made to the FileResourceManager
 * to create, modify and remove files etc, and they also need to known which Transaction Id to use.
 *
 * This class does not check the state of the transaction, to verify that a transaction is in the correct
 * state to call a particular method.  The FileResourceManager will error if the transaction is in the wrong
 * state, however as the class doesn't check this explicitly, the correct XAExceptions will not be returned.
 *
 * XAResource#recover isn't implemented - FileResourceManager automatically rolls-back or rolls-forward a
 * transaction in order to recover it on restart.  This limits this class's XA compliance.
 *
 * @author mdneale (https://matthewneale.wordpress.com/)
 */
public class XAFileResource implements XAResource {

	private static Logger logger = Logger.getLogger(XAFileResource.class);

	/** The Apache Commons Transaction FileResourceManager managed by this XAResource */
	private FileResourceManager fileResourceManager = null;

	/**
	 * Constructs a new XAFileResource to manage an Apache Commons Transaction FileResourceManager and allow
	 * it to take part in XA Transactions.
	 *
	 * A single FileResourceManager instance must be managed by a single XAFileResource instance, do not create
	 * more than one XAFileResource for a single FileResourceManager.
	 *
	 * @param fileResourceManager The Commons Transaction FileResourceManager to be managed by this XAResource
	 * @throws XAException Possible XAException values are: XAER_INVAL if fileResourceManager is null
	 */
	public XAFileResource(FileResourceManager fileResourceManager) throws XAException {
		if (fileResourceManager == null) {
			logger.error("fileResourceManager cannot be null");
			throw new XAException(XAException.XAER_INVAL);
		}

		this.fileResourceManager = fileResourceManager;
	}

	/**
	 * Commits the transaction specified by xid.
	 *
	 * If the FileResourceManager does not commit the transaction and the argument onePhase is set to true,
	 * the FileResourceManager rolls-back the transaction and throws an XAException with the code XA_RBOTHER.
	 *
	 * @param xid XA Transaction identifier
	 * @param onePhase If true, the FileResourceManager uses a one-phase commit protocol to commit the work done on
	 *                 behalf of xid.
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null;
	 *                     XAER_RMERR if the commit fails or a subsequent rollback of a one-phase commit fails;
	 *                     XA_RBOTHER if a one-phase commit fails and the transaction is rolled-back.
	 */
	@Override
	public void commit(Xid xid, boolean onePhase) throws XAException {
		logger.debug("commit xid=" + xid + ", onePhase=" + onePhase);

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		try {
			fileResourceManager.commitTransaction(xid.toString());
		} catch (ResourceManagerException e) {
			logger.warn("Failed to commit transaction xid=" + xid, e);

			if (onePhase) {
				// If one-phase commit has been specified we are allowed to roll-back the transaction
				try {
					logger.debug("Rolling-back transaction xid=" + xid);

					fileResourceManager.rollbackTransaction(xid.toString());

					throw new XAException(XAException.XA_RBOTHER);
				} catch (ResourceManagerException e1) {
					logger.warn("Failed to rollback transaction after a failed one-phase commit xid=" + xid, e1);
					XAException xaException = new XAException(XAException.XAER_RMERR);
					xaException.initCause(e);
					throw xaException;
				}
			}

			XAException xaException = new XAException(XAException.XAER_RMERR);
			xaException.initCause(e);
			throw xaException;
		}
	}

	/**
	 * Ends the work performed on behalf of a transaction branch.
	 *
	 * If the flag TMSUCCESS is specified, the portion of work has completed successfully.
	 *
	 * If the flag TMFAIL is specified, the portion of work has failed and FileResourceManager will mark the transaction
	 * as rollback-only.
	 *
	 * Flag TMSUSPEND is not supported.
	 *
	 * @param xid XA Transaction identifier
	 * @param flags One of TMSUCCESS or TMFAIL
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null;
	 *                     XAER_RMERR if TMFAIL was specified but the transaction fails to be marked as rollback-only;
	 *                     XAER_INVAL if the flags argument is not TMSUCCESS or TMFAIL
	 */
	@Override
	public void end(Xid xid, int flags) throws XAException {
		logger.debug("end xid=" + xid + ", flags=" + flags);

		if (flags == TMSUSPEND) {
			logger.error("Flag TMSUSPEND not supported");
			throw new XAException(XAException.XAER_INVAL);
		} else if ((flags != TMSUCCESS) && (flags != TMFAIL)) {
			logger.error("Invalid argument: flags=" + flags);
			throw new XAException(XAException.XAER_INVAL);
		}

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		if (flags == TMFAIL) {
			try {
				fileResourceManager.markTransactionForRollback(xid.toString());
			} catch (ResourceManagerException e) {
				logger.warn("Failed to mark transaction as rollback-only xid=" + xid, e);
				XAException xaException = new XAException(XAException.XAER_RMERR);
				xaException.initCause(e);
				throw xaException;
			}
		}
	}

	/**
	 * Tells the resource manager to forget about a heuristically completed transaction branch.
	 *
	 * This rolls back work done by the FileResourceManager for the transaction specified by xid.
	 *
	 * @param xid XA Transaction identifier
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null;
	 *                     XAER_RMERR if the rollback operation fails
	 */
	@Override
	public void forget(Xid xid) throws XAException {
		logger.debug("forget xid=" + xid);

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		try {
			fileResourceManager.rollbackTransaction(xid.toString());
		} catch (ResourceManagerException e) {
			logger.warn("Failed to forget (rollback) transaction xid=" + xid, e);
			XAException xaException = new XAException(XAException.XAER_RMERR);
			xaException.initCause(e);
			throw xaException;
		}
	}

	/**
	 * Returns the current transaction timeout value set for the FileResourceManager.
	 *
	 * If the method setTransactionTimeout was not called prior to invoking this method,
	 * the return value is the default timeout set for the FileResourceManager.
	 *
	 * @return The transaction timeout value in seconds.
	 */
	@Override
	public int getTransactionTimeout() {
		logger.debug("getTransactionTimeout");
		return (int)(fileResourceManager.getDefaultTransactionTimeout() / 1000);
	}

	/**
	 * This method is called to determine if the Commons Transaction FileResourceManager instance
	 * managed by this object is the same as the FileResourceManager instance managed by the object xares.
	 *
	 * If xares is null or if xares is not an XAFileResource then the method will return false.
	 *
	 * @param xares An XAResource object whose resource manager instance is to be compared with the
	 *              FileResourceManager instance managed by this object
	 * @return true if it's the same FileResourceManager instance; otherwise false.
	 */
	@Override
	public boolean isSameRM(XAResource xares) {
		logger.debug("isSameRM");

		boolean same = false;

		if ((xares != null) && (xares instanceof XAFileResource)) {
			XAFileResource fileResource = (XAFileResource)xares;

			if (fileResource.fileResourceManager.equals(fileResourceManager)) {
				same = true;
			}
		}

		return same;
	}

	/**
	 * Ask the FileResourceManager to prepare for a transaction commit of the transaction specified by xid.
	 *
	 * @param xid XA Transaction identifier
	 * @return XA_OK - the prepare has been successful.  An exception is raised if the prepare fails
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null;
	 *         and XA_RBROLLBACK if the prepare fails
	 */
	@Override
	public int prepare(Xid xid) throws XAException {
		logger.debug("prepare xid=" + xid);

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		try {
			if (fileResourceManager.prepareTransaction(xid.toString())
					== org.apache.commons.transaction.file.ResourceManager.PREPARE_FAILURE) {
				logger.warn("Failed to prepare transaction xid=" + xid);
				throw new XAException(XAException.XA_RBROLLBACK);
			}
		} catch (ResourceManagerException e) {
			logger.warn("Failed to prepare transaction xid=" + xid, e);
			XAException xaException = new XAException(XAException.XA_RBROLLBACK);
			xaException.initCause(e);
			throw xaException;
		}

		return XA_OK;
	}

	/**
	 * This method isn't implemented - FileResourceManager automatically rolls-back or rolls-forward a
	 * transaction in order to recover it on restart.  This limits this class's XA compliance.
	 *
	 * @param flag None, method not implemented.
	 * @return Nothing, method not implemented.
	 * @throws XAException Throws XAER_RMERR always as method not implemented.
	 */
	@Override
	public Xid[] recover(int flag) throws XAException {
		logger.debug("recover flag=" + flag);

		logger.error("recover method not implemented");
		throw new XAException(XAException.XAER_RMERR);
	}

	/**
	 * Instructs the FileResourceManager to roll back work done on behalf of a transaction branch.
	 *
	 * @param xid XA Transaction identifier
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null;
	 *                     XAER_RMERR if the rollback operation fails
	 */
	@Override
	public void rollback(Xid xid) throws XAException {
		logger.debug("rollback xid=" + xid);

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		try {
			fileResourceManager.rollbackTransaction(xid.toString());
		} catch (ResourceManagerException e) {
			logger.warn("Failed to rollback transaction xid=" + xid, e);
			XAException xaException = new XAException(XAException.XAER_RMERR);
			xaException.initCause(e);
			throw xaException;
		}
	}

	/**
	 * Sets the current transaction timeout value for the FileResourceManager instance.
	 * Once set, this timeout value is effective until setTransactionTimeout is invoked again
	 * with a different value.
	 *
	 * This method does not support passing zero to reset the timeout value to the default, as specified
	 * by the XAResource interface.  If zero is passed an exception is thrown.
	 *
	 * @param seconds The transaction timeout value in seconds.
	 * @return true - always
	 * @throws XAException Possible XAException values are: XAER_INVAL if the seconds argument is zero.
	 */
	@Override
	public boolean setTransactionTimeout(int seconds) throws XAException {
		logger.debug("setTransactionTimeout seconds=" + seconds);

		if (seconds == 0) {
			logger.error("Setting transaction timeout to default value not supported");
			throw new XAException(XAException.XAER_INVAL);
		} else {
			fileResourceManager.setDefaultTransactionTimeout(seconds * 1000);
		}

		return true;
	}

	/**
	 * Starts a transaction on FileResourceManager with transaction id xid.
	 *
	 * If TMJOIN is specified, the start applies to joining a transaction previously seen by the FileResourceManager.
	 *
	 * If TMJOIN is not specified and the transaction specified by xid has previously been seen by the FileResourceManager,
	 * an XAException with code XAER_DUPID is thrown.
	 *
	 * Flag TMRESUME is not supported.
	 *
	 * @param xid XA Transaction identifier
	 * @param flags Either TMNOFLAGS or TMJOIN
	 * @throws XAException Possible XAException values are: XAER_NOTA if the xid is null or if the xid does not exist on a TMJOIN;
	 *                     XAER_RMERR if the start operation fails;
	 *                     XAER_INVAL if the flags argument is not TMNOFLAGS or TMJOIN;
	 *                     XAER_DUPID if the the xid has already been seen by the FileResourceManager
	 */
	@Override
	public void start(Xid xid, int flags) throws XAException {
		logger.debug("start xid=" + xid + ", flags=" + flags);

		if (flags == TMRESUME) {
			logger.error("Flag TMRESUME not supported");
			throw new XAException(XAException.XAER_INVAL);
		} else if ((flags != TMNOFLAGS) && (flags != TMJOIN)) {
			logger.error("Invalid argument: flags=" + flags);
			throw new XAException(XAException.XAER_INVAL);
		}

		if (xid == null) {
			logger.error("xid cannot be null");
			throw new XAException(XAException.XAER_NOTA);
		}

		int transactionState = Status.STATUS_NO_TRANSACTION;

		try {
			transactionState = fileResourceManager.getTransactionState(xid.toString());
		} catch (ResourceManagerException e) {
			logger.error("Failed to get transaction state xid=" + xid, e);
			XAException xaException = new XAException(XAException.XAER_RMERR);
			xaException.initCause(e);
			throw xaException;
		}

		if (flags == TMJOIN) {
			// Join an existing transaction
			if (transactionState == Status.STATUS_NO_TRANSACTION) {
				logger.error("Transaction does not exist xid=" + xid);
				throw new XAException(XAException.XAER_NOTA);
			}
		} else {
			// Start a new transaction
			if (transactionState != Status.STATUS_NO_TRANSACTION) {
				logger.error("Duplicate transaction id xid=" + xid);
				throw new XAException(XAException.XAER_DUPID);
			}

			try {
				fileResourceManager.startTransaction(xid.toString());
			} catch (ResourceManagerException e) {
				logger.warn("Failed to start transaction xid=" + xid, e);
				XAException xaException = new XAException(XAException.XAER_RMERR);
				xaException.initCause(e);
				throw xaException;
			}
		}
	}

}
Advertisements
8 Comments
  1. Awesome initiative! 🙂 I will probably look into this further for using the idea in a production system. Will keep you posted when I get there. Thank you for taking the time!

  2. Tom Leach permalink

    Great post! I am looking to integrate a legacy system which writes its messages to the file system with JMS and bundle the reading of each message file, transformation of it’s content into a message, the sending of that message to a JMS queue and subsequent deletion of the file from the file system into a single transaction.

    I had already made use of the Commons transactions stuff for my file system IO but wasn’t sure if I could combine this with my JMS transaction into a single distributed transaction via JTA. It now appears it’s not impossible. I’ll give your ideas a spin and let you know what happens.

    Thanks

    • Matt permalink

      Thanks. I’m certainly interested to know how you get on.

  3. Alessandro Apostoli permalink

    Good stuff, really!! but looks like like I really miss something. I’d love an example on how to actually use it. I personally use spring, so I have transactional beans, what I’d like to do is embed a fileResourceManager into my transactions. I need a hint on how to pass the xid. Your “trivial” ejb based example should suffice, thanks a lot for your work!

    • Matt permalink

      I don’t know much about Spring so I won’t be able to help with any specifics, but I’ll certainly dig out the EJB code I wrote. It’s in a bit of a mess (I’m not actually using it, I have my own standalone Transaction Manager), and it probably has degraded to the point of not working by now! I’m up to my ears until at least Wednesday, but towards the end of the week I’ll see what I can do.

  4. Matt permalink

    @Alessandro – I’ve posted up the EJB test code here.

  5. Nitin permalink

    Hi Matthew,
    This post was an interesting read on how Apache Commons’s solution can be integrated with your XAResource class. I also note that you have pointed to resource adapter, connection etc.

    I had come across this blog many months earlier and since it is quite related to a project I have been working on, I thought I would share with you and others. The project is called XADisk (https://xadisk.dev.java.net/) and is open source for interested folks to look into. Comments/suggestions are always welcome.

    Thanks Matthew again for your efforts on this enlightening post…

    -Nitin

  6. Matt permalink

    @Nitin – Thanks for the comment and thanks for the link to your project. If it had existed two years ago it looks like exactly what I needed 🙂 I shall remember for the future…

Comments are closed.

%d bloggers like this: