[jboss-cvs] JBossAS SVN: r65842 - in trunk: server/src/main/org/jboss/jms/asf and 10 other directories.
jboss-cvs-commits at lists.jboss.org
jboss-cvs-commits at lists.jboss.org
Thu Oct 4 10:34:29 EDT 2007
Author: adrian at jboss.org
Date: 2007-10-04 10:34:28 -0400 (Thu, 04 Oct 2007)
New Revision: 65842
Added:
trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java
trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java
trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java
trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java
trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java
trunk/transaction/build.bat
trunk/transaction/build.sh
trunk/transaction/build.xml
trunk/transaction/component-info.xml
trunk/transaction/jbossbuild.xml
trunk/transaction/pom.xml
trunk/transaction/src/etc/default.mf
trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java
trunk/transaction/src/main/org/jboss/tm/GlobalId.java
trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java
trunk/transaction/src/main/org/jboss/tm/JBossXAException.java
trunk/transaction/src/main/org/jboss/tm/LocalId.java
trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java
trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java
trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java
trunk/transaction/src/main/org/jboss/tm/TMUtil.java
trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java
trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java
trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java
trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java
trunk/transaction/src/main/org/jboss/tm/TxManager.java
trunk/transaction/src/main/org/jboss/tm/XidFactory.java
trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java
trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java
trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java
trunk/transaction/src/main/org/jboss/tm/XidImpl.java
trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java
trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java
trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java
trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java
trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java
trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java
trunk/transaction/src/main/org/jboss/tm/package.html
trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java
trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java
trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java
trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java
trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java
trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java
trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java
trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java
trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java
trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java
trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java
trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java
trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java
trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java
trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java
trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java
trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java
trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java
trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java
trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java
trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java
trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java
trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java
trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java
trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java
trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java
trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java
trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java
trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java
trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java
trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java
trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java
trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java
trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java
trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java
Modified:
trunk/build/build.xml
Log:
[JBAS-4516] - Revert old TM delete so the server boots
Modified: trunk/build/build.xml
===================================================================
--- trunk/build/build.xml 2007-10-04 14:03:50 UTC (rev 65841)
+++ trunk/build/build.xml 2007-10-04 14:34:28 UTC (rev 65842)
@@ -110,6 +110,7 @@
<module name="system-jmx"/>
<module name="testsuite"/>
<module name="tomcat"/>
+ <module name="transaction"/>
<module name="varia"/>
<module name="webservices"/>
@@ -127,7 +128,8 @@
</group>
<group name="basic">
- <include modules="security,
+ <include modules="transaction,
+ security,
server,
deployment" />
@@ -614,6 +616,18 @@
<ant antfile="build-distr.xml" target="_module-management-all"/>
</target>
+ <!-- =========== -->
+ <!-- Transaction -->
+ <!-- =========== -->
+
+ <target name="_module-transaction-most">
+ <ant antfile="build-distr.xml" target="_module-transaction-most"/>
+ </target>
+
+ <target name="_module-transaction-all" depends="_module-transaction-most">
+ <ant antfile="build-distr.xml" target="_module-transaction-all"/>
+ </target>
+
<!-- ======= -->
<!-- Console -->
<!-- ======= -->
Copied: trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,90 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.jms.asf;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.ServerSessionPool;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Defines the model for creating <tt>ServerSessionPoolFactory</tt> objects. <p>
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public interface ServerSessionPoolFactory
+{
+ /**
+ * Set the name of the factory.
+ *
+ * @param name The name of the factory.
+ */
+ void setName(String name);
+
+ /**
+ * Get the name of the factory.
+ *
+ * @return The name of the factory.
+ */
+ String getName();
+
+ /**
+ * The <code>setXidFactory</code> method supplies the XidFactory that
+ * server sessions will use to get Xids to control local transactions.
+ *
+ * @param xidFactory a <code>XidFactoryMBean</code> value
+ */
+ void setXidFactory(XidFactoryMBean xidFactory);
+
+ /**
+ * The <code>getXidFactory</code> method returns the XidFactory that
+ * server sessions will use to get xids..
+ *
+ * @return a <code>XidFactoryMBean</code> value
+ */
+ XidFactoryMBean getXidFactory();
+
+ /**
+ * Create a new <tt>ServerSessionPool</tt>.
+ *
+ * @param destination the destination
+ * @param con the jms connection
+ * @param minSession the minimum number of sessions
+ * @param maxSession the maximum number of sessions
+ * @param keepAlive the time to keep sessions alive
+ * @param isTransacted whether the pool is transacted
+ * @param ack the acknowledegement method
+ * @param listener the listener
+ * @param useLocalTX whether to use local transactions
+ * @return A new pool.
+ * @throws JMSException for any error
+ */
+ ServerSessionPool getServerSessionPool(Destination destination, Connection con, int minSession, int maxSession,
+ long keepAlive, boolean isTransacted, int ack, boolean useLocalTX, MessageListener listener)
+ throws JMSException;
+}
\ No newline at end of file
Copied: trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,138 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.jms.asf;
+
+import javax.management.ObjectName;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.jboss.util.naming.NonSerializableFactory;
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * A loader for <tt>ServerSessionPools</tt>.
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a>.
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class ServerSessionPoolLoader extends ServiceMBeanSupport implements ServerSessionPoolLoaderMBean
+{
+ /** The factory used to create server session pools. */
+ private ServerSessionPoolFactory poolFactory;
+
+ /** The name of the pool. */
+ private String name;
+
+ /** The type of pool factory to use. */
+ private String poolFactoryClass;
+
+ private ObjectName xidFactory;
+
+ public void setPoolName(final String name)
+ {
+ this.name = name;
+ }
+
+ public String getPoolName()
+ {
+ return name;
+ }
+
+ public void setPoolFactoryClass(final String classname)
+ {
+ this.poolFactoryClass = classname;
+ }
+
+ public String getPoolFactoryClass()
+ {
+ return poolFactoryClass;
+ }
+
+ public ObjectName getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ public void setXidFactory(final ObjectName xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ protected void startService() throws Exception
+ {
+ XidFactoryMBean xidFactoryObj = (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+
+ Class cls = Class.forName(poolFactoryClass);
+ poolFactory = (ServerSessionPoolFactory) cls.newInstance();
+ poolFactory.setName(name);
+ poolFactory.setXidFactory(xidFactoryObj);
+ log.debug("initialized with pool factory: " + poolFactory);
+
+ InitialContext ctx = new InitialContext();
+ String name = poolFactory.getName();
+ String jndiname = "java:/" + name;
+ try
+ {
+ NonSerializableFactory.rebind(ctx, jndiname, poolFactory);
+ log.debug("pool factory " + name + " bound to " + jndiname);
+ }
+ finally
+ {
+ ctx.close();
+ }
+ }
+
+ protected void stopService()
+ {
+ // Unbind from JNDI
+ InitialContext ctx = null;
+ try
+ {
+ ctx = new InitialContext();
+ String name = poolFactory.getName();
+ String jndiname = "java:/" + name;
+
+ ctx.unbind(jndiname);
+ NonSerializableFactory.unbind(jndiname);
+ log.debug("pool factory " + name + " unbound from " + jndiname);
+ }
+ catch (NamingException ignore)
+ {
+ }
+ finally
+ {
+ if (ctx != null)
+ {
+ try
+ {
+ ctx.close();
+ }
+ catch (NamingException ignore)
+ {
+ }
+ }
+ }
+ }
+}
Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,520 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.ServerSession;
+import javax.jms.Session;
+import javax.jms.XASession;
+import javax.naming.InitialContext;
+import javax.transaction.Status;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import org.jboss.logging.Logger;
+import org.jboss.tm.TransactionManagerService;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * An implementation of ServerSession. <p>
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class StdServerSession implements Runnable, ServerSession, MessageListener
+{
+ /** Instance logger. */
+ static Logger log = Logger.getLogger(StdServerSession.class);
+
+ /** The server session pool which we belong to. */
+ private StdServerSessionPool serverSessionPool;
+
+ /** Our session resource. */
+ private Session session;
+
+ /** Our XA session resource. */
+ private XASession xaSession;
+
+ /** The transaction manager that we will use for transactions. */
+ private TransactionManager tm;
+
+ /**
+ * Use the session's XAResource directly if we have an JBossMQ XASession.
+ * this allows us to get around the TX timeout problem when you have
+ * extensive message processing.
+ */
+ private boolean useLocalTX;
+
+ /** The listener to delegate calls, to. In our case the container invoker. */
+ private MessageListener delegateListener;
+
+ private XidFactoryMBean xidFactory;
+
+ /**
+ * @deprecated
+ * @todo these appeared in jboss-head where are they used?
+ */
+ public TransactionManager getTransactionManager()
+ {
+ return tm;
+ }
+
+ /**
+ * @deprecated
+ * @todo these appeared in jboss-head where are they used?
+ */
+ public void setTransactionManager(TransactionManager transactionManager)
+ {
+ this.tm = transactionManager;
+ }
+
+ /**
+ * Create a <tt>StdServerSession</tt> .
+ *
+ * @param pool The server session pool which we belong to.
+ * @param session Our session resource.
+ * @param xaSession Our XA session resource.
+ * @param delegateListener Listener to call when messages arrives.
+ * @param useLocalTX Will this session be used in a global TX (we can optimize with 1 phase commit)
+ * @throws JMSException Transation manager was not found.
+ */
+ StdServerSession(final StdServerSessionPool pool,
+ final Session session,
+ final XASession xaSession,
+ final MessageListener delegateListener,
+ boolean useLocalTX,
+ final XidFactoryMBean xidFactory,
+ final TransactionManager tm)
+ throws JMSException
+ {
+ this.serverSessionPool = pool;
+ this.session = session;
+ this.xaSession = xaSession;
+ this.delegateListener = delegateListener;
+ if (xaSession == null)
+ useLocalTX = false;
+ this.useLocalTX = useLocalTX;
+ this.xidFactory = xidFactory;
+ this.tm = tm;
+
+ log.trace(this + " initializing (pool, session, xaSession, useLocalTX): " +
+ pool + ", " + session + ", " + xaSession + ", " + useLocalTX);
+
+ // Set out self as message listener
+ if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+ xaSession.setMessageListener(this);
+ else
+ session.setMessageListener(this);
+
+ if (tm == null)
+ {
+ InitialContext ctx = null;
+ try
+ {
+ ctx = new InitialContext();
+ this.tm = (TransactionManager) ctx.lookup(TransactionManagerService.JNDI_NAME);
+ }
+ catch (Exception e)
+ {
+ throw new JMSException("Transation manager was not found");
+ }
+ finally
+ {
+ if (ctx != null)
+ {
+ try
+ {
+ ctx.close();
+ }
+ catch (Exception ignore)
+ {
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the session. <p>
+ * <p/>
+ * This simply returns what it has fetched from the connection. It is up to
+ * the jms provider to typecast it and have a private API to stuff messages
+ * into it.
+ *
+ * @return The session.
+ * @throws JMSException Description of Exception
+ */
+ public Session getSession() throws JMSException
+ {
+ if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+ return xaSession;
+ else
+ return session;
+ }
+
+ /**
+ * Runs in an own thread, basically calls the session.run(), it is up to the
+ * session to have been filled with messages and it will run against the
+ * listener set in StdServerSessionPool. When it has send all its messages it
+ * returns.
+ */
+ public void run()
+ {
+ boolean trace = log.isTraceEnabled();
+
+ TransactionDemarcation td = null;
+ if (StdServerSessionPoolFactory.USE_OLD == false)
+ {
+ td = createTransactionDemarcation();
+ if (td == null)
+ return;
+ }
+ try
+ {
+ if (trace)
+ log.trace(this + " running...");
+
+ if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+ xaSession.run();
+ else
+ session.run();
+
+ if (trace)
+ log.trace(this + " run.");
+ }
+ catch (Throwable t)
+ {
+ log.error(this + " onMessage failed to run; setting rollback only", t);
+ if (td != null)
+ td.error();
+ }
+ finally
+ {
+ if (td != null)
+ td.end();
+
+ recycle();
+ }
+ }
+
+ /**
+ * Will get called from session for each message stuffed into it.
+ * <p/>
+ * Starts a transaction with the TransactionManager
+ * and enlists the XAResource of the JMS XASession if a XASession was
+ * available. A good JMS implementation should provide the XASession for use
+ * in the ASF. So we optimize for the case where we have an XASession. So,
+ * for the case where we do not have an XASession and the bean is not
+ * transacted, we have the unneeded overhead of creating a Transaction. I'm
+ * leaving it this way since it keeps the code simpler and that case should
+ * not be too common (JBossMQ provides XASessions).
+ */
+ public void onMessage(Message msg)
+ {
+ boolean trace = log.isTraceEnabled();
+
+ TransactionDemarcation td = null;
+ if (StdServerSessionPoolFactory.USE_OLD)
+ {
+ td = createTransactionDemarcation();
+ if (td == null)
+ return;
+ }
+ try
+ {
+ if (trace)
+ log.trace(this + " onMessage running (pool, session, xaSession, useLocalTX): " +
+ ", " + session + ", " + xaSession + ", " + useLocalTX);
+
+ // Call delegate listener
+ delegateListener.onMessage(msg);
+
+ if (trace)
+ log.trace(this + " onMessage finished");
+ }
+ catch (Throwable t)
+ {
+ log.error(this + " onMessage failed to run; setting rollback only", t);
+ if (td != null)
+ td.error();
+ }
+ finally
+ {
+ if (td != null)
+ td.end();
+ }
+ if (trace)
+ log.trace(this + " onMessage done");
+ }
+
+ /**
+ * Start the session and begin consuming messages.
+ *
+ * @throws JMSException No listener has been specified.
+ */
+ public void start() throws JMSException
+ {
+ log.trace(this + " starting invokes on server session");
+
+ if (session != null)
+ {
+ try
+ {
+ serverSessionPool.getExecutor().execute(this);
+ }
+ catch (InterruptedException ignore)
+ {
+ }
+ }
+ else
+ {
+ throw new JMSException(this + " no listener has been specified");
+ }
+ }
+
+ /**
+ * Called by the ServerSessionPool when the sessions should be closed.
+ */
+ void close()
+ {
+ log.trace(this + " closing.");
+
+ if (session != null)
+ {
+ try
+ {
+ session.close();
+ }
+ catch (Exception ignore)
+ {
+ }
+
+ session = null;
+ }
+
+ if (xaSession != null)
+ {
+ try
+ {
+ xaSession.close();
+ }
+ catch (Exception ignore)
+ {
+ }
+ xaSession = null;
+ }
+
+ log.debug("closed");
+ }
+
+ /**
+ * This method is called by the ServerSessionPool when it is ready to be
+ * recycled intot the pool
+ */
+ void recycle()
+ {
+ boolean trace = log.isTraceEnabled();
+ if (trace)
+ log.trace(this + " recycling");
+ serverSessionPool.recycle(this);
+ if (trace)
+ log.trace(this + " recycled");
+ }
+
+ TransactionDemarcation createTransactionDemarcation()
+ {
+ try
+ {
+ return new TransactionDemarcation();
+ }
+ catch (Throwable t)
+ {
+ log.error(this + " error creating transaction demarcation ", t);
+ return null;
+ }
+ }
+
+ private class TransactionDemarcation
+ {
+ boolean trace = log.isTraceEnabled();
+
+ // Used if run with useLocalTX if true
+ Xid localXid = null;
+ boolean localRollbackFlag = false;
+ // Used if run with useLocalTX if false
+ Transaction trans = null;
+
+ public TransactionDemarcation() throws Throwable
+ {
+ if (useLocalTX)
+ {
+ // Use JBossMQ One Phase Commit to commit the TX
+ localXid = xidFactory.newXid();//new XidImpl();
+ XAResource res = xaSession.getXAResource();
+ res.start(localXid, XAResource.TMNOFLAGS);
+
+ if (trace)
+ log.trace(StdServerSession.this + " using optimized 1p commit to control TX. xid=" + localXid);
+ }
+ else
+ {
+
+ // Use the TM to control the TX
+ tm.begin();
+ try
+ {
+ trans = tm.getTransaction();
+
+ if (trace)
+ log.trace(StdServerSession.this + " using tx=" + trans);
+
+ if (xaSession != null)
+ {
+ XAResource res = xaSession.getXAResource();
+ if (!trans.enlistResource(res))
+ {
+ throw new JMSException("could not enlist resource");
+ }
+ if (trace)
+ log.trace(StdServerSession.this + " XAResource '" + res + "' enlisted.");
+ }
+ }
+ catch (Throwable t)
+ {
+ try
+ {
+ tm.rollback();
+ }
+ catch (Throwable ignored)
+ {
+ log.trace(StdServerSession.this + " ignored error rolling back after failed enlist", ignored);
+ }
+ throw t;
+ }
+ }
+ }
+
+ public void error()
+ {
+ if (useLocalTX)
+ {
+ // Use JBossMQ One Phase Commit to commit the TX
+ localRollbackFlag = true;
+ }
+ else
+ {
+ // Mark for tollback TX via TM
+ try
+ {
+ // The transaction will be rolledback in the finally
+ if (trace)
+ log.trace(StdServerSession.this + " using TM to mark TX for rollback tx=" + trans);
+ trans.setRollbackOnly();
+ }
+ catch (Throwable t)
+ {
+ log.error(StdServerSession.this + " failed to set rollback only", t);
+ }
+ }
+ }
+
+ public void end()
+ {
+ try
+ {
+ if (useLocalTX)
+ {
+ if (localRollbackFlag == true)
+ {
+ if (trace)
+ log.trace(StdServerSession.this + " using optimized 1p commit to rollback TX xid=" + localXid);
+
+ XAResource res = xaSession.getXAResource();
+ res.end(localXid, XAResource.TMSUCCESS);
+ res.rollback(localXid);
+
+ }
+ else
+ {
+ if (trace)
+ log.trace(StdServerSession.this + " using optimized 1p commit to commit TX xid=" + localXid);
+
+ XAResource res = xaSession.getXAResource();
+ res.end(localXid, XAResource.TMSUCCESS);
+ res.commit(localXid, true);
+ }
+ }
+ else
+ {
+ // Use the TM to commit the Tx (assert the correct association)
+ Transaction currentTx = tm.getTransaction();
+ if (trans.equals(currentTx) == false)
+ throw new IllegalStateException("Wrong tx association: expected " + trans + " was " + currentTx);
+
+ // Marked rollback
+ if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK)
+ {
+ if (trace)
+ log.trace(StdServerSession.this + " rolling back JMS transaction tx=" + trans);
+ // actually roll it back
+ tm.rollback();
+
+ // NO XASession? then manually rollback.
+ // This is not so good but
+ // it's the best we can do if we have no XASession.
+ if (xaSession == null && serverSessionPool.isTransacted())
+ {
+ session.rollback();
+ }
+ }
+ else if (trans.getStatus() == Status.STATUS_ACTIVE)
+ {
+ // Commit tx
+ // This will happen if
+ // a) everything goes well
+ // b) app. exception was thrown
+ if (trace)
+ log.trace(StdServerSession.this + " commiting the JMS transaction tx=" + trans);
+ tm.commit();
+
+ // NO XASession? then manually commit. This is not so good but
+ // it's the best we can do if we have no XASession.
+ if (xaSession == null && serverSessionPool.isTransacted())
+ {
+ session.commit();
+ }
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ log.error(StdServerSession.this + " failed to commit/rollback", t);
+ }
+ }
+ }
+}
Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,413 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.ServerSession;
+import javax.jms.ServerSessionPool;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.XAQueueConnection;
+import javax.jms.XAQueueSession;
+import javax.jms.XASession;
+import javax.jms.XATopicConnection;
+import javax.jms.XATopicSession;
+import javax.transaction.TransactionManager;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.XidFactoryMBean;
+
+import EDU.oswego.cs.dl.util.concurrent.Executor;
+import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
+import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
+
+/**
+ * Implementation of ServerSessionPool.
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class StdServerSessionPool implements ServerSessionPool
+{
+ /** The thread group which session workers will run. */
+ private static ThreadGroup threadGroup = new ThreadGroup("ASF Session Pool Threads");
+
+ /** Instance logger. */
+ private final Logger log = Logger.getLogger(this.getClass());
+
+ /** The minimum size of the pool */
+ private int minSize;
+
+ /** The size of the pool. */
+ private int poolSize;
+
+ /** The message acknowledgment mode. */
+ private int ack;
+
+ /** Is the bean container managed? */
+ private boolean useLocalTX;
+
+ /** True if this is a transacted session. */
+ private boolean transacted;
+
+ /** The destination. */
+ private Destination destination;
+
+ /** The session connection. */
+ private Connection con;
+
+ /** The message listener for the session. */
+ private MessageListener listener;
+
+ /** The list of ServerSessions. */
+ private List sessionPool;
+
+ /** The executor for processing messages? */
+ private PooledExecutor executor;
+
+ /** Used to signal when the Pool is being closed down */
+ private boolean closing = false;
+
+ /** Used during close down to wait for all server sessions to be returned and closed. */
+ private int numServerSessions = 0;
+
+ private XidFactoryMBean xidFactory;
+
+ private TransactionManager tm;
+
+ /**
+ * Construct a <tt>StdServerSessionPool</tt> using the default pool size.
+ *
+ * @param destination the destination
+ * @param con connection to get sessions from
+ * @param transacted transaction mode when not XA (
+ * @param ack ackmode when not XA
+ * @param listener the listener the sessions will call
+ * @param minSession minumum number of sessions in the pool
+ * @param maxSession maximum number of sessions in the pool
+ * @param keepAlive the time to keep sessions alive
+ * @param xidFactory the xid factory
+ * @param tm the transaction manager
+ * @exception JMSException Description of Exception
+ */
+ public StdServerSessionPool(final Destination destination,
+ final Connection con,
+ final boolean transacted,
+ final int ack,
+ final boolean useLocalTX,
+ final MessageListener listener,
+ final int minSession,
+ final int maxSession,
+ final long keepAlive,
+ final XidFactoryMBean xidFactory,
+ final TransactionManager tm)
+ throws JMSException
+ {
+ this.destination = destination;
+ this.con = con;
+ this.ack = ack;
+ this.listener = listener;
+ this.transacted = transacted;
+ this.minSize = minSession;
+ this.poolSize = maxSession;
+ this.sessionPool = new ArrayList(maxSession);
+ this.useLocalTX = useLocalTX;
+ this.xidFactory = xidFactory;
+ this.tm = tm;
+ // setup the worker pool
+ executor = new MyPooledExecutor(poolSize);
+ executor.setMinimumPoolSize(minSize);
+ executor.setKeepAliveTime(keepAlive);
+ executor.waitWhenBlocked();
+ executor.setThreadFactory(new DefaultThreadFactory());
+
+ // finish initializing the session
+ create();
+ log.debug("Server Session pool set up");
+ }
+
+ /**
+ * Get a server session.
+ *
+ * @return A server session.
+ * @throws JMSException Failed to get a server session.
+ */
+ public ServerSession getServerSession() throws JMSException
+ {
+ if( log.isTraceEnabled() )
+ log.trace("getting a server session");
+ ServerSession session = null;
+
+ try
+ {
+ while (true)
+ {
+ synchronized (sessionPool)
+ {
+ if (closing)
+ {
+ throw new JMSException("Cannot get session after pool has been closed down.");
+ }
+ else if (sessionPool.size() > 0)
+ {
+ session = (ServerSession)sessionPool.remove(0);
+ break;
+ }
+ else
+ {
+ try
+ {
+ sessionPool.wait();
+ }
+ catch (InterruptedException ignore)
+ {
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new JMSException("Failed to get a server session: " + e);
+ }
+
+ if( log.isTraceEnabled() )
+ log.trace("using server session: " + session);
+ return session;
+ }
+
+ /**
+ * Clear the pool, clear out both threads and ServerSessions,
+ * connection.stop() should be run before this method.
+ */
+ public void clear()
+ {
+ synchronized (sessionPool)
+ {
+ // FIXME - is there a runaway condition here. What if a
+ // ServerSession are taken by a ConnecionConsumer? Should we set
+ // a flag somehow so that no ServerSessions are recycled and the
+ // ThreadPool won't leave any more threads out.
+ closing = true;
+
+ log.debug("Clearing " + sessionPool.size() + " from ServerSessionPool");
+
+ Iterator iter = sessionPool.iterator();
+ while (iter.hasNext())
+ {
+ StdServerSession ses = (StdServerSession)iter.next();
+ // Should we do anything to the server session?
+ ses.close();
+ numServerSessions--;
+ }
+
+ sessionPool.clear();
+ sessionPool.notifyAll();
+ }
+
+ //Must be outside synchronized block because of recycle method.
+ executor.shutdownAfterProcessingCurrentlyQueuedTasks();
+
+ //wait for all server sessions to be returned.
+ synchronized (sessionPool)
+ {
+ while (numServerSessions > 0)
+ {
+ try
+ {
+ sessionPool.wait();
+ }
+ catch (InterruptedException ignore)
+ {
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the executor we are using.
+ *
+ * @return The Executor value
+ */
+ Executor getExecutor()
+ {
+ return executor;
+ }
+
+ // --- Protected messages for StdServerSession to use
+
+ /**
+ * Returns true if this server session is transacted.
+ *
+ * @return The Transacted value
+ */
+ boolean isTransacted()
+ {
+ return transacted;
+ }
+
+ /**
+ * Recycle a server session.
+ *
+ * @param session Description of Parameter
+ */
+ void recycle(StdServerSession session)
+ {
+ synchronized (sessionPool)
+ {
+ if (closing)
+ {
+ session.close();
+ numServerSessions--;
+ if (numServerSessions == 0)
+ {
+ //notify clear thread.
+ sessionPool.notifyAll();
+ }
+ }
+ else
+ {
+ sessionPool.add(session);
+ sessionPool.notifyAll();
+ if( log.isTraceEnabled() )
+ log.trace("recycled server session: " + session);
+ }
+ }
+ }
+
+ private void create() throws JMSException
+ {
+ for (int index = 0; index < poolSize; index++)
+ {
+ // Here is the meat, that MUST follow the spec
+ Session ses = null;
+ XASession xaSes = null;
+
+ log.debug("initializing with connection: " + con);
+
+ if (destination instanceof Topic && con instanceof XATopicConnection)
+ {
+ xaSes = ((XATopicConnection)con).createXATopicSession();
+ ses = ((XATopicSession)xaSes).getTopicSession();
+ }
+ else if (destination instanceof Queue && con instanceof XAQueueConnection)
+ {
+ xaSes = ((XAQueueConnection)con).createXAQueueSession();
+ ses = ((XAQueueSession)xaSes).getQueueSession();
+ }
+ else if (destination instanceof Topic && con instanceof TopicConnection)
+ {
+ ses = ((TopicConnection)con).createTopicSession(transacted, ack);
+ log.warn("Using a non-XA TopicConnection. " +
+ "It will not be able to participate in a Global UOW");
+ }
+ else if (destination instanceof Queue && con instanceof QueueConnection)
+ {
+ ses = ((QueueConnection)con).createQueueSession(transacted, ack);
+ log.warn("Using a non-XA QueueConnection. " +
+ "It will not be able to participate in a Global UOW");
+ }
+ else
+ {
+ throw new JMSException("Connection was not reconizable: " + con + " for destination " + destination);
+ }
+
+ // create the server session and add it to the pool - it is up to the
+ // server session to set the listener
+ StdServerSession serverSession = new StdServerSession(this, ses, xaSes,
+ listener, useLocalTX, xidFactory, tm);
+
+ sessionPool.add(serverSession);
+ numServerSessions++;
+
+ log.debug("added server session to the pool: " + serverSession);
+ }
+ }
+
+ /**
+ * A pooled executor where the minimum pool size
+ * threads are kept alive
+ */
+ private static class MyPooledExecutor extends PooledExecutor
+ {
+ public MyPooledExecutor(int poolSize)
+ {
+ super(poolSize);
+ }
+
+ protected Runnable getTask() throws InterruptedException
+ {
+ Runnable task = null;
+ while ((task = super.getTask()) == null && keepRunning());
+ return task;
+ }
+
+ /**
+ * We keep running unless we are told to shutdown
+ * or there are more than minimumPoolSize_ threads in the pool
+ *
+ * @return whether to keep running
+ */
+ protected synchronized boolean keepRunning()
+ {
+ if (shutdown_)
+ return false;
+
+ return poolSize_ <= minimumPoolSize_;
+ }
+ }
+
+ private static class DefaultThreadFactory implements ThreadFactory
+ {
+ private static int count = 0;
+ private static synchronized int nextCount()
+ {
+ return count ++;
+ }
+
+ /**
+ * Create a new Thread for the given Runnable
+ *
+ * @param command The Runnable to pass to Thread
+ * @return The newly created Thread
+ */
+ public Thread newThread(final Runnable command)
+ {
+ String name = "JMS SessionPool Worker-" + nextCount();
+ Thread thread = new Thread(threadGroup, command, name);
+ thread.setDaemon(true);
+ return thread;
+ }
+ }
+}
Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,109 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software 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
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import java.io.Serializable;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.ServerSessionPool;
+import javax.transaction.TransactionManager;
+
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * An implementation of ServerSessionPoolFactory.
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class StdServerSessionPoolFactory implements ServerSessionPoolFactory, Serializable
+{
+ private static final long serialVersionUID = 4969432475779524576L;
+
+ public static final boolean USE_OLD;
+
+ static
+ {
+ USE_OLD = ((Boolean) AccessController.doPrivileged(new PrivilegedAction()
+ {
+ public Object run()
+ {
+ return new Boolean(System.getProperty("org.jboss.jms.asf.useold", "false"));
+ }
+ })).booleanValue();
+ }
+
+ /** The name of this factory. */
+ private String name;
+
+ private XidFactoryMBean xidFactory;
+
+ private TransactionManager transactionManager;
+
+ public StdServerSessionPoolFactory()
+ {
+ super();
+ }
+
+ public void setName(final String name)
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setXidFactory(final XidFactoryMBean xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ public XidFactoryMBean getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ public void setTransactionManager(TransactionManager transactionManager)
+ {
+ this.transactionManager = transactionManager;
+ }
+
+ public TransactionManager getTransactionManager()
+ {
+ return transactionManager;
+ }
+
+ public ServerSessionPool getServerSessionPool(Destination destination, Connection con, int minSession, int maxSession, long keepAlive, boolean isTransacted, int ack, boolean useLocalTX, MessageListener listener) throws JMSException
+ {
+ ServerSessionPool pool = new StdServerSessionPool(destination, con, isTransacted, ack, useLocalTX, listener, minSession, maxSession, keepAlive, xidFactory, transactionManager);
+ return pool;
+ }
+}
Added: trunk/transaction/build.bat
===================================================================
--- trunk/transaction/build.bat (rev 0)
+++ trunk/transaction/build.bat 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,89 @@
+ at echo off
+REM ======================================================================
+REM
+REM This is the main entry point for the build system.
+REM
+REM Users should be sure to execute this file rather than 'ant' to ensure
+REM the correct version is being used with the correct configuration.
+REM
+REM ======================================================================
+REM
+REM $Id: build.bat 24242 2004-10-04 20:13:37Z andd $
+REM
+REM Authors:
+REM Jason Dillon <jason at planet57.com>
+REM Sacha Labourey <sacha.labourey at cogito-info.ch>
+REM
+
+REM ******************************************************
+REM Ignore the ANT_HOME variable: we want to use *our*
+REM ANT version and associated JARs.
+REM ******************************************************
+REM Ignore the users classpath, cause it might mess
+REM things up
+REM ******************************************************
+
+SETLOCAL
+
+set CLASSPATH=
+set ANT_HOME=
+set ANT_OPTS=-Djava.protocol.handler.pkgs=org.jboss.net.protocol -Dbuild.script=build.bat
+
+REM ******************************************************
+REM - "for" loops have been unrolled for compatibility
+REM with some WIN32 systems.
+REM ******************************************************
+
+set NAMES=tools;tools\ant;tools\apache\ant
+set SUBFOLDERS=..;..\..;..\..\..;..\..\..\..
+
+REM ******************************************************
+REM ******************************************************
+
+SET EXECUTED=FALSE
+for %%i in (%NAMES%) do call :subLoop %%i %1 %2 %3 %4 %5 %6
+
+goto :EOF
+
+
+REM ******************************************************
+REM ********* Search for names in the subfolders *********
+REM ******************************************************
+
+:subLoop
+for %%j in (%SUBFOLDERS%) do call :testIfExists %%j\%1\bin\ant.bat %2 %3 %4 %5 %6 %7
+
+goto :EOF
+
+
+REM ******************************************************
+REM ************ Test if ANT Batch file exists ***********
+REM ******************************************************
+
+:testIfExists
+if exist %1 call :BatchFound %1 %2 %3 %4 %5 %6 %7 %8
+
+goto :EOF
+
+
+REM ******************************************************
+REM ************** Batch file has been found *************
+REM ******************************************************
+
+:BatchFound
+if (%EXECUTED%)==(FALSE) call :ExecuteBatch %1 %2 %3 %4 %5 %6 %7 %8
+set EXECUTED=TRUE
+
+goto :EOF
+
+REM ******************************************************
+REM ************* Execute Batch file only once ***********
+REM ******************************************************
+
+:ExecuteBatch
+echo Calling %1 %2 %3 %4 %5 %6 %7 %8
+call %1 %2 %3 %4 %5 %6 %7 %8
+
+:end
+
+if "%NOPAUSE%" == "" pause
Property changes on: trunk/transaction/build.bat
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/transaction/build.sh
===================================================================
--- trunk/transaction/build.sh (rev 0)
+++ trunk/transaction/build.sh 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,172 @@
+#!/bin/sh
+### ====================================================================== ###
+## ##
+## This is the main entry point for the build system. ##
+## ##
+## Users should be sure to execute this file rather than 'ant' to ensure ##
+## the correct version is being used with the correct configuration. ##
+## ##
+### ====================================================================== ###
+
+# $Id: build.sh 24242 2004-10-04 20:13:37Z andd $
+
+PROGNAME=`basename $0`
+DIRNAME=`dirname $0`
+GREP="grep"
+ROOT="/"
+
+# Ignore user's ANT_HOME if it is set
+ANT_HOME=""
+
+# the default search path for ant
+ANT_SEARCH_PATH="\
+ tools
+ tools/ant \
+ tools/apache/ant \
+ ant"
+
+# the default build file name
+ANT_BUILD_FILE="build.xml"
+
+# the default arguments
+ANT_OPTIONS="-find $ANT_BUILD_FILE"
+
+# Use the maximum available, or set MAX_FD != -1 to use that
+MAX_FD="maximum"
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false;
+darwin=false;
+case "`uname`" in
+ CYGWIN*)
+ cygwin=true
+ ;;
+
+ Darwin*)
+ darwin=true
+ ;;
+esac
+
+#
+# Helper to complain.
+#
+die() {
+ echo "${PROGNAME}: $*"
+ exit 1
+}
+
+#
+# Helper to complain.
+#
+warn() {
+ echo "${PROGNAME}: $*"
+}
+
+#
+# Helper to source a file if it exists.
+#
+maybe_source() {
+ for file in $*; do
+ if [ -f "$file" ]; then
+ . $file
+ fi
+ done
+}
+
+search() {
+ search="$*"
+ for d in $search; do
+ ANT_HOME="`pwd`/$d"
+ ANT="$ANT_HOME/bin/ant"
+ if [ -x "$ANT" ]; then
+ # found one
+ echo $ANT_HOME
+ break
+ fi
+ done
+}
+
+#
+# Main function.
+#
+main() {
+ # if there is a build config file. then source it
+ maybe_source "$DIRNAME/build.conf" "$HOME/.build.conf"
+
+ # Increase the maximum file descriptors if we can
+ if [ $cygwin = "false" ]; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ]; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
+ # use the system max
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ]; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query system maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+ fi
+
+ # try the search path
+ ANT_HOME=`search $ANT_SEARCH_PATH`
+
+ # try looking up to root
+ if [ "x$ANT_HOME" = "x" ]; then
+ target="build"
+ _cwd=`pwd`
+
+ while [ "x$ANT_HOME" = "x" ] && [ "$cwd" != "$ROOT" ]; do
+ cd ..
+ cwd=`pwd`
+ ANT_HOME=`search $ANT_SEARCH_PATH`
+ done
+
+ # make sure we get back
+ cd $_cwd
+
+ if [ "$cwd" != "$ROOT" ]; then
+ found="true"
+ fi
+
+ # complain if we did not find anything
+ if [ "$found" != "true" ]; then
+ die "Could not locate Ant; check \$ANT or \$ANT_HOME."
+ fi
+ fi
+
+ # make sure we have one
+ ANT=$ANT_HOME/bin/ant
+ if [ ! -x "$ANT" ]; then
+ die "Ant file is not executable: $ANT"
+ fi
+
+ # need to specify planet57/buildmagic protocol handler package
+ ANT_OPTS="-Djava.protocol.handler.pkgs=org.jboss.net.protocol"
+
+ # setup some build properties
+ ANT_OPTS="$ANT_OPTS -Dbuild.script=$0"
+
+ # change to the directory where the script lives so users are not forced
+ # to be in the same directory as build.xml
+ cd $DIRNAME
+
+ # export some stuff for ant
+ export ANT ANT_HOME ANT_OPTS
+
+ # execute in debug mode, or simply execute
+ if [ "x$ANT_DEBUG" != "x" ]; then
+ /bin/sh -x $ANT $ANT_OPTIONS "$@"
+ else
+ exec $ANT $ANT_OPTIONS "$@"
+ fi
+}
+
+##
+## Bootstrap
+##
+
+main "$@"
Property changes on: trunk/transaction/build.sh
___________________________________________________________________
Name: svn:executable
+ *
Added: trunk/transaction/build.xml
===================================================================
--- trunk/transaction/build.xml (rev 0)
+++ trunk/transaction/build.xml 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE project [
+ <!ENTITY buildmagic SYSTEM "../tools/etc/buildmagic/buildmagic.ent">
+ <!ENTITY libraries SYSTEM "../thirdparty/libraries.ent">
+ <!ENTITY modules SYSTEM "../tools/etc/buildmagic/modules.ent">
+]>
+
+<!-- ====================================================================== -->
+<!-- -->
+<!-- JBoss, the OpenSource J2EE webOS -->
+<!-- -->
+<!-- Distributable under LGPL license. -->
+<!-- See terms of license at http://www.gnu.org. -->
+<!-- -->
+<!-- ====================================================================== -->
+
+<!-- $Id: build.xml 64576 2007-08-14 15:48:44Z adrian at jboss.org $ -->
+
+<project default="main" name="JBoss/Transaction">
+
+ <!-- ================================================================== -->
+ <!-- Setup -->
+ <!-- ================================================================== -->
+
+ <!--
+ | Include the common Buildmagic elements.
+ |
+ | This defines several different targets, properties and paths.
+ | It also sets up the basic extention tasks amoung other things.
+ -->
+
+ &buildmagic;
+
+
+ <!-- ================================================================== -->
+ <!-- Initialization -->
+ <!-- ================================================================== -->
+
+ <!--
+ | Initialize the build system. Must depend on '_buildmagic:init'.
+ | Other targets should depend on 'init' or things will mysteriously fail.
+ -->
+
+ <target name="init" unless="init.disable" depends="_buildmagic:init">
+ </target>
+
+
+ <!-- ================================================================== -->
+ <!-- Configuration -->
+ <!-- ================================================================== -->
+
+ <!--
+ | Configure the build system.
+ |
+ | This target is invoked by the Buildmagic initialization logic and
+ | should contain module specific configuration elements.
+ -->
+
+ <target name="configure" unless="configure.disable">
+
+ <!-- =================== -->
+ <!-- Basic Configuration -->
+ <!-- =================== -->
+
+ <!-- Module name(s) & version -->
+ <property name="module.name" value="jboss-transaction"/>
+ <property name="module.Name" value="JBoss Transaction"/>
+ <property name="module.version" value="DEV"/>
+
+ <!-- ========= -->
+ <!-- Libraries -->
+ <!-- ========= -->
+ &libraries;
+
+ <!-- The combined library classpath -->
+ <path id="library.classpath">
+ <path refid="apache.log4j.classpath"/>
+ <path refid="apache.commons.classpath"/>
+ <path refid="oswego.concurrent.classpath"/>
+ <path refid="jboss.remoting.classpath"/>
+ <path refid="jboss.integration.classpath"/>
+ </path>
+
+ <!-- ======= -->
+ <!-- Modules -->
+ <!-- ======= -->
+
+ &modules;
+
+ <!-- The combined dependent module classpath -->
+ <path id="dependentmodule.classpath">
+ <path refid="jboss.jboss.javaee.classpath"/>
+ <path refid="jboss.common.core.classpath"/>
+ <path refid="jboss.common.logging.spi.classpath"/>
+ <path refid="jboss.common.logging.log4j.classpath"/>
+ <path refid="jboss.common.logging.jdk.classpath"/>
+ <path refid="jboss.systemjmx.classpath"/>
+ <path refid="jboss.system.classpath"/>
+ <path refid="jboss.j2se.classpath"/>
+ <path refid="jboss.main.classpath"/>
+ </path>
+
+ <!-- ===== -->
+ <!-- Tasks -->
+ <!-- ===== -->
+
+ <!-- Where source files live -->
+ <property name="source.java" value="${module.source}/main"/>
+ <property name="source.etc" value="${module.source}/etc"/>
+
+ <!-- Where build generated files will go -->
+ <property name="build.reports" value="${module.output}/reports"/>
+ <property name="build.classes" value="${module.output}/classes"/>
+ <property name="build.lib" value="${module.output}/lib"/>
+ <property name="build.api" value="${module.output}/api"/>
+ <property name="build.etc" value="${module.output}/etc"/>
+ <property name="build.gen-src" value="${module.output}/gen-src"/>
+
+ <!-- Install/Release structure -->
+ <property name="install.id" value="${module.name}-${module.version}"/>
+ <property name="release.id" value="${install.id}"/>
+ <property name="install.root" value="${module.output}/${install.id}"/>
+
+ <!-- The combined thirdparty classpath -->
+ <path id="thirdparty.classpath">
+ <path refid="library.classpath"/>
+ <path refid="dependentmodule.classpath"/>
+ </path>
+
+ <!-- classpath and local.classpath must have a value using with a path -->
+ <property name="classpath" value=""/>
+ <property name="local.classpath" value=""/>
+
+ <!-- The classpath required to build classes. -->
+ <path id="javac.classpath">
+ <pathelement path="${classpath}"/>
+ <pathelement path="${local.classpath}"/>
+ <path refid="thirdparty.classpath"/>
+ </path>
+
+ <!-- The classpath required to build javadocs. -->
+ <path id="javadoc.classpath">
+ <path refid="javac.classpath"/>
+ </path>
+
+ <!-- Packages to include when generating api documentation -->
+ <property name="javadoc.packages" value="org.jboss.*"/>
+
+ <!-- Override JUnit defaults -->
+ <property name="junit.timeout" value="240000"/> <!-- 4 minutes -->
+ <property name="junit.batchtest.todir" value="${build.reports}"/>
+ <property name="junit.jvm.options" value="-Ddummy"/>
+
+ <!-- xdoclet -->
+ <path id="xdoclet.task.classpath">
+ <path refid="javac.classpath"/>
+ <fileset dir="${xdoclet.xdoclet.lib}">
+ <include name="**/*.jar"/>
+ </fileset>
+ </path>
+ <property name="xdoclet.task.classpath" refid="xdoclet.task.classpath"/>
+ </target>
+
+
+ <!-- ================================================================== -->
+ <!-- Compile -->
+ <!-- ================================================================== -->
+
+ <!--
+ | Compile everything.
+ |
+ | This target should depend on other compile-* targets for each
+ | different type of compile that needs to be performed, short of
+ | documentation compiles.
+ -->
+
+ <target name="compile"
+ description="Compile all source files."
+ depends="compile-classes, compile-etc"/>
+
+ <!-- Compile all class files -->
+ <target name="compile-classes" depends="init">
+ <mkdir dir="${build.classes}"/>
+ <javac destdir="${build.classes}"
+ optimize="${javac.optimize}"
+ target="${javac.target}"
+ source="${javac.source}"
+ debug="${javac.debug}"
+ depend="${javac.depend}"
+ verbose="${javac.verbose}"
+ deprecation="${javac.deprecation}"
+ includeAntRuntime="${javac.include.ant.runtime}"
+ includeJavaRuntime="${javac.include.java.runtime}"
+ includes="${javac.includes}"
+ excludes="${javac.excludes}"
+ failonerror="${javac.fail.onerror}">
+
+ <src path="${source.java}"/>
+ <exclude name="org/jboss/tm/recovery/test/**"/>
+ <classpath refid="javac.classpath"/>
+ </javac>
+ </target>
+
+ <!-- Compile etc files (manifests and such) -->
+ <target name="compile-etc" depends="init">
+ <mkdir dir="${build.etc}"/>
+ <copy todir="${build.etc}" filtering="yes">
+ <fileset dir="${source.etc}">
+ <include name="**/*"/>
+ </fileset>
+ </copy>
+ </target>
+
+
+ <!-- ================================================================== -->
+ <!-- Archives -->
+ <!-- ================================================================== -->
+
+ <!--
+ | Build all jar files.
+ -->
+ <target name="jars"
+ description="Builds all jar files."
+ depends="_buildmagic:build-bypass-check"
+ unless="build-bypass.on">
+
+ <call target="compile"/>
+
+ <mkdir dir="${build.lib}"/>
+
+ <!-- Build ${module.name}.jar -->
+ <jar jarfile="${build.lib}/${module.name}.jar" manifest="${build.etc}/default.mf">
+ <fileset dir="${build.classes}">
+ <include name="org/jboss/tm/**"/>
+ </fileset>
+ </jar>
+
+ <!-- Build ${module.name}-client.jar -->
+ <jar jarfile="${build.lib}/${module.name}-client.jar" manifest="${build.etc}/default.mf">
+ <fileset dir="${build.classes}">
+ <include name="**/*Exception.class"/>
+ <include name="**/*Error.class"/>
+ <include name="**/*MBean.class"/>
+
+ <!--
+ | jason: not really sure what is meant for the client...
+ | someone should trim this list.
+ -->
+ <include name="org/jboss/tm/**"/>
+ </fileset>
+ </jar>
+
+ <!--
+ | JBoss/Testsuite Support
+ -->
+
+ <!-- testsuite-support.jar -->
+ <jar jarfile="${build.lib}/testsuite-support.jar" manifest="${build.etc}/default.mf">
+ <fileset dir="${build.classes}">
+ <include name="org/jboss/tm/**"/>
+ </fileset>
+ </jar>
+
+ <!-- Update the build marker to allow bypassing -->
+ <touch file="${build-bypass.marker}"/>
+
+ </target>
+
+ <!-- ================================================================== -->
+ <!-- Install & Release -->
+ <!-- ================================================================== -->
+
+ <target name="install"
+ description="Install the structure for a release."
+ depends="all, _buildmagic:install:default"/>
+
+ <target name="release" depends="install"/>
+
+ <target name="release-zip"
+ description="Builds a ZIP distribution."
+ depends="release, _buildmagic:release:zip"/>
+
+ <target name="release-tar"
+ description="Builds a TAR distribution."
+ depends="release, _buildmagic:release:tar"/>
+
+ <target name="release-tgz"
+ description="Builds a TAR-GZ distribution."
+ depends="release, _buildmagic:release:tgz"/>
+
+ <target name="release-all"
+ description="Builds a distribution for each archive type."
+ depends="release-zip, release-tgz"/>
+
+
+ <!-- ================================================================== -->
+ <!-- Cleaning -->
+ <!-- ================================================================== -->
+
+ <!-- Clean up all build output -->
+ <target name="clean"
+ description="Cleans up most generated files."
+ depends="_buildmagic:clean">
+ </target>
+
+ <!-- Clean up all generated files -->
+ <target name="clobber"
+ description="Cleans up all generated files."
+ depends="_buildmagic:clobber, clean">
+ </target>
+
+
+ <!-- ================================================================== -->
+ <!-- Misc. -->
+ <!-- ================================================================== -->
+
+ <target name="main"
+ description="Executes the default target (most)."
+ depends="most"/>
+
+ <target name="all"
+ description="Builds everything."
+ depends="jars, docs"/>
+
+ <target name="most"
+ description="Builds almost everything."
+ depends="jars"/>
+
+ <target name="help"
+ description="Show this help message."
+ depends="_buildmagic:help:standard"/>
+
+</project>
Added: trunk/transaction/component-info.xml
===================================================================
--- trunk/transaction/component-info.xml (rev 0)
+++ trunk/transaction/component-info.xml 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,26 @@
+<project name="transaction-component-info">
+ <!-- ============================================================ -->
+ <!-- Transaction -->
+ <!-- ============================================================ -->
+
+ <component id="transaction"
+ module="jboss-transaction"
+ version="5.0-SNAPSHOT"
+ specTitle="JBoss"
+ specVersion="5.0.0"
+ specVendor="JBoss (http://www.jboss.org)"
+ implTitle="JBoss"
+ implURL="http://www.jboss.org"
+ implVersion="5.0.0"
+ implVendor="JBoss.org"
+ >
+ <artifact id="jboss-transaction.jar"/>
+ <artifact id="jboss-transaction-client.jar"/>
+ <artifact id="transaction-testsuite-support.jar"/>
+ <export>
+ <include input="jboss-transaction.jar"/>
+ </export>
+ </component>
+
+
+</project>
Added: trunk/transaction/jbossbuild.xml
===================================================================
--- trunk/transaction/jbossbuild.xml (rev 0)
+++ trunk/transaction/jbossbuild.xml 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+
+<!--
+ JBoss, the OpenSource J2EE webOS
+
+ Distributable under LGPL license.
+ See terms of license at gnu.org.
+-->
+
+<!-- ================================================================== -->
+<!-- Transaction component definition -->
+<!-- ================================================================== -->
+
+<project name="project"
+ default="build"
+ basedir="."
+>
+ <import file="../tools/etc/jbossbuild/tasks.xml"/>
+ <import file="component-info.xml"/>
+
+ <!-- =============================================================== -->
+ <!-- The component definition -->
+ <!-- =============================================================== -->
+
+ <componentdef component="transaction" description="JBoss Transaction">
+
+ <!-- ============================================================ -->
+ <!-- The main source -->
+ <!-- ============================================================ -->
+
+ <source id="main"
+ excludes="org/jboss/tm/recovery/test/**">
+
+ <include component="oswego-concurrent"/>
+ <include component="apache-log4j"/>
+
+ <include component="common"/>
+ <include component="j2se"/>
+ <include component="j2ee"/>
+ <include component="jmx"/>
+ <include component="system"/>
+ <include component="jboss/remoting"/>
+ </source>
+
+ <!-- ============================================================ -->
+ <!-- jboss-transaction.jar -->
+ <!-- ============================================================ -->
+
+ <artifactdef artifact="jboss-transaction.jar">
+ <include input="main">
+ <include pattern="org/jboss/tm/**"/>
+ </include>
+ </artifactdef>
+
+ <!-- ============================================================ -->
+ <!-- jboss-transaction-client.jar -->
+ <!-- ============================================================ -->
+
+ <artifactdef artifact="jboss-transaction-client.jar">
+ <include input="main">
+ <include pattern="org/jboss/tm/**"/>
+ </include>
+ </artifactdef>
+
+ <!-- ============================================================ -->
+ <!-- transaction-testsuite-support.jar -->
+ <!-- ============================================================ -->
+
+ <artifactdef artifact="transaction-testsuite-support.jar">
+ <include input="main">
+ <include pattern="org/jboss/tm/**"/>
+ </include>
+ </artifactdef>
+
+ </componentdef>
+
+ <!-- Generate the targets -->
+ <generate generate="transaction"/>
+
+</project>
Added: trunk/transaction/pom.xml
===================================================================
--- trunk/transaction/pom.xml (rev 0)
+++ trunk/transaction/pom.xml 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,73 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.jboss.jbossas</groupId>
+ <artifactId>jboss-as-parent</artifactId>
+ <version>5.0.0-SNAPSHOT</version>
+ <relativePath>../build/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.jboss.jbossas</groupId>
+ <artifactId>jboss-as-transaction</artifactId>
+ <packaging>jar</packaging>
+ <name>JBoss Application Server Transaction</name>
+ <url>http://www.jboss.com/products/jbossas</url>
+ <description>JBoss Application Server (transaction module)</description>
+ <build>
+ <sourceDirectory>src/main</sourceDirectory>
+ <resources>
+ <resource>
+ <directory>src/resources</directory>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>org/jboss/tm/recovery/test/*.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+ <dependencies>
+ <!-- Compile (global dependencies) -->
+ <dependency>
+ <groupId>org.jboss.jbossas</groupId>
+ <artifactId>jboss-as-j2se</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.jbossas</groupId>
+ <artifactId>jboss-as-system-jmx</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.jbossas</groupId>
+ <artifactId>jboss-as-main</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>javax.transaction</groupId>
+ <artifactId>jta</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss</groupId>
+ <artifactId>jboss-common-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jboss</groupId>
+ <artifactId>jboss-common-logging-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss</groupId>
+ <artifactId>jboss-transaction-spi</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jboss</groupId>
+ <artifactId>remoting</artifactId>
+ </dependency>
+ </dependencies>
+
+</project>
Added: trunk/transaction/src/etc/default.mf
===================================================================
--- trunk/transaction/src/etc/default.mf (rev 0)
+++ trunk/transaction/src/etc/default.mf 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Created-By: @java.vm.version@ (@java.vm.vendor@)
+Specification-Title: @specification.title@
+Specification-Version: @specification.version@
+Specification-Vendor: @specification.vendor@
+Implementation-Title: @implementation.title@
+Implementation-URL: @implementation.url@
+Implementation-Version: @implementation.version@
+Implementation-Vendor: @implementation.vendor@
+Implementation-Vendor-Id: @implementation.vendor.id@
Added: trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Coordinator;
+
+/**
+ * Interface of a factory that creates references to DTM or OTS coordinators.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface CoordinatorFactory
+{
+ /**
+ * Creates a reference to a DTM or OTS coordinator associated with the
+ * transaction with a given <code>localId</code>.
+ * @param localId the local id of a transaction
+ * @return a <code>Coordinator</code> reference.
+ */
+ Coordinator createCoordinator(long localId);
+}
Added: trunk/transaction/src/main/org/jboss/tm/GlobalId.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/GlobalId.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/GlobalId.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,226 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.io.IOException;
+import javax.transaction.xa.Xid;
+
+/**
+ * This object encapsulates the global transaction ID of a transaction.
+ * It is similar to an Xid, but holds only the GlobalId part.
+ * This implementation is immutable and always serializable at runtime.
+ *
+ * @see XidImpl
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class GlobalId
+ implements java.io.Externalizable
+{
+ static final long serialVersionUID = -230282197045463046L;
+
+ private static String thisClassName;
+
+ static {
+ thisClassName = GlobalId.class.getName();
+ thisClassName =
+ thisClassName.substring(thisClassName.lastIndexOf('.') + 1);
+ }
+
+ /**
+ * Format id of this instance.
+ */
+ private int formatId;
+
+ /**
+ * Global transaction id of this instance.
+ * The coding of this class depends on the fact that this variable is
+ * initialized in the constructor and never modified. References to
+ * this array are never given away, instead a clone is delivered.
+ */
+ private byte[] globalId;
+
+ /**
+ * Hash code of this instance. For a native GlobalId (one whose formatId
+ * is XidImpl.JBOSS_FORMAT_ID), this is really a sequence number.
+ */
+ private int hash;
+
+ // Constructors --------------------------------------------------
+
+ public GlobalId()
+ {
+ // Used for Externalizable support
+ }
+
+ /**
+ * Create a new instance. This constructor is package-private, as it
+ * trusts the hash parameter to be good.
+ */
+ GlobalId(int formatId, byte[] globalId, int hash)
+ {
+ this.formatId = formatId;
+ this.globalId = globalId;
+ this.hash = hash;
+ }
+
+ /**
+ * Create a new instance. This constructor is public <em>only</em>
+ * to get around a class loader problem; it should be package-private.
+ */
+ public GlobalId(int formatId, byte[] globalId)
+ {
+ this.formatId = formatId;
+ this.globalId = globalId;
+ hash = computeHash();
+ }
+
+ public GlobalId(Xid xid)
+ {
+ formatId = xid.getFormatId();
+ globalId = xid.getGlobalTransactionId();
+ if (xid instanceof XidImpl && formatId == XidImpl.JBOSS_FORMAT_ID)
+ {
+ // native GlobalId: use its hash code (a sequence number)
+ hash = xid.hashCode();
+ }
+ else
+ {
+ // foreign GlobalId: do the hash computation
+ hash = computeHash();
+ }
+ }
+
+ public GlobalId(int formatId, int bqual_length, byte[] tid)
+ {
+ this.formatId = formatId;
+ if (bqual_length == 0)
+ globalId = tid;
+ else
+ {
+ int len = tid.length - bqual_length;
+ globalId = new byte[len];
+ System.arraycopy(tid, 0, globalId, 0, len);
+ }
+ hash = computeHash();
+ }
+
+ // Public --------------------------------------------------------
+
+ /**
+ * Returns the global transaction id of this transaction.
+ */
+ public byte[] getGlobalTransactionId()
+ {
+ return (byte[])globalId.clone();
+ }
+
+ /**
+ * Returns the format identifier of this transaction.
+ */
+ public int getFormatId()
+ {
+ return formatId;
+ }
+
+ /**
+ * Compare for equality.
+ *
+ * Instances are considered equal if they both refer to the same
+ * global transaction id.
+ */
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof GlobalId) {
+ GlobalId other = (GlobalId)obj;
+
+ if (formatId != other.formatId)
+ return false;
+
+ if (globalId == other.globalId)
+ return true;
+
+ if (globalId.length != other.globalId.length)
+ return false;
+
+ int len = globalId.length;
+ for (int i = 0; i < len; ++i)
+ if (globalId[i] != other.globalId[i])
+ return false;
+
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return hash;
+ }
+
+ public String toString()
+ {
+ return thisClassName + "[formatId=" + formatId
+ + ", globalId=" + new String(globalId).trim()
+ + ", hash=" + hash + "]";
+ }
+
+ // Externalizable implementation ---------------------------------
+
+ public void writeExternal(java.io.ObjectOutput out)
+ throws IOException
+ {
+ out.writeInt(formatId);
+ out.writeObject(globalId);
+ }
+
+ public void readExternal(java.io.ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ formatId = in.readInt();
+ globalId = (byte[])in.readObject();
+ hash = computeHash();
+ }
+
+ // Private -------------------------------------------------------
+
+ private int computeHash()
+ {
+ if (formatId == XidImpl.JBOSS_FORMAT_ID)
+ {
+ return (int)TransactionImpl.xidFactory.extractLocalIdFrom(globalId);
+ }
+ else
+ {
+ int len = globalId.length;
+ int hashval = 0;
+
+ // TODO: use a better hash function
+ for (int i = 0; i < len; ++i)
+ hashval = 3 * globalId[i] + hashval;
+ hashval += formatId;
+ return hashval;
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,129 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import javax.transaction.RollbackException;
+import org.jboss.util.NestedThrowable;
+
+/**
+ * JBossRollbackException.java
+ *
+ *
+ * Created: Sun Feb 9 22:45:03 2003
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @version
+ */
+
+public class JBossRollbackException
+ extends RollbackException
+ implements NestedThrowable
+{
+ static final long serialVersionUID = 2924502280803535350L;
+
+ Throwable t;
+
+ public JBossRollbackException()
+ {
+ super();
+ }
+
+ public JBossRollbackException(final String message)
+ {
+ super(message);
+ }
+
+ public JBossRollbackException(final Throwable t)
+ {
+ super();
+ this.t = t;
+ }
+
+ public JBossRollbackException(final String message, final Throwable t)
+ {
+ super(message);
+ this.t = t;
+ }
+
+ // Implementation of org.jboss.util.NestedThrowable
+
+ public Throwable getNested()
+ {
+ return t;
+ }
+
+ public Throwable getCause()
+ {
+ return t;
+ }
+
+ /**
+ * Returns the composite throwable message.
+ *
+ * @return The composite throwable message.
+ */
+ public String getMessage() {
+ return NestedThrowable.Util.getMessage(super.getMessage(), t);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to the
+ * specified print stream.
+ *
+ * @param stream Stream to print to.
+ */
+ public void printStackTrace(final PrintStream stream)
+ {
+ if (t == null || NestedThrowable.PARENT_TRACE_ENABLED)
+ {
+ super.printStackTrace(stream);
+ }
+ NestedThrowable.Util.print(t, stream);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to the
+ * specified print writer.
+ *
+ * @param writer Writer to print to.
+ */
+ public void printStackTrace(final PrintWriter writer)
+ {
+ if (t == null || NestedThrowable.PARENT_TRACE_ENABLED)
+ {
+ super.printStackTrace(writer);
+ }
+ NestedThrowable.Util.print(t, writer);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to
+ * <tt>System.err</tt>.
+ */
+ public void printStackTrace()
+ {
+ printStackTrace(System.err);
+ }
+
+}// JBossRollbackException
Added: trunk/transaction/src/main/org/jboss/tm/JBossXAException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/JBossXAException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/JBossXAException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,177 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.io.PrintWriter;
+import java.io.PrintStream;
+import javax.transaction.xa.XAException;
+
+import org.jboss.util.NestedThrowable;
+
+/**
+ * Thrown to indicate a problem with a xaresource related operation.
+ *
+ * <p>
+ * Properly displays linked exception (ie. nested exception)
+ * when printing the stack trace.
+ *
+ * @version <tt>$Revision: 37459 $</tt>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ */
+public class JBossXAException
+ extends XAException
+ implements NestedThrowable
+{
+ /** The servial version uid*/
+ private static final long serialVersionUID = 6614203184612359692L;
+
+ /** The linked exception */
+ Throwable linked;
+
+ /**
+ * Rethrow as an xa exception if it is not already
+ *
+ * @param message the message
+ * @param t the original exception
+ * @throws XAException the xa exception
+ */
+ public static void rethrowAsXAException(String message, Throwable t) throws XAException
+ {
+ if (t instanceof XAException)
+ throw (XAException) t;
+ else
+ throw new JBossXAException(message, t);
+ }
+
+ /**
+ * Construct a <tt>JBossXAException</tt> with the specified detail
+ * message.
+ *
+ * @param msg Detail message.
+ */
+ public JBossXAException(final String msg)
+ {
+ super(msg);
+ }
+
+ /**
+ * Construct a <tt>JBossXAException</tt> with the specified detail
+ * message and error code.
+ *
+ * @param code Error code.
+ */
+ public JBossXAException(final int code)
+ {
+ super(code);
+ }
+
+ /**
+ * Construct a <tt>JBossXAException</tt> with the specified detail
+ * message and linked <tt>Exception</tt>.
+ *
+ * @param msg Detail message.
+ * @param linked Linked <tt>Exception</tt>.
+ */
+ public JBossXAException(final String msg, final Throwable linked)
+ {
+ super(msg);
+ this.linked = linked;
+ }
+
+ /**
+ * Construct a <tt>JBossXAException</tt> with the specified
+ * linked <tt>Exception</tt>.
+ *
+ * @param linked Linked <tt>Exception</tt>.
+ */
+ public JBossXAException(final Throwable linked)
+ {
+ this(linked.getMessage(), linked);
+ }
+
+ /**
+ * Return the nested <tt>Throwable</tt>.
+ *
+ * @return Nested <tt>Throwable</tt>.
+ */
+ public Throwable getNested()
+ {
+ return linked;
+ }
+
+ /**
+ * Return the nested <tt>Throwable</tt>.
+ *
+ * <p>For JDK 1.4 compatibility.
+ *
+ * @return Nested <tt>Throwable</tt>.
+ */
+ public Throwable getCause()
+ {
+ return linked;
+ }
+
+ /**
+ * Returns the composite throwable message.
+ *
+ * @return The composite throwable message.
+ */
+ public String getMessage()
+ {
+ return NestedThrowable.Util.getMessage(super.getMessage(), linked);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to the
+ * specified print stream.
+ *
+ * @param stream Stream to print to.
+ */
+ public void printStackTrace(final PrintStream stream)
+ {
+ if (linked == null || NestedThrowable.PARENT_TRACE_ENABLED)
+ super.printStackTrace(stream);
+ NestedThrowable.Util.print(linked, stream);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to the
+ * specified print writer.
+ *
+ * @param writer Writer to print to.
+ */
+ public void printStackTrace(final PrintWriter writer)
+ {
+ if (linked == null || NestedThrowable.PARENT_TRACE_ENABLED)
+ super.printStackTrace(writer);
+ NestedThrowable.Util.print(linked, writer);
+ }
+
+ /**
+ * Prints the composite message and the embedded stack trace to
+ * <tt>System.err</tt>.
+ */
+ public void printStackTrace()
+ {
+ printStackTrace(System.err);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/LocalId.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/LocalId.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/LocalId.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,211 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+/**
+ * LocalId is a wrapper for a long value that identifies a transaction within
+ * a JBoss server. This implementation is immutable and serializable at
+ * runtime.
+ *
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class LocalId
+ implements java.io.Externalizable
+{
+ static final long serialVersionUID = 2076780468014328911L;
+
+ /**
+ * Long value that identifies a transaction within a JBoss server.
+ * This is really a sequence number generated by the XidFactory.
+ */
+ private long value;
+
+
+ // Static --------------------------------------------------------
+
+ /** Number of bits in the "transaction generation" part of the local id */
+ private static final int TX_GEN_SIZE = 20;
+
+ /** Number of bits in a long */
+ private static final int LONG_SIZE = 64;
+
+ /** Number of bits in the "transaction number" part of the local id */
+ private static final int TX_NUM_SIZE = LONG_SIZE - TX_GEN_SIZE;
+
+ /** Mask to extract a transaction generation from the lower part of a long */
+ private static final long TX_GEN_MASK = (1L << TX_GEN_SIZE) - 1;
+
+ /** Mask to extract the transaction number from a local id */
+ private static final long TX_NUM_MASK = (1L << TX_NUM_SIZE) - 1;
+
+ /**
+ * Assemble a local id as a long value with a generation number in its high
+ * part (the <code>TX_GEN_SIZE</code> most significant bits) and a
+ * transaction number in its low part (the <code>TX_NUM_SIZE</code> least
+ * significant bits).
+ *
+ * @param genNumber the generation number
+ * @param txNumber the transaction number.
+ * @return a local transaction id.
+ */
+ public static long assemble(int genNumber, long txNumber)
+ {
+ return (((long) genNumber) << TX_NUM_SIZE) | (txNumber & TX_NUM_MASK);
+ }
+
+ /**
+ * Extracts the generation number from a local id value.
+ *
+ * @param localIdValue a local id value.
+ * @return the generation number in the <code>TX_GEN_SIZE</code> most
+ * significant bits of the local id.
+ */
+ public static int toGenerationNumber(long localIdValue)
+ {
+ return (int)((localIdValue >> TX_NUM_SIZE) & TX_GEN_MASK);
+ }
+
+ /**
+ * Extracts a transaction number from a local id value.
+ *
+ * @param localIdValue a local id value.
+ * @return the transaction number in the <code>TX_NUM_SIZE</code> least
+ * significant bits of the local id.
+ */
+ public static long toTransactionNumber(long localIdValue)
+ {
+ return localIdValue & TX_NUM_MASK;
+ }
+
+ /**
+ * Converts a local id value into a string.
+ *
+ * @param l a local id value
+ * @return a string of the form "<em>gn</em>:<em>tn</em>", where
+ * <em>gn</em> is the generation number and <em>tn</em> is
+ * the transaction number.
+ */
+ public static String toString(long l)
+ {
+ return "" + toGenerationNumber(l) + ":" + toTransactionNumber(l);
+ }
+
+ /**
+ * Copies a local id value into a byte array.
+ *
+ * @param localIdValue a local id value
+ * @param dst the destination byte array
+ * @param dstBegin the index of the first element of <code>dst</code> that
+ * will receive a byte of the local id.
+ */
+ public static void toByteArray(long localIdValue, byte[] dst, int dstBegin)
+ {
+ dst[dstBegin + 0] = (byte)(0xff & (localIdValue >>> 56));
+ dst[dstBegin + 1] = (byte)(0xff & (localIdValue >>> 48));
+ dst[dstBegin + 2] = (byte)(0xff & (localIdValue >>> 40));
+ dst[dstBegin + 3] = (byte)(0xff & (localIdValue >>> 32));
+ dst[dstBegin + 4] = (byte)(0xff & (localIdValue >>> 24));
+ dst[dstBegin + 5] = (byte)(0xff & (localIdValue >>> 16));
+ dst[dstBegin + 6] = (byte)(0xff & (localIdValue >>> 8));
+ dst[dstBegin + 7] = (byte)(0xff & (localIdValue >>> 0));
+ }
+
+ /**
+ * Gets a local id value from a byte array/
+ *
+ * @param src the source byte array
+ * @param srcBegin the index of the first element of <code>src</code> that
+ * contains a byte of the local id.
+ * @return a local id extracted from the byte array.
+ */
+ public static long fromByteArray(byte[] src, int srcBegin)
+ {
+ return ((long)(src[srcBegin + 0] & 0xff) << 56)
+ | ((long)(src[srcBegin + 1] & 0xff) << 48)
+ | ((long)(src[srcBegin + 2] & 0xff) << 40)
+ | ((long)(src[srcBegin + 3] & 0xff) << 32)
+ | ((long)(src[srcBegin + 4] & 0xff) << 24)
+ | ((long)(src[srcBegin + 5] & 0xff) << 16)
+ | ((long)(src[srcBegin + 6] & 0xff) << 8)
+ | ((long)(src[srcBegin + 7] & 0xff));
+ }
+
+ // Constructors --------------------------------------------------
+
+ /**
+ * No-arg constructor for Externalizable support.
+ */
+ public LocalId()
+ {
+ }
+
+ /**
+ * Create a new instance. This constructor is public <em>only</em>
+ * to get around a class loader problem; it should be package-private.
+ */
+ public LocalId(long value)
+ {
+ this.value = value;
+ }
+
+ public LocalId(XidImpl xid)
+ {
+ this(xid.getLocalIdValue());
+ }
+
+ // Public --------------------------------------------------------
+
+ public long getValue()
+ {
+ return value;
+ }
+
+ /**
+ * Compare for equality.
+ */
+ public boolean equals(Object obj)
+ {
+ return (obj instanceof LocalId) ? (value == ((LocalId)obj).value)
+ : false;
+ }
+
+ public int hashCode()
+ {
+ return (int)value;
+ }
+
+ // Externalizable implementation ---------------------------------
+ public void writeExternal(java.io.ObjectOutput out)
+ throws java.io.IOException
+ {
+ out.writeLong(value);
+ }
+
+ public void readExternal(java.io.ObjectInput in)
+ throws java.io.IOException, ClassNotFoundException
+ {
+ value = in.readLong();
+ }
+
+}
+
Added: trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,84 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Coordinator;
+
+/**
+ * Interface of a factory that creates OTS propagation contexts.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface OTSContextFactory
+{
+ /**
+ * Creates an OTS context with the specified <code>formatId</code>,
+ * <code>globalId</code>, and <code>coordinator</code>. The OTS context
+ * will contain a null <code>Terminator</code> reference and an
+ * <code>org.omg.CosTransactions.Coordinator</code> reference extracted from
+ * the <code>coordinator</code> parameter, which must be an instance of
+ * <code>org.jboss.tm.iiop.wrapper.OTSCoordinatorWrapper</code>.
+ * It will contain zero in its timeout field. The caller should use the
+ * method <code>setTimeout</code> to set the timeout field of the newly
+ * created OTS context.
+ *
+ * @param formatId the format id to be stored in the OTS context
+ * @param globalId the global id to be stored in the OTS context
+ * @param coordinator an <code>org.jboss.tm.iiop.wrapper.OTSCoordinatorWrapper</code>
+ * containing the <code>org.omg.CosTransactions.Coordinator</code>
+ * reference to be stored in the OTS context
+ * @return an instance of <code>org.omg.CosTransactions.PropagationContext</code>.
+ */
+ Object createOTSContext(int formatId,
+ byte[] globalId,
+ Coordinator coordinator);
+
+ /**
+ * Creates an OTS context with the specified <code>formatId</code> and
+ * <code>globalId</code>, and with an
+ * <code>org.omg.CosTransactions.Coordinator</code> reference that
+ * corresponds to the local transaction whose local id is
+ * <code>coordinatorLocalId</code>. The OTS context will contain a null
+ * <code>Terminator</code> reference and zero in its timeout field.
+ * The caller should use the method <code>setTimeout</code> to set the
+ * timeout field of the newly created OTS context.
+ *
+ * @param formatId the format id to be stored in the OTS context
+ * @param globalId the global id to be stored in the OTS context
+ * @param coordinatorLocalId the local id of the transaction associated with
+ * the coordinator reference to be stored in the OTS context
+ * @return an instance of <code>org.omg.CosTransactions.PropagationContext</code>.
+ */
+ Object createOTSContext(int formatId,
+ byte[] globalId,
+ long coordinatorLocalId);
+
+ /**
+ * Sets the timeout field of the specified OTS context.
+ * @param otsContext an instance of <code>org.omg.CosTransactions.PropagationContext</code>
+ * @param timeout the timeout value, in seconds, to be stored in the OTS
+ * context.
+ */
+ void setTimeout(Object otsContext, int timeout);
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Resource;
+
+/**
+ * Interface of a factory that creates references to DTM or OTS resources.
+ * In order to register itself as a subcoordinator with the parent coordinator,
+ * a <code>TransactionImpl</code> instance needs to obtain a reference to a
+ * resource associated with itself. It calls a <code>ResourceFactory</code> to
+ * obtain that reference, which it then uses as the resource parameter in a
+ * call to <code>registerResource</code> on the parent coordinator.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface ResourceFactory
+{
+ /**
+ * Creates a reference to a DTM or OTS resource that corresponds to the
+ * transaction with a given <code>localId</code>.
+ * @param localId the local id of a transaction
+ * @return a <code>Resource</code> reference
+ */
+ Resource createResource(long localId);
+}
Added: trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,74 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+
+/**
+ * Converts stringfied references to remote <code>Resource</code>s and
+ * <code>RecoveryCoordinator</code>s back to remote references. This
+ * interface serves the purpose of avoiding a dependency from the transaction
+ * recovery module to the CORBA/OTS module.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface StringRemoteRefConverter
+{
+ /**
+ * Converts a stringfied reference to a remote <code>Resource</code>
+ * back to a remote reference.
+ *
+ * @param strResource a stringfied reference to a remote
+ * <code>Resource</code>
+ * @return a remote reference to the <code>Resource</code>.
+ */
+ Resource stringToResource(String strResource);
+
+ /**
+ * Converts a stringfied reference to a remote
+ * <code>RecoveryCoordinator</code> back to a remote reference.
+ *
+ * @param strRecCoordinator a stringfied reference to a remote
+ * <code>RecoveryCoordinator</code>
+ * @return a remote reference to the <code>RecoveryCoordinator</code>
+ */
+ RecoveryCoordinator stringToRecoveryCoordinator(String strRecCoordinator);
+
+ /**
+ * Takes a remote reference to a resource and converts it to a string.
+ *
+ * @param res a remote reference to a resource
+ * @return a string that represents the remote resource.
+ */
+ String resourceToString(Resource res);
+
+ /**
+ * Takes a remote reference to recovery coordinator and converts it to a
+ * string.
+ *
+ * @param recoveryCoord a remote reference to a recovery coordinator
+ * @return a string that represents the remote recovery coordinator.
+ */
+ String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord);
+}
Added: trunk/transaction/src/main/org/jboss/tm/TMUtil.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TMUtil.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TMUtil.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,94 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Transaction Manager utility methods.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class TMUtil
+{
+
+ private static final Logger log =
+ Logger.getLogger(TMUtil.class);
+
+ private static TransactionManager tm;
+
+ // enforce non-instantiability
+ private TMUtil()
+ {
+ }
+
+ /**
+ * Gets a <code>Transaction</code> instance given its <code>LocalId</code>.
+ */
+ public static Transaction getTransaction(LocalId localId)
+ {
+ TransactionPropagationContextImporter tpcImporter =
+ TransactionPropagationContextUtil.getTPCImporter();
+
+ return tpcImporter.importTransactionPropagationContext(localId);
+ }
+
+ /**
+ * Gets a reference to the transaction manager.
+ */
+ public static TransactionManager getTransactionManager()
+ {
+ if (tm == null)
+ {
+ try
+ {
+ Context ctx = new InitialContext();
+ tm = (TransactionManager)ctx.lookup("java:/TransactionManager");
+ }
+ catch (NamingException ex)
+ {
+ log.error("java:/TransactionManager lookup failed", ex);
+ }
+ }
+ return tm;
+ }
+
+ /**
+ * Time-out conversion from milliseconds to seconds.
+ */
+ public static int divideAndRoundUp(long m, long n)
+ {
+ long retval = m / n;
+
+ if ((m % n) != 0)
+ retval = retval + 1;
+ return (int)retval ;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,5402 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.lang.reflect.Proxy;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkException;
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrity;
+import org.jboss.tm.recovery.HeuristicStatus;
+import org.jboss.tm.recovery.LogRecord;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.RecoveryTestingException;
+import org.jboss.tm.recovery.TxCompletionHandler;
+import org.jboss.tm.recovery.XAResourceAccess;
+import org.jboss.tm.recovery.XAWork;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.TransactionAlreadyPreparedException;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.Vote;
+import org.jboss.util.timeout.Timeout;
+import org.jboss.util.timeout.TimeoutFactory;
+import org.jboss.util.timeout.TimeoutTarget;
+
+/**
+ * Our <code>Transaction</code> implementation.
+ *
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:marc.fleury at telkel.com">Marc Fleury</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @author <a href="mailto:dimitris at jboss.org">Dimitris Andreadis</a>
+ * @version $Revision: 64098 $
+ * @see TxManager
+ */
+public class TransactionImpl
+ implements Transaction, TimeoutTarget
+{
+ // Constants -----------------------------------------------------
+
+ /**
+ * Code meaning "no heuristics seen",
+ * must not be XAException.XA_HEURxxx
+ */
+ private static final int HEUR_NONE = 0;
+
+ /**
+ * Code meaning "heuristics seen, but no additional info is available",
+ * must not be XAException.XA_HEURxxx
+ */
+ private static final int HEUR_UNKNOWN = XAException.XA_RETRY;
+
+ // Resource states
+ private final static int RS_NEW = 0; // not yet enlisted
+ private final static int RS_ENLISTED = 1; // enlisted
+ private final static int RS_SUSPENDED = 2; // suspended
+ private final static int RS_ENDED = 3; // not associated
+ private final static int RS_VOTE_READONLY = 4; // voted read-only
+ private final static int RS_VOTE_OK = 5; // voted ok
+ private final static int RS_FORGOT = 6; // RM has forgotten
+ private final static int RS_COMMITTED = 7; // committed
+ private final static int RS_ROLLEDBACK = 8; // rolledback
+ private final static int RS_HEUR_OUTCOME = 8; // had an heuristic outcome
+ private final static int RS_ERROR = 9; // error condition (no use retrying)
+
+ // Attributes ----------------------------------------------------
+
+ /**
+ * True if trace messages should be logged.
+ */
+ private boolean trace = log.isTraceEnabled();
+
+ /**
+ * The ID of this transaction.
+ */
+ private XidImpl xid;
+
+ private HashSet threads = new HashSet(1);
+
+ private Map transactionLocalMap = Collections.synchronizedMap(new HashMap());
+
+ private Throwable cause;
+
+ /**
+ * True for a foreign transaction that has been imported either through an
+ * DTM/OTS transaction propagation context or via JCA transaction inflow;
+ * false for a locally started transaction.
+ */
+ private final boolean foreignTx;
+
+ /**
+ * This transaction's parent coordinator, or null if this transaction does
+ * not have a parent coordinator. A transaction with no parent coodinator
+ * is either a (locally started) root transaction or a foreign transaction
+ * that has been imported via JCA transaction inflow.
+ */
+ private Coordinator parentCoordinator = null;
+
+ /**
+ * The resource registered by transaction with the parent coordinator.
+ */
+ private Resource registeredResource = null;
+
+ /**
+ * This transaction's recovery coordinator, or null this transaction did not
+ * register itself as a remote resource with the parent coordinator.
+ */
+ private RecoveryCoordinator recoveryCoordinator = null;
+
+ /**
+ * The inbound branch qualifier, if this is a foreign transaction that has
+ * been imported via JCA transaction inflow, or null otherwise.
+ */
+ private byte[] inboundBranchQualifier = null;
+
+ /**
+ * The synchronizations to call back.
+ */
+ private Synchronization[] sync = new Synchronization[3];
+
+ /**
+ * Size of allocated synchronization array.
+ */
+ private int syncAllocSize = 3;
+
+ /**
+ * Count of synchronizations for this transaction.
+ */
+ private int syncCount = 0;
+
+ /**
+ * A list of the XAResources that have participated in this transaction.
+ */
+ private ArrayList xaResources = new ArrayList(3);
+
+ /**
+ * A list of the remote resources that have participated in this transaction.
+ */
+ private ArrayList remoteResources = new ArrayList(3);
+
+ /**
+ * The XAResource used in the last resource gambit
+ */
+ private EnlistedXAResource lastResource;
+
+ /**
+ * Flags that it is too late to enlist new resources.
+ */
+ private boolean resourcesEnded = false;
+
+ /**
+ * Last branch id used.
+ */
+ private long lastBranchId = 0;
+
+ /**
+ * Status of this transaction.
+ */
+ private int status;
+
+ /**
+ * The heuristics status of this transaction.
+ */
+ private int heuristicCode = HEUR_NONE;
+
+ /**
+ * The time when this transaction was started.
+ */
+ private long start;
+
+ /**
+ * The timeout handle for this transaction.
+ */
+ private Timeout timeout;
+
+ /**
+ * Timeout in millisecs
+ */
+ private long timeoutPeriod;
+
+ /**
+ * Mutex for thread-safety. This should only be changed in the
+ * <code>lock()</code> and <code>unlock()</code> methods.
+ */
+ private Thread locked = null;
+
+ /**
+ * The lock depth
+ */
+ private int lockDepth = 0;
+
+ /**
+ * Any current work associated with the transaction
+ */
+ private Work work;
+
+ /**
+ * Flags that we are done with this transaction and that it can be reused.
+ */
+ private boolean done = false;
+
+ /**
+ * This transaction's DTM propagation context.
+ */
+ private TxPropagationContext dtmPropagationContext = null;
+
+ /**
+ * This transaction's OTS propagation context.
+ */
+ private Object otsPropagationContext = null;
+
+ /**
+ * This transaction's TxCompletionHandler.
+ */
+ private TxCompletionHandler completionHandler = null;
+
+ /**
+ * Number of XA resources that reported transient problems.
+ */
+ private int xaResourcesToRetry = 0;
+
+ /**
+ * Number of remote resources that reported transient problems.
+ */
+ private int remoteResourcesToRetry = 0;
+
+ /**
+ * Timeout before calling <code>replayCompletion</code> on the recovery
+ * the coordinator when waiting for the coordinator in the prepared state.
+ */
+ private Timeout preparedTimeout = null;
+
+ /**
+ * Timeout before retrying commit or rollback calls on XA resources that
+ * reported transient problems.
+ */
+ private Timeout xaRetryTimeout = null;
+
+ /**
+ * List of <code>EnlistedXAResource</code> instances with heuristic
+ * decisions.
+ */
+ private List xaResourcesWithHeuristicDecisions = null;
+
+ /**
+ * List of <code>EnlistedRemoteResource</code> instances with heuristic
+ * decisions.
+ */
+ private List remoteResourcesWithHeuristicDecisions = null;
+
+ /**
+ * Counts how many resources were committed.
+ */
+ private int committedResources = 0;
+
+ /**
+ * Counts how many resources were rolled back.
+ */
+ private int rolledbackResources = 0;
+
+ /**
+ * True if this <code>TransactionImpl</code> could not reach a resource
+ * during the second phase of 2PC.
+ */
+ private boolean heuristicHazard = false;
+
+ // Static --------------------------------------------------------
+
+ /**
+ * Class logger, we don't want a new logger with every transaction.
+ */
+ private static Logger log = Logger.getLogger(TransactionImpl.class);
+
+ /**
+ * Factory for Xid instances of specified class.
+ * This is set from the <code>TransactionManagerService</code>
+ * MBean.
+ */
+ static XidFactoryBase xidFactory;
+
+ static XAExceptionFormatter xaExceptionFormatter;
+
+ /** The timeout factory */
+ static TimeoutFactory timeoutFactory = TimeoutFactory.getSingleton();
+
+ /**
+ * CoordinatorFactory that creates remote references to DTM coordinators,
+ * or null if the DTM is not employed.
+ */
+ private static CoordinatorFactory dtmCoordinatorFactory = null;
+
+ /**
+ * ResourceFactory that creates remote references to DTM resources,
+ * or null if the DTM is not employed.
+ */
+ private static ResourceFactory dtmResourceFactory = null;
+
+ /**
+ * ResourceFactory that creates remote references to OTS resources,
+ * or null if OTS is not employed.
+ */
+ private static ResourceFactory otsResourceFactory = null;
+
+ /**
+ * Factory that creates OTS transaction propagation contexts,
+ * or null if OTS is not employed.
+ */
+ private static OTSContextFactory otsContextFactory = null;
+
+ /**
+ * True if coordinator interposition is enabled.
+ */
+ private static boolean interpositionEnabled = false;
+
+ /**
+ * Object that converts between strings and remote references for
+ * DTM objects, or null if DTM is not employed.
+ */
+ private static StringRemoteRefConverter dtmStrRemoteRefConverter = null;
+
+ /**
+ * Object that converts between strings and remote references for
+ * OTS objects, or null if OTS is not employed.
+ */
+ private static StringRemoteRefConverter otsStrRemoteRefConverter = null;
+
+ /**
+ * This static code is only present for testing purposes so a
+ * tm can be usable without a lot of setup.
+ */
+ public static XidFactoryBase defaultXidFactory()
+ {
+ if (xidFactory == null)
+ {
+ XidFactoryImpl impl = new XidFactoryImpl();
+ impl.start();
+ xidFactory = impl;
+ }
+ return xidFactory;
+ }
+
+ /**
+ * Setter for <code>dtmCoordinatorFactory</code>.
+ */
+ static void setDTMCoordinatorFactory(CoordinatorFactory dtmCoordinatorFactory)
+ {
+ TransactionImpl.dtmCoordinatorFactory = dtmCoordinatorFactory;
+ }
+
+ /**
+ * Setter for <code>dtmResourceFactory</code>.
+ */
+ static void setDTMResourceFactory(ResourceFactory dtmResourceFactory)
+ {
+ TransactionImpl.dtmResourceFactory = dtmResourceFactory;
+ }
+
+ /**
+ * Setter for <code>otsResourceFactory</code>.
+ */
+ static void setOTSResourceFactory(ResourceFactory otsResourceFactory)
+ {
+ TransactionImpl.otsResourceFactory = otsResourceFactory;
+ }
+
+ /**
+ * Setter for <code>otsContextFactory</code>.
+ */
+ static void setOTSContextFactory(OTSContextFactory otsContextFactory)
+ {
+ TransactionImpl.otsContextFactory = otsContextFactory;
+ }
+
+ /**
+ * Setter for <code>interpositionEnabled</code>.
+ */
+ static void setInterpositionEnabled(boolean interpositionEnabled)
+ {
+ TransactionImpl.interpositionEnabled = interpositionEnabled;
+ }
+
+ /**
+ * Getter for <code>interpositionEnabled</code>.
+ */
+ static boolean getInterpositionEnabled()
+ {
+ return TransactionImpl.interpositionEnabled;
+ }
+
+ /**
+ * Setter for <code>dtmStrRemoteRefConverter</code>.
+ */
+ static void setDTMStrRemoteRefConverter(
+ StringRemoteRefConverter dtmStrRemoteRefConverter)
+ {
+ TransactionImpl.dtmStrRemoteRefConverter = dtmStrRemoteRefConverter;
+ }
+
+ /**
+ * Setter for <code>otsStrRemoteRefConverter</code>.
+ */
+ static void setOTSStrRemoteRefConverter(
+ StringRemoteRefConverter otsStrRemoteRefConverter)
+ {
+ TransactionImpl.otsStrRemoteRefConverter = otsStrRemoteRefConverter;
+ }
+
+ /**
+ * Converts a stringfied reference to a remote resource back to a remote
+ * reference.
+ *
+ * @param strResource a stringfied reference to a remote resource
+ * @return a remote reference to the resource.
+ */
+ static Resource stringToResource(String strResource)
+ {
+ if (strResource.startsWith("IOR:"))
+ {
+ if (otsStrRemoteRefConverter != null)
+ return otsStrRemoteRefConverter.stringToResource(strResource);
+ else
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ if (dtmStrRemoteRefConverter != null)
+ return dtmStrRemoteRefConverter.stringToResource(strResource);
+ else
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Converts a stringfied reference to a remote recovery coordinator back
+ * to a remote reference.
+ *
+ * @param strRecCoordinator a stringfied reference to a remote recovery
+ * coordinator
+ * @return a remote reference to the recovery coordinator.
+ */
+ static RecoveryCoordinator stringToRecoveryCoordinator(
+ String strRecCoordinator)
+ {
+ if (strRecCoordinator.startsWith("IOR:"))
+ {
+ if (otsStrRemoteRefConverter != null)
+ return otsStrRemoteRefConverter.stringToRecoveryCoordinator(
+ strRecCoordinator);
+ else
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ if (dtmStrRemoteRefConverter != null)
+ return dtmStrRemoteRefConverter.stringToRecoveryCoordinator(
+ strRecCoordinator);
+ else
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Takes a remote reference to a resource and converts it to a string.
+ *
+ * @param res a remote reference to a resource
+ * @return a string that represents the remote resource.
+ */
+ static String resourceToString(Resource res)
+ {
+ if (Proxy.isProxyClass(res.getClass()))
+ {
+ if (dtmStrRemoteRefConverter != null)
+ return dtmStrRemoteRefConverter.resourceToString(res);
+ else
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ if (otsStrRemoteRefConverter != null)
+ return otsStrRemoteRefConverter.resourceToString(res);
+ else
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Takes a remote reference to a recovery coordinator and converts it to a
+ * string.
+ *
+ * @param recoveryCoord a remote reference to a recovery coordinator
+ * @return a string that represents the remote recovery coordinator.
+ */
+ static String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+ {
+ if (Proxy.isProxyClass(recoveryCoord.getClass()))
+ {
+ if (dtmStrRemoteRefConverter != null)
+ return dtmStrRemoteRefConverter.recoveryCoordinatorToString(
+ recoveryCoord);
+ else
+ throw new IllegalArgumentException();
+ }
+ else
+ {
+ if (otsStrRemoteRefConverter != null)
+ return otsStrRemoteRefConverter.recoveryCoordinatorToString(
+ recoveryCoord);
+ else
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // Constructors --------------------------------------------------
+
+ /**
+ * Constructor for transactions started locally.
+ */
+ TransactionImpl(long timeout)
+ {
+ foreignTx = false;
+ xid = xidFactory.newXid();
+
+ status = Status.STATUS_ACTIVE;
+
+ start = System.currentTimeMillis();
+ this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+ this.timeoutPeriod = timeout;
+ if (trace)
+ log.trace("Created new instance for tx=" + toString());
+ }
+
+ /**
+ * Constructor for foreign transactions imported through DTM/OTS transaction
+ * propagation contexts.
+ */
+ TransactionImpl(GlobalId gid, Coordinator parentCoordinator, long timeout)
+ {
+ foreignTx = true;
+ xid = xidFactory.newBranch(gid);
+
+ this.parentCoordinator = parentCoordinator;
+
+ status = Status.STATUS_ACTIVE;
+
+ start = System.currentTimeMillis();
+ this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+ this.timeoutPeriod = timeout;
+ if (trace)
+ log.trace("Created new instance for tx=" + toString());
+ }
+
+ /**
+ * Constructor for foreign transactions imported through the JCA transaction
+ * inflow mechanism.
+ */
+ TransactionImpl(GlobalId gid, byte[] inboundBranchQualifier, long timeout)
+ {
+ foreignTx = true;
+ xid = xidFactory.newBranch(gid);
+
+ this.inboundBranchQualifier = inboundBranchQualifier;
+
+ status = Status.STATUS_ACTIVE;
+
+ start = System.currentTimeMillis();
+ this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+ this.timeoutPeriod = timeout;
+ if (trace)
+ log.trace("Created new instance for tx=" + toString());
+
+ }
+
+ /**
+ * Constructor to recreate a locally-started transaction that does not
+ * involve other transaction managers. It is intended to be called at
+ * recovery time, for recreating transactions that were in the committing
+ * state when the server crashed. Such a transaction completed the first
+ * phase of the 2PC protocol and logged the commit decision, but it must
+ * still send commit messages to some or all of its <code>XAResource</code>s.
+ *
+ * @param localId the local id of a locally-started transaction that is in
+ * the committing state and does not involve other transaction
+ * managers
+ * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is enlisted with the transaction
+ * and is still in the prepared state
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ TransactionImpl(long localId,
+ List preparedXAWorkList,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ foreignTx = false;
+ xid = xidFactory.recreateXid(localId);
+ status = Status.STATUS_COMMITTING;
+ if (heurData != null)
+ {
+ heuristicCode = heurData.heuristicStatusCode;
+ heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+ }
+ this.completionHandler = completionHandler;
+ for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+ {
+ XAWork preparedXAWork = (XAWork) it.next();
+ EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+ xaResources.add(r);
+
+ }
+ commitXAResourcesAfterTimeout();
+ }
+
+ /**
+ * Constructor to recreate a locally-started transaction that involves other
+ * transaction managers. Involving other transaction managers means that
+ * there are remote <code>Resource</code>s enlisted with the transaction.
+ * This constructor is intended to be called at recovery time, for recreating
+ * transactions that were in the committing state when the server crashed.
+ * Such a transaction completed the first phase of the 2PC protocol and
+ * logged the commit decision, but it must still send commit messages to
+ * some or all of its resources (<code>XAResource</code>s and remote
+ * <code>Resource</code>s).
+ *
+ * @param localId the local id of a locally-started transaction that is in
+ * the committing state and involves other transaction managers
+ * @param preparedXAWorkList list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is enlisted with the transaction
+ * and is still in the prepared state
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ TransactionImpl(long localId,
+ List preparedXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ foreignTx = false;
+ xid = xidFactory.recreateXid(localId);
+ status = Status.STATUS_COMMITTING;
+ this.completionHandler = completionHandler;
+ if (heurData != null)
+ {
+ heuristicCode = heurData.heuristicStatusCode;
+ heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+ }
+ for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+ {
+ XAWork preparedXAWork = (XAWork) it.next();
+ EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+ xaResources.add(r);
+ }
+ for (int i = 0; i < resources.length; i++)
+ {
+ EnlistedRemoteResource r =
+ new EnlistedRemoteResource(stringToResource(resources[i]), true);
+ remoteResources.add(r);
+ }
+ lock();
+ try
+ {
+ retryCommitRemoteResources();
+ }
+ finally
+ {
+ unlock();
+ }
+ if (preparedXAWorkList.size() > 0)
+ {
+ // Keep this TransactionImpl around to
+ // commit its XAResources at a later time.
+ commitXAResourcesAfterTimeout();
+ }
+ else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+ {
+ // This TransactionImpl is not needed anymore.
+ lock();
+ try
+ {
+ completeTransaction();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ else
+ {
+ // Do nothing. Just keep this TransactionImpl around to receive
+ // replayCompletion calls from remote resources.
+ }
+ }
+
+ /**
+ * Constructor to recreate a foreign transaction that entered this virtual
+ * machine in a transaction context propagated along with a remote method
+ * invocation. This constructor is intended to be called at recovery time,
+ * for recreating transactions that were in the prepared state when the
+ * server crashed.
+ *
+ * @param localId the local id of a foreign transaction that entered this
+ * virtual machine in a transaction propagation context and is
+ * propagated along with a remote method invocation and is in
+ * the prepared state
+ * @param inboundFormatId format id part of the foreign transaction
+ * identifier that entered this virtual machine
+ * @param globalTransactionId global id part of the foreign transaction
+ * identifier that entered this virtual machine
+ * @param recoveryCoord an stringfied reference to the transaction branch's
+ * <code>RecovertyCoordinator</code>
+ * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> enlisted with the transaction
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ TransactionImpl(long localId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ String recoveryCoord,
+ List preparedXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ foreignTx = true;
+ GlobalId globalId = new GlobalId(inboundFormatId, globalTransactionId);
+ xid = xidFactory.recreateXid(localId, globalId);
+ recoveryCoordinator = stringToRecoveryCoordinator(recoveryCoord);
+ if (heurData == null)
+ status = Status.STATUS_PREPARED;
+ else
+ {
+ status = heurData.transactionStatus;
+ heuristicCode = heurData.heuristicStatusCode;
+ heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+ }
+ this.completionHandler = completionHandler;
+ for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+ {
+ XAWork preparedXAWork = (XAWork) it.next();
+ EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+ xaResources.add(r);
+ }
+ for (int i = 0; i < resources.length; i++)
+ {
+ EnlistedRemoteResource r =
+ new EnlistedRemoteResource(stringToResource(resources[i]), true);
+ remoteResources.add(r);
+ }
+
+ // Set my registeredResource using the appropriate resource factory.
+ if (Proxy.isProxyClass(recoveryCoordinator.getClass()))
+ {
+ // DTM coordinator case
+ if (dtmResourceFactory != null)
+ registeredResource = dtmResourceFactory.createResource(localId);
+ else
+ log.warn("Error reconstructing TransactionImpl instance for tx="
+ + toString() + " -- DTM resource factory missing." );
+ }
+ else
+ {
+ // OTS coordinator case
+ if (otsResourceFactory != null)
+ registeredResource = otsResourceFactory.createResource(localId);
+ else
+ log.warn("Error reconstructing TransactionImpl instance for tx=" +
+ toString() + " -- OTS resource factory missing." );
+ }
+
+ if (status == Status.STATUS_PREPARED)
+ {
+ if (registeredResource != null)
+ {
+ createPreparedTimeout();
+ if (trace)
+ log.trace("Calling replayCompletion " +
+ "on the recovery coordinator, tx=" + toString());
+ try
+ {
+ recoveryCoordinator.replayCompletion(registeredResource);
+ }
+ catch (NoSuchObjectException noCoordinator)
+ {
+ if (trace)
+ {
+ log.trace("Exception in replayCompletion: no coordinator for tx="
+ + toString(), noCoordinator);
+ log.trace("Rolling back transaction branch, tx=" + toString());
+ }
+ try
+ {
+ rollbackBranch();
+ }
+ catch (Exception e)
+ {
+ if (trace)
+ log.trace("Exception in transaction branch rollback, tx=" +
+ toString(), e);
+ }
+ }
+ catch (Exception ignore)
+ {
+ if (trace)
+ log.trace("Ignoring exception in replayCompletion, tx=" +
+ toString(), ignore);
+ }
+ }
+ else
+ log.warn("Error reconstructing TransactionImpl instance for tx=" +
+ toString() + " -- registeredResource not set.\n" +
+ "The remote coordinator will NOT be contacted.");
+ }
+ else if (status == Status.STATUS_COMMITTING)
+ {
+ lock();
+ try
+ {
+ retryCommitRemoteResources();
+
+ if (preparedXAWorkList.size() > 0)
+ {
+ // Keep this TransactionImpl around to
+ // commit its XAResources at a later time.
+ commitXAResourcesAfterTimeout();
+ }
+ else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+ {
+ // This TransactionImpl is not needed anymore.
+ completeTransaction();
+ }
+ else
+ {
+ // Do nothing. Just keep this TransactionImpl around to
+ // receive replayCompletion calls from remote resources.
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ else if (status == Status.STATUS_ROLLING_BACK)
+ {
+ try
+ {
+ rollbackResourcesAndCompleteTransaction();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ }
+
+ /**
+ * Constructor to recreate a foreign transaction that entered this virtual
+ * machine through JCA transaction inflow. This constructor is intended to
+ * be called at recovery time, for recreating transactions that were in the
+ * prepared state when the server crashed.
+ *
+ * @param localId the local id of a foreign transaction that entered this
+ * virtual machine in a transaction propagation context and is
+ * propagated along with a remote method invocation and is in
+ * the prepared state
+ * @param inboundFormatId format id part of the foreign <code>Xid</code>
+ * @param globalTransactionId global id part of the foreign <code>Xid</code>
+ * @param inboundBranchQualifier the branch qualifier part of the foreign
+ * <code>Xid</code>
+ * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> enlisted with the transaction
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ TransactionImpl(long localId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ byte[] inboundBranchQualifier,
+ List preparedXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ foreignTx = true;
+ GlobalId globalId = new GlobalId(inboundFormatId, globalTransactionId);
+ xid = xidFactory.recreateXid(localId, globalId);
+ if (heurData == null)
+ status = Status.STATUS_PREPARED;
+ else
+ {
+ status = heurData.transactionStatus;
+ heuristicCode = heurData.heuristicStatusCode;
+ heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+ }
+ this.inboundBranchQualifier = inboundBranchQualifier;
+ this.completionHandler = completionHandler;
+
+ for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+ {
+ XAWork preparedXAWork = (XAWork) it.next();
+ EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+ xaResources.add(r);
+ }
+
+ for (int i = 0; i < resources.length; i++)
+ {
+ EnlistedRemoteResource resource =
+ new EnlistedRemoteResource(stringToResource(resources[i]), true);
+ remoteResources.add(resource);
+ }
+
+ if (status == Status.STATUS_COMMITTING)
+ {
+ lock();
+ try
+ {
+ retryCommitRemoteResources();
+
+ if (xaResourcesToRetry > 0)
+ {
+ // Keep this TransactionImpl around to
+ // commit its XAResources at a later time.
+ commitXAResourcesAfterTimeout();
+ }
+ else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+ {
+ // This TransactionImpl is not needed anymore.
+ completeTransaction();
+ }
+ else
+ {
+ // Do nothing. Just keep this TransactionImpl around to
+ // receive replayCompletion calls from remote resources.
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ else if (status == Status.STATUS_ROLLING_BACK)
+ {
+ try
+ {
+ rollbackResourcesAndCompleteTransaction();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ }
+
+ /**
+ * Constructor to recreate a transaction that is in a heuristically
+ * completed state (either committed or rolledback). This constructor is
+ * intended to be called at recovery time, for recreating heuristically
+ * completed transactions that were not yet forgotten when the server
+ * crashed.
+ *
+ * @param heurData an instance of <code>LogRecord.HeurData</code> with
+ * information on the heuristically completed transaction
+ * @param xaResourcesWithHeuristics a list of
+ * <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is in a a heuristic status
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes.
+ */
+ TransactionImpl(LogRecord.HeurData heurData,
+ List xaResourcesWithHeuristics,
+ TxCompletionHandler completionHandler)
+ {
+ foreignTx = heurData.foreignTx;
+ status = heurData.transactionStatus;
+
+ if (status != Status.STATUS_COMMITTING &&
+ status != Status.STATUS_COMMITTED &&
+ status != Status.STATUS_ROLLING_BACK &&
+ status != Status.STATUS_ROLLEDBACK)
+ throw new RuntimeException("Attempt to recreate heuristically " +
+ "completed transaction with status " +
+ TxUtils.getStatusAsString(status) + ". " +
+ "Must be STATUS_COMMITTING, " +
+ "STATUS_COMMITTED, STATUS_ROLLING_BACK, " +
+ "or STATUS_ROLLEDBACK.");
+ if (!foreignTx)
+ xid = xidFactory.recreateXid(heurData.localTransactionId);
+ else
+ {
+ GlobalId globalId = new GlobalId(heurData.formatId,
+ heurData.globalTransactionId);
+ xid = xidFactory.recreateXid(heurData.localTransactionId, globalId);
+ }
+ inboundBranchQualifier = heurData.inboundBranchQualifier;
+ this.completionHandler = completionHandler;
+ heuristicCode = heurData.heuristicStatusCode;
+ if (!xaResourcesWithHeuristics.isEmpty())
+ {
+ xaResourcesWithHeuristicDecisions = new ArrayList();
+ for (Iterator it = xaResourcesWithHeuristics.iterator();
+ it.hasNext(); )
+ {
+ XAWork xaWork = (XAWork) it.next();
+ xaWork.xaResourceAccess.release();
+ EnlistedXAResource r = new EnlistedXAResource(
+ xaWork.res,
+ xaWork.xid,
+ (heurData.transactionStatus == Status.STATUS_COMMITTED));
+ xaResources.add(r);
+ xaResourcesWithHeuristicDecisions.add(r);
+ }
+ }
+ if (heurData.remoteResourceHeuristics != null &&
+ heurData.remoteResourceHeuristics.length > 0)
+ {
+ remoteResourcesWithHeuristicDecisions = new ArrayList();
+ for (int i = 0; i < heurData.remoteResourceHeuristics.length; i++)
+ {
+ HeuristicStatus heurStatus = heurData.remoteResourceHeuristics[i];
+ EnlistedRemoteResource r =
+ new EnlistedRemoteResource(
+ stringToResource(heurStatus.resourceRef),
+ true, /* committed */
+ heurStatus.code);
+ remoteResources.add(r);
+ remoteResourcesWithHeuristicDecisions.add(r);
+ }
+ }
+ }
+
+ /**
+ * Returns true if this is a foreign transaction and false if this is a
+ * locally started transaction.
+ *
+ * @return true for a transaction that has been imported either through a
+ * DTM/OTS transaction propagation context or through JCA
+ * transaction inflow, and false for a locally started transaction.
+ */
+ public boolean isImported()
+ {
+ return foreignTx;
+ }
+
+ // Implements TimeoutTarget --------------------------------------
+
+ /**
+ * Called when our timeout expires.
+ */
+ public void timedOut(Timeout timeout)
+ {
+ lock();
+ try
+ {
+ log.warn("Transaction " + toString() + " timed out." +
+ " status=" + TxUtils.getStatusAsString(status));
+
+ if (this.timeout == null)
+ return; // Don't race with timeout cancellation.
+ this.timeout = null;
+
+ switch (status)
+ {
+ case Status.STATUS_ROLLEDBACK:
+ case Status.STATUS_COMMITTED:
+ case Status.STATUS_NO_TRANSACTION:
+ return; // Transaction done.
+
+ case Status.STATUS_ROLLING_BACK:
+ return; // Will be done shortly.
+
+ case Status.STATUS_PREPARED:
+ case Status.STATUS_COMMITTING:
+ return; // Timeout expiration has no effect anymore.
+
+ case Status.STATUS_ACTIVE:
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // fall through..
+ case Status.STATUS_MARKED_ROLLBACK:
+ // don't rollback for now, this messes up with the TxInterceptor.
+ interruptThreads();
+ return;
+
+ case Status.STATUS_PREPARING:
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return; // commit will fail
+
+ default:
+ log.warn("Unknown status at timeout, tx=" + toString());
+ return;
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ // Implements Transaction ----------------------------------------
+
+ public void commit()
+ throws RollbackException,
+ HeuristicMixedException,
+ HeuristicRollbackException,
+ java.lang.SecurityException,
+ java.lang.IllegalStateException,
+ SystemException
+ {
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("Committing, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ beforePrepare();
+
+ if (status == Status.STATUS_ACTIVE)
+ {
+ switch (getCommitStrategy())
+ {
+ case 0:
+ // Zero phase commit is really fast ;-)
+ if (trace)
+ log.trace("Zero phase commit " + toString() +
+ ": No resources.");
+ status = Status.STATUS_COMMITTED;
+ break;
+ case 1:
+ // One phase commit
+ if (trace)
+ log.trace("One phase commit " + toString() +
+ ": One resource.");
+ commitResources(true);
+ break;
+ default:
+ // Two phase commit
+ if (trace)
+ log.trace("Two phase commit " + toString() +
+ ": Many resources.");
+
+ if (!prepareResources())
+ {
+ boolean commitDecision =
+ status == Status.STATUS_PREPARED &&
+ (heuristicCode == HEUR_NONE ||
+ heuristicCode == XAException.XA_HEURCOM);
+
+ if (commitDecision)
+ {
+ // Save decision to stable storage
+ // for recovery after system crash.
+ try
+ {
+ RecoveryLogger logger =
+ TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ {
+ completionHandler = logger.saveCommitDecision(
+ xid.getLocalIdValue(),
+ getStringfiedRemoteResourcesThatVotedCommit());
+ }
+ }
+ catch (Throwable e)
+ {
+ if (e instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) e;
+ log.warn("FAILED WHEN WRITING COMMIT RECORD."
+ + " Rolling back now.", e);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = e;
+ }
+ cancelTimeout();
+ if (status == Status.STATUS_PREPARED)
+ {
+ try
+ {
+ commitResources(false);
+ }
+ catch (Throwable e)
+ {
+ if (e instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) e;
+ log.warn("Unexpected exception in commitResources:",
+ e);
+ }
+ }
+ }
+ }
+ else
+ status = Status.STATUS_COMMITTED; // all was read-only
+ }
+ }
+
+ if (status == Status.STATUS_COMMITTING)
+ {
+ // Keep this TransactionImpl around.
+ if (xaResourcesToRetry > 0)
+ commitXAResourcesAfterTimeout();
+ checkHeuristicsButDoNotThrowHeuristicHazard();
+ }
+ else if (status != Status.STATUS_COMMITTED)
+ {
+ Throwable causedByThrowable = cause;
+ rollbackResourcesAndCompleteTransaction();
+
+ // throw jboss rollback exception with the saved off cause
+ throw new JBossRollbackException("Unable to commit, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status),
+ causedByThrowable);
+ }
+ else /* (status == Status.STATUS_COMMITTED) */
+ {
+ cancelTimeout();
+ doAfterCompletion();
+ checkHeuristicsButDoNotThrowHeuristicHazard();
+ instanceDone();
+
+ if (trace)
+ log.trace("Committed OK, tx=" + toString());
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public void rollback()
+ throws java.lang.IllegalStateException,
+ java.lang.SecurityException,
+ SystemException
+ {
+ lock();
+ try
+ {
+
+ if (trace)
+ log.trace("rollback(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ checkWork();
+
+ switch (status)
+ {
+ case Status.STATUS_ACTIVE:
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // fall through..
+ case Status.STATUS_MARKED_ROLLBACK:
+ endResources();
+ rollbackResourcesAndCompleteTransaction();
+ // Cannot throw heuristic exception, so we just have to
+ // clear the heuristics without reporting.
+ heuristicCode = HEUR_NONE;
+ return;
+ case Status.STATUS_PREPARING:
+ // Set status to avoid race with prepareResources().
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return; // commit() will do rollback.
+ default:
+ throw new IllegalStateException("Cannot rollback(), tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ }
+ }
+ finally
+ {
+ Thread.interrupted();// clear timeout that did an interrupt
+ unlock();
+ }
+ }
+
+ public boolean delistResource(XAResource xaRes, int flag)
+ throws java.lang.IllegalStateException,
+ SystemException
+ {
+ if (xaRes == null)
+ throw new IllegalArgumentException("null xaRes tx=" + toString());
+ if (flag != XAResource.TMSUCCESS &&
+ flag != XAResource.TMSUSPEND &&
+ flag != XAResource.TMFAIL)
+ throw new IllegalArgumentException("Bad flag: " + flag +
+ " tx=" + toString());
+
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("delistResource(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ EnlistedXAResource resource = findResource(xaRes);
+ if (resource == null)
+ throw new IllegalArgumentException("xaRes not enlisted " + xaRes);
+
+ switch (status)
+ {
+ case Status.STATUS_ACTIVE:
+ case Status.STATUS_MARKED_ROLLBACK:
+ break;
+ case Status.STATUS_PREPARING:
+ throw new IllegalStateException("Already started preparing, tx=" +
+ toString());
+ case Status.STATUS_ROLLING_BACK:
+ throw new IllegalStateException("Already started rolling back, tx="
+ + toString());
+ case Status.STATUS_PREPARED:
+ throw new IllegalStateException("Already prepared, tx=" +
+ toString());
+ case Status.STATUS_COMMITTING:
+ throw new IllegalStateException("Already started committing, tx=" +
+ toString());
+ case Status.STATUS_COMMITTED:
+ throw new IllegalStateException("Already committed, tx=" +
+ toString());
+ case Status.STATUS_ROLLEDBACK:
+ throw new IllegalStateException("Already rolled back, tx=" +
+ toString());
+ case Status.STATUS_NO_TRANSACTION:
+ throw new IllegalStateException("No transaction, tx=" + toString());
+ case Status.STATUS_UNKNOWN:
+ throw new IllegalStateException("Unknown state, tx=" + toString());
+ default:
+ throw new IllegalStateException("Illegal status: " +
+ TxUtils.getStatusAsString(status) +
+ ", tx=" + toString());
+ }
+
+ try
+ {
+ return resource.delistResource(xaRes, flag);
+ }
+ catch (XAException xae)
+ {
+ logXAException(xae);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = xae;
+ return false;
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public boolean enlistResource(XAResource xaRes)
+ throws RollbackException,
+ java.lang.IllegalStateException,
+ SystemException
+ {
+ if (xaRes == null)
+ throw new IllegalArgumentException("null xaRes, tx=" + toString());
+
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("enlistResource(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status) +
+ ", xaRes=" + xaRes);
+
+ checkStatus();
+
+ if (resourcesEnded)
+ throw new IllegalStateException("Too late to enlist resources, tx="
+ + toString());
+
+ // Add resource
+ try
+ {
+ EnlistedXAResource resource = findResource(xaRes);
+
+ // Existing resource
+ if (resource != null)
+ {
+ if (resource.isEnlisted())
+ {
+ if (trace)
+ log.trace("Already enlisted: tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status) +
+ ", xaRes=" + xaRes);
+ return true; // already enlisted
+ }
+ if (resource.isDelisted(xaRes))
+ // this is a resource that returns false on all calls to
+ // isSameRM. Further, the last resource enlisted has
+ // already been delisted, so it is time to enlist it again.
+ resource = null;
+ else
+ return resource.startResource();
+ }
+
+ // Register itself as a resource with the parent coordinator
+ if (parentCoordinator != null && recoveryCoordinator == null)
+ registerResourceWithParentCoordinator();
+
+ resource = findResourceManager(xaRes);
+ if (resource != null)
+ {
+ // The xaRes is new. We register the xaRes with the Xid
+ // that the RM has previously seen from this transaction,
+ // and note that it has the same RM.
+ resource = addResource(xaRes, resource.getXid(), resource);
+ return resource.startResource();
+ }
+
+ // New resource and new RM: Create a new transaction branch.
+ resource = addResource(xaRes, createXidBranch(), null);
+ return resource.startResource();
+ }
+ catch (XAException xae)
+ {
+ logXAException(xae);
+ cause = xae;
+ return false;
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+
+ }
+
+ public int getStatus()
+ {
+ if (done)
+ return Status.STATUS_NO_TRANSACTION;
+ return status;
+ }
+
+ public void registerSynchronization(Synchronization s)
+ throws RollbackException,
+ java.lang.IllegalStateException,
+ SystemException
+ {
+ if (s == null)
+ throw new IllegalArgumentException("Null synchronization, tx=" +
+ toString());
+
+ lock();
+ try
+ {
+ if (trace)
+ {
+ log.trace("registerSynchronization(): Entered, " +
+ "tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ }
+
+ checkStatus();
+
+ if (syncCount == syncAllocSize)
+ {
+ // expand table
+ syncAllocSize = 2 * syncAllocSize;
+
+ Synchronization[] sy = new Synchronization[syncAllocSize];
+ System.arraycopy(sync, 0, sy, 0, syncCount);
+ sync = sy;
+ }
+ sync[syncCount++] = s;
+
+ // Register itself as a resource with the parent coordinator
+ if (parentCoordinator != null && recoveryCoordinator == null)
+ registerResourceWithParentCoordinator();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public void setRollbackOnly()
+ throws java.lang.IllegalStateException,
+ SystemException
+ {
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("setRollbackOnly(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ switch (status)
+ {
+ case Status.STATUS_ACTIVE:
+ // Register itself as a resource with the parent coordinator
+ if (parentCoordinator != null && recoveryCoordinator == null)
+ {
+ try
+ {
+ registerResourceWithParentCoordinator();
+ }
+ catch (RollbackException e)
+ {
+ log.warn("RollbackException in setRollbackOnly: " + e);
+ }
+ }
+ // fall through..
+ case Status.STATUS_PREPARING:
+ case Status.STATUS_PREPARED:
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // fall through..
+ case Status.STATUS_MARKED_ROLLBACK:
+ case Status.STATUS_ROLLING_BACK:
+ return;
+ case Status.STATUS_COMMITTING:
+ throw new IllegalStateException("Already started committing, tx=" +
+ toString());
+ case Status.STATUS_COMMITTED:
+ throw new IllegalStateException("Already committed, tx=" +
+ toString());
+ case Status.STATUS_ROLLEDBACK:
+ throw new IllegalStateException("Already rolled back, tx=" +
+ toString());
+ case Status.STATUS_NO_TRANSACTION:
+ throw new IllegalStateException("No transaction, tx=" + toString());
+ case Status.STATUS_UNKNOWN:
+ throw new IllegalStateException("Unknown state, tx=" + toString());
+ default:
+ throw new IllegalStateException("Illegal status: " +
+ TxUtils.getStatusAsString(status) +
+ ", tx=" + toString());
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ // Public methods called by DTM/OTS servants ---------------------
+
+ /**
+ * Enlists a remote DTM/OTS resource in this transaction. Called by the
+ * DTM/OTS servants to implement the register resource method of the
+ * DTM/OTS coordinator interface.
+ *
+ * @param remoteRes a remote DTM/OTS resource to be enlisted in this
+ * transaction
+ * @throws RollbackException
+ * @throws java.lang.IllegalStateException
+ *
+ */
+ public void enlistRemoteResource(Resource remoteRes)
+ throws RollbackException,
+ java.lang.IllegalStateException
+ {
+ if (remoteRes == null)
+ throw new IllegalArgumentException("null remoteRes, tx=" + toString());
+
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("enlistRemoteResource(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ checkStatus();
+
+ if (resourcesEnded)
+ throw new IllegalStateException("Too late to enlist resources, tx="
+ + toString());
+
+ // Register itself as a resource with the parent coordinator
+ if (parentCoordinator != null && recoveryCoordinator == null)
+ registerResourceWithParentCoordinator();
+
+ // Add resource
+ EnlistedRemoteResource resource =
+ new EnlistedRemoteResource(remoteRes);
+ remoteResources.add(resource);
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * Prepare an external transaction
+ *
+ * @param inboundXid a foreign Xid that entered this VM via JCA transaction
+ * inflow, or null in the case of an external transaction that
+ * entered this VM in a transaction context propagated along with a
+ * remote request.
+ * @return XAResource.XA_RDONLY or XAResource.XA_OK
+ */
+ public int prepare(Xid inboundXid)
+ throws HeuristicHazardException,
+ HeuristicMixedException,
+ HeuristicRollbackException,
+ RollbackException
+ {
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("Preparing, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ checkWork();
+
+ beforePrepare();
+
+ if (status == Status.STATUS_ACTIVE)
+ {
+ switch (getCommitStrategy())
+ {
+ case 0:
+ // Nothing to do, but we must be careful with synchronizations
+ if (trace)
+ log.trace("Prepare foreign tx=" + toString() +
+ ": No resources.");
+ if (syncCount == 0)
+ {
+ // No Synchronizations registered: we can vote read-only
+ status = Status.STATUS_COMMITTED;
+ completeTransaction();
+ return XAResource.XA_RDONLY;
+ }
+ else
+ { // There is at least one Synchronization registered:
+ // vote commit so that afterCompletion gets called
+ status = Status.STATUS_PREPARED;
+ return XAResource.XA_OK;
+ }
+ default:
+ // Two phase commit
+ if (trace)
+ log.trace("Prepare foreign tx=" + toString() +
+ ": one or more resources.");
+ if (!prepareResources())
+ {
+ boolean commitDecision =
+ status == Status.STATUS_PREPARED &&
+ (heuristicCode == HEUR_NONE ||
+ heuristicCode == XAException.XA_HEURCOM);
+
+ if (commitDecision)
+ {
+ // Save decision to stable storage
+ // for recovery after system crash.
+ try
+ {
+ RecoveryLogger logger =
+ TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ {
+ if (inboundXid == null)
+ {
+ completionHandler = logger.savePrepareDecision(
+ xid.getLocalIdValue(),
+ xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ recoveryCoordinatorToString(recoveryCoordinator),
+ getStringfiedRemoteResourcesThatVotedCommit());
+ }
+ else
+ {
+ completionHandler = logger.savePrepareDecision(
+ xid.getLocalIdValue(),
+ inboundXid,
+ getStringfiedRemoteResourcesThatVotedCommit());
+ }
+ }
+ }
+ catch (Throwable e)
+ {
+ if (e instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) e;
+ log.warn("FAILED WHEN WRITING PREPARE RECORD."
+ + " Rolling back now.");
+ }
+ }
+ }
+ else
+ {
+ if (syncCount == 0)
+ {
+ // No Synchronizations registered: we can vote read-only
+ if (trace)
+ log.trace("Prepared foreign tx=" + toString() +
+ ": All readonly," +
+ " no Synchronizations registered");
+ status = Status.STATUS_COMMITTED;
+ completeTransaction();
+ return XAResource.XA_RDONLY;
+ }
+ else
+ { // There is at least one Synchronization registered:
+ // vote commit so that afterCompletion gets called
+ if (trace)
+ log.trace("Prepared foreign tx=" + toString() +
+ ": All readonly, but there is at least" +
+ " one Synchronization registered");
+ status = Status.STATUS_PREPARED;
+ return XAResource.XA_OK;
+ }
+ }
+ break;
+ }
+ }
+
+ if (status != Status.STATUS_PREPARED)
+ {
+ // save off the cause throwable as
+ // instanceDone resets it to null
+ Throwable causedByThrowable = cause;
+ rollbackResourcesAndCompleteTransaction();
+
+ // throw jboss rollback exception with the saved off cause
+ throw new JBossRollbackException("Unable to prepare, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status),
+ causedByThrowable);
+ }
+
+ if (inboundXid == null)
+ createPreparedTimeout();
+
+ // We are ok to commit
+ return XAResource.XA_OK;
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * Commit an external transaction
+ *
+ * @param onePhase whether the commit is one or two phase
+ */
+ public void commit(boolean onePhase)
+ throws RollbackException,
+ HeuristicHazardException,
+ HeuristicMixedException,
+ HeuristicRollbackException,
+ SystemException
+ {
+ checkWork();
+
+ // One phase commit optimization
+ if (onePhase)
+ {
+ commit();
+ return;
+ }
+
+ // Two phase
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("Committing two phase, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ cancelPreparedTimeout();
+
+ switch (status)
+ {
+ case Status.STATUS_PREPARING:
+ throw new IllegalStateException("Still preparing, tx=" +
+ toString());
+ case Status.STATUS_ROLLING_BACK:
+ throw new IllegalStateException("Already started rolling back, tx="
+ + toString());
+ case Status.STATUS_ROLLEDBACK:
+ instanceDone();
+ //checkHeuristics();
+ throw new IllegalStateException("Already rolled back, tx=" +
+ toString());
+ case Status.STATUS_COMMITTING:
+ if (trace)
+ log.trace("Already committing, tx=" + toString());
+ return;
+ case Status.STATUS_COMMITTED:
+ instanceDone();
+ //checkHeuristics();
+ if (trace)
+ log.trace("Already committed, tx=" + toString());
+ return;
+ case Status.STATUS_NO_TRANSACTION:
+ throw new IllegalStateException("No transaction, tx=" + toString());
+ case Status.STATUS_UNKNOWN:
+ throw new IllegalStateException("Unknown state, tx=" + toString());
+ case Status.STATUS_MARKED_ROLLBACK:
+ endResources();
+ rollbackResourcesAndCompleteTransaction();
+ checkHeuristics();
+ throw new RollbackException("Already marked for rollback, tx=" +
+ toString());
+ case Status.STATUS_PREPARED:
+ break;
+ default:
+ throw new IllegalStateException("Illegal status: " +
+ TxUtils.getStatusAsString(status) +
+ ", tx=" + toString());
+ }
+
+ commitResources(false);
+
+ if (status == Status.STATUS_COMMITTING)
+ {
+ // Keep this TransactionImpl around.
+ if (xaResourcesToRetry > 0)
+ commitXAResourcesAfterTimeout();
+ checkHeuristics();
+ }
+ else if (status != Status.STATUS_COMMITTED)
+ {
+ Throwable causedByThrowable = cause;
+
+ // throw jboss rollback exception with the saved off cause
+ throw new JBossRollbackException("Unable to commit, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status),
+ causedByThrowable);
+ }
+ else /* (status == Status.STATUS_COMMITTED) */
+ {
+ cancelTimeout();
+ doAfterCompletion();
+ checkHeuristics();
+ instanceDone();
+
+ if (trace)
+ log.trace("Committed OK, tx=" + toString());
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * Rollback an external transaction
+ */
+ public void rollbackBranch()
+ throws HeuristicCommitException,
+ HeuristicMixedException,
+ HeuristicHazardException,
+ java.lang.IllegalStateException
+ {
+ lock();
+ try
+ {
+
+ if (trace)
+ log.trace("rollbackBranch(): Entered, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ checkWork();
+ cancelPreparedTimeout();
+
+ switch (status)
+ {
+ case Status.STATUS_ACTIVE:
+ case Status.STATUS_PREPARED:
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // fall through..
+ case Status.STATUS_MARKED_ROLLBACK:
+ endResources();
+ rollbackResourcesAndCompleteTransaction();
+ switch (getFullHeuristicCode())
+ {
+ case XAException.XA_HEURHAZ:
+ if (trace)
+ log.trace("Throwing HeuristicHazardException, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ throw new HeuristicHazardException();
+ case XAException.XA_HEURMIX:
+ if (trace)
+ log.trace("Throwing HeuristicMixedException, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ throw new HeuristicMixedException();
+ case XAException.XA_HEURRB:
+ if (trace)
+ log.trace("Not throwing HeuristicRollbackException, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ break;
+ case XAException.XA_HEURCOM:
+ if (trace)
+ log.trace("Throwing HeuristicCommitException, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ throw new HeuristicCommitException();
+ }
+ return;
+ case Status.STATUS_PREPARING:
+ // Set status to avoid race with prepareResources().
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return; // commit() will do rollback.
+ case Status.STATUS_ROLLEDBACK:
+ if (trace)
+ log.trace("Already rolledback, tx=" + toString());
+ return;
+ default:
+ throw new IllegalStateException("Cannot rollback(), tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ }
+ }
+ finally
+ {
+ Thread.interrupted();// clear timeout that did an interrupt
+ unlock();
+ }
+ }
+
+ /**
+ * Forgets the heuristic status of the transaction. This method should
+ * be called on heuristically completed transactions.
+ */
+ public void forget()
+ {
+ lock();
+ try {
+ if (heuristicCode == HEUR_NONE && !heuristicHazard)
+ return;
+
+ if (status != Status.STATUS_COMMITTED &&
+ status != Status.STATUS_ROLLEDBACK &&
+ (status != Status.STATUS_COMMITTING || !heuristicHazard) &&
+ (status != Status.STATUS_ROLLING_BACK || !heuristicHazard))
+ return;
+
+ if (forgetResources())
+ {
+ // Clear heuristics status from stable storage
+ RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ {
+ logger.clearHeuristicStatus(xid.getLocalIdValue());
+ if (status == Status.STATUS_COMMITTED &&
+ completionHandler != null)
+ completionHandler.handleTxCompletion(xid.getLocalIdValue());
+ }
+ instanceDone();
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * If this transaction is in the committing state, this method starts
+ * a thread that calls commit on the remote resources that were not yet
+ * successful committed. It returns the current status of the transaction
+ * (before any commit calls are issued).
+ *
+ * @return the current status of the transaction.
+ */
+ public int replayCompletion(final Resource r)
+ {
+ lock();
+ try
+ {
+ if (status != Status.STATUS_MARKED_ROLLBACK &&
+ status != Status.STATUS_ROLLING_BACK &&
+ status != Status.STATUS_ROLLEDBACK)
+ {
+ final boolean pendingCommits =
+ (status == Status.STATUS_COMMITTING &&
+ remoteResourcesToRetry > 0);
+
+ Runnable runnable = new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ if (trace)
+ log.trace("Replay completion thread: " +
+ " committing remote resource " + r);
+ r.commit();
+
+ if (trace)
+ log.trace("Replay completion thread: " +
+ " committed remote resource " + r);
+ }
+ catch (Throwable ignored)
+ {
+ if (trace)
+ log.trace("Replay completion thread: ignored exception"
+ + " when committing remote resource " + r ,
+ ignored);
+ }
+
+ if (pendingCommits)
+ {
+ lock();
+ try
+ {
+ if (trace)
+ log.trace("Replay completion thread: calling " +
+ "retryCommitRemoteResources");
+
+ retryCommitRemoteResources();
+ if (xaResourcesToRetry == 0 &&
+ remoteResourcesToRetry == 0 &&
+ heuristicCode == HEUR_NONE)
+ completeTransaction();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ if (trace)
+ log.trace("Replay completion thread: exiting");
+ }
+ };
+ Thread t = new Thread(runnable, "replayCompletionThread");
+
+ t.start();
+ }
+ return status;
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * DTM/OTS servants need this method to implement the get transaction
+ * propagation context method of the coordinator interface.
+ *
+ * @param errorRollback throw a RollbackException if the transaction is
+ * not active
+ * @return the time left before this transaction time-out expires.
+ * @throws RollbackException if the transaction is not active and
+ * errorRollback is true.
+ */
+ public long getTimeLeftBeforeTimeout(boolean errorRollback)
+ throws RollbackException
+ {
+ if (errorRollback && status != Status.STATUS_ACTIVE)
+ throw new RollbackException("Transaction is not active: " +
+ TxUtils.getStatusAsString(status));
+ return (start + timeoutPeriod) - System.currentTimeMillis();
+ }
+
+ public int getAssociatedThreadCount()
+ {
+ lock();
+ try
+ {
+ return threads.size();
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public Set getAssociatedThreads()
+ {
+ lock();
+ try
+ {
+ return Collections.unmodifiableSet(threads);
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ public int hashCode()
+ {
+ return xid.hashCode();
+ }
+
+ public String toString()
+ {
+ return "TransactionImpl:" + xidFactory.toString(xid);
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (obj != null && obj instanceof TransactionImpl)
+ return getLocalIdValue() == (((TransactionImpl) obj).getLocalIdValue());
+ return false;
+ }
+
+
+ /**
+ * Returns the local id of this transaction. The local id is used as
+ * a transaction propagation context within the JBoss server, and
+ * in the TxManager for mapping local transaction ids to transactions.
+ */
+ public long getLocalIdValue()
+ {
+ return xid.getLocalIdValue();
+ }
+
+ /**
+ * Returns the local id of this transaction. The local id is used as
+ * a transaction propagation context within the JBoss server, and
+ * in the TxManager for mapping local transaction ids to transactions.
+ */
+ public LocalId getLocalId()
+ {
+ return xid.getLocalId();
+ }
+
+ /**
+ * Returns the global id of this transaction. Ths global id is used in
+ * the TxManager, which keeps a map from global ids to transactions.
+ */
+ public GlobalId getGlobalId()
+ {
+ return xid.getTrulyGlobalId();
+ }
+
+ /**
+ * Returns the xid of this transaction.
+ */
+ public XidImpl getXid()
+ {
+ return xid;
+ }
+
+ /**
+ * Returns this transaction's DTM propagation context.
+ */
+ public TxPropagationContext getPropagationContext()
+ {
+ if (dtmPropagationContext == null)
+ {
+ Coordinator coordinatorToPropagate;
+
+ if (parentCoordinator == null || interpositionEnabled ||
+ !Proxy.isProxyClass(parentCoordinator.getClass()))
+ {
+ // no parent coordinator, or interposition enabled, or
+ // parent coordinator is a wrapped OTS coordinator:
+ // propagate proxy to a local DTM coordinator
+ coordinatorToPropagate =
+ dtmCoordinatorFactory.createCoordinator(getLocalIdValue());
+ }
+ else
+ {
+ // parent coordinator is a DTM coordinator and interposition is
+ // disabled: propagate the parent coordinator
+ coordinatorToPropagate = parentCoordinator;
+ }
+
+ // Create TxPropagationContext
+ dtmPropagationContext =
+ new TxPropagationContext(xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ 0, /* timeout set below */
+ coordinatorToPropagate,
+ null /* null Terminator */);
+ }
+
+ // Update the timeout in the TPC and return the TPC
+ try
+ {
+ dtmPropagationContext.timeout =
+ TMUtil.divideAndRoundUp(getTimeLeftBeforeTimeout(true), 1000);
+ }
+ catch (RollbackException transactionNotActive)
+ {
+ if (status == Status.STATUS_MARKED_ROLLBACK)
+ dtmPropagationContext.timeout = 0;
+ else
+ dtmPropagationContext = null;
+ }
+ return dtmPropagationContext;
+ }
+
+ /**
+ * Returns this transaction's OTS propagation context.
+ */
+ public Object getOTSPropagationContext()
+ {
+ if (otsPropagationContext == null)
+ {
+ if (parentCoordinator == null || interpositionEnabled ||
+ Proxy.isProxyClass(parentCoordinator.getClass()))
+ {
+ // no parent coordinator, or interposition enabled, or
+ // parent coordinator is a dynamic proxy (rather than a wrapped
+ // OTS coordinator): propagate reference to a local OTS coordinator
+ otsPropagationContext =
+ otsContextFactory.createOTSContext(xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ getLocalIdValue());
+ }
+ else
+ {
+ // parent coordinator is a DTM coordinator and interposition is
+ // disabled: propagate the parent coordinator
+ otsPropagationContext =
+ otsContextFactory.createOTSContext(xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ parentCoordinator);
+ }
+ }
+
+ // Update the timeout in the TPC and return the TPC
+ try
+ {
+ otsContextFactory.setTimeout(
+ otsPropagationContext,
+ TMUtil.divideAndRoundUp(getTimeLeftBeforeTimeout(true), 1000));
+ }
+ catch (RollbackException transactionNotActive)
+ {
+ if (status == Status.STATUS_MARKED_ROLLBACK)
+ otsContextFactory.setTimeout(otsPropagationContext, 0);
+ else
+ otsPropagationContext = null;
+ }
+ return otsPropagationContext;
+ }
+
+ public void setHeuristicStatus(LogRecord.HeurData heurData)
+ {
+ lock();
+ try
+ {
+ if (status != heurData.transactionStatus)
+ {
+ if (status == Status.STATUS_PREPARED)
+ cancelPreparedTimeout();
+
+ status = heurData.transactionStatus;
+
+ if (status == Status.STATUS_COMMITTING)
+ {
+ retryCommitRemoteResources();
+
+ if (xaResourcesToRetry > 0)
+ {
+ // Keep this TransactionImpl around to
+ // commit its XAResources at a later time.
+ commitXAResourcesAfterTimeout();
+ }
+ else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+ {
+ // This TransactionImpl is not needed anymore.
+ completeTransaction();
+ }
+ else
+ {
+ // Do nothing. Just keep this TransactionImpl around to
+ // receive replayCompletion calls from remote resources.
+ }
+ }
+ else if (status == Status.STATUS_ROLLING_BACK)
+ {
+ rollbackResourcesAndCompleteTransaction();
+ }
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ // Package protected ---------------------------------------------
+
+ /**
+ * Checks whether this transaction is a foreign transaction that entered
+ * the server via JCA transaction inflow and is either in the prepared state
+ * or in a heuristically completed state.
+ *
+ * @return true if this is a JCA inbound transaction that is either prepared
+ * or heuristically completed (but not yet forgotten), false
+ * otherwise.
+ */
+ boolean isPreparedOrHeuristicallyCompletedJCAInboundTx()
+ {
+ return inboundBranchQualifier != null
+ && (status == Status.STATUS_PREPARED
+ || (getFullHeuristicCode() != HEUR_NONE
+ && (status == Status.STATUS_COMMITTED
+ || status == Status.STATUS_ROLLEDBACK)));
+ }
+
+ /**
+ * Gets the inbound <code>Xid</code> of a foreign transaction that entered
+ * the server via JCA transaction inflow.
+ *
+ * @return the inbound <code>Xid</code>.
+ */
+ Xid getInboundXid()
+ {
+ return new Xid()
+ {
+ public int getFormatId()
+ {
+ return xid.getFormatId();
+ }
+
+ public byte[] getGlobalTransactionId()
+ {
+ return xid.getGlobalTransactionId();
+ }
+
+ public byte[] getBranchQualifier()
+ {
+ return (byte[])inboundBranchQualifier.clone();
+ }
+ };
+ }
+
+ void associateCurrentThread()
+ {
+ Thread.interrupted();
+ lock();
+ try
+ {
+ threads.add(Thread.currentThread());
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ void disassociateCurrentThread()
+ {
+ // Just a tidyup, no need to synchronize
+ if (done)
+ {
+ threads.remove(Thread.currentThread());
+ }
+ else
+ {
+ // Removing the association for an active transaction
+ lock();
+ try
+ {
+ threads.remove(Thread.currentThread());
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ Thread.interrupted();
+ }
+
+ /**
+ * Lock this instance.
+ */
+ synchronized void lock()
+ {
+ if (done)
+ throw new IllegalStateException("Transaction has terminated, tx=" +
+ toString());
+
+ Thread currentThread = Thread.currentThread();
+ if (locked != null && locked != currentThread)
+ {
+ log.debug("Lock contention, tx=" + toString() +
+ " otherThread=" + locked);
+ //DEBUG Thread.currentThread().dumpStack();
+
+ while (locked != null && locked != currentThread)
+ {
+ try
+ {
+ // Wakeup happens when:
+ // - notify() is called from unlock()
+ // - notifyAll is called from instanceDone()
+ wait();
+ }
+ catch (InterruptedException ex)
+ {
+ // ignore
+ }
+
+ if (done)
+ throw new IllegalStateException(
+ "Transaction has now terminated, tx=" + toString());
+ }
+ }
+
+ locked = currentThread;
+ ++lockDepth;
+ }
+
+ /**
+ * Unlock this instance.
+ */
+ synchronized void unlock()
+ {
+ Thread currentThread = Thread.currentThread();
+ if (locked == null || locked != currentThread)
+ {
+ log.warn("Unlocking, but not locked, tx=" + toString() +
+ " otherThread=" + locked,
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ if (--lockDepth == 0)
+ {
+ locked = null;
+ notify();
+ }
+ }
+ }
+
+ /**
+ * Deactivate this transaction.
+ */
+ synchronized void deactivate()
+ {
+ lock();
+ try
+ {
+ cancelTimeout();
+ cancelPreparedTimeout();
+ cancelXARetryTimeout();
+
+ // Clear tables refering to external objects.
+ // Even if a client holds on to this instance forever, the objects
+ // that we have referenced may be garbage collected.
+ parentCoordinator = null;
+ sync = null;
+ xaResources = null;
+ remoteResources = null;
+ transactionLocalMap.clear();
+ threads.clear();
+
+ // Set the status
+ status = Status.STATUS_NO_TRANSACTION;
+
+ // Notify all threads waiting for the lock.
+ notifyAll();
+
+ // set the done flag
+ done = true;
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * Get the work
+ *
+ * @return the work
+ */
+ Work getWork()
+ {
+ return work;
+ }
+
+ /**
+ * Set the work
+ *
+ * @param work the work
+ * @throws WorkCompletedException with error code WorkException.TX_CONCURRENT_WORK_DISALLOWED
+ * when work is already present for the xid or whose completion is in progress, only
+ * the global part of the xid must be used for this check. Or with error code
+ * WorkException.TX_RECREATE_FAILED if it is unable to recreate the transaction context
+ */
+ void setWork(Work work)
+ throws WorkCompletedException
+ {
+ lock();
+ try
+ {
+ if (work == null)
+ {
+ this.work = null;
+ return;
+ }
+
+ if (status == Status.STATUS_NO_TRANSACTION || status == Status.STATUS_UNKNOWN)
+ throw new WorkCompletedException("The transaction is not active " +
+ toString() + ": " +
+ TxUtils.getStatusAsString(status),
+ WorkException.TX_RECREATE_FAILED);
+ else if (status != Status.STATUS_ACTIVE)
+ throw new WorkCompletedException("Too late to start work " +
+ toString() + ": " +
+ TxUtils.getStatusAsString(status),
+ WorkException.TX_CONCURRENT_WORK_DISALLOWED);
+ else if (this.work != null)
+ throw new WorkCompletedException("Already have work " + toString() +
+ ": " + this.work,
+ WorkException.TX_CONCURRENT_WORK_DISALLOWED);
+
+ this.work = work;
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+
+ /**
+ * Getter for property done.
+ */
+ boolean isDone()
+ {
+ return done || (heuristicCode != HEUR_NONE &&
+ (status == Status.STATUS_COMMITTING ||
+ status == Status.STATUS_COMMITTED ||
+ status == Status.STATUS_ROLLING_BACK ||
+ status == Status.STATUS_ROLLEDBACK));
+ }
+
+ Object getTransactionLocalValue(TransactionLocal tlocal)
+ {
+ return transactionLocalMap.get(tlocal);
+ }
+
+ void putTransactionLocalValue(TransactionLocal tlocal, Object value)
+ {
+ transactionLocalMap.put(tlocal, value);
+ }
+
+ boolean containsTransactionLocal(TransactionLocal tlocal)
+ {
+ return transactionLocalMap.containsKey(tlocal);
+ }
+
+ // Private -------------------------------------------------------
+
+ /**
+ * Before prepare
+ */
+ private void beforePrepare()
+ throws HeuristicMixedException,
+ HeuristicRollbackException,
+ RollbackException
+ {
+ checkIntegrity();
+
+ doBeforeCompletion();
+
+ if (trace)
+ log.trace("Before completion done, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+
+ endResources();
+ }
+
+ /**
+ * Check the integrity of the transaction
+ */
+ private void checkIntegrity()
+ throws HeuristicMixedException,
+ HeuristicRollbackException,
+ RollbackException
+ {
+ // Spec defined checks for the transaction in a valid state
+ checkBeforeStatus();
+
+ TransactionIntegrity integrity = TxManager.getInstance().getTransactionIntegrity();
+ if (integrity != null)
+ {
+ // Extra integrity checks
+ unlock();
+ try
+ {
+ integrity.checkTransactionIntegrity(this);
+ }
+ finally
+ {
+ lock();
+ }
+
+ // Recheck the transaction state
+ checkBeforeStatus();
+ }
+ }
+
+ /**
+ * Check the before status
+ */
+ private void checkBeforeStatus()
+ throws HeuristicMixedException,
+ HeuristicRollbackException,
+ RollbackException
+ {
+ switch (status)
+ {
+ case Status.STATUS_PREPARING:
+ throw new IllegalStateException("Already started preparing, tx=" +
+ toString());
+ case Status.STATUS_PREPARED:
+ throw new IllegalStateException("Already prepared, tx=" + toString());
+ case Status.STATUS_ROLLING_BACK:
+ throw new IllegalStateException("Already started rolling back, tx=" +
+ toString());
+ case Status.STATUS_ROLLEDBACK:
+ instanceDone();
+ //checkHeuristics();
+ throw new IllegalStateException("Already rolled back, tx=" +
+ toString());
+ case Status.STATUS_COMMITTING:
+ throw new IllegalStateException("Already started committing, tx=" +
+ toString());
+ case Status.STATUS_COMMITTED:
+ instanceDone();
+ //checkHeuristics();
+ throw new IllegalStateException("Already committed, tx=" + toString());
+ case Status.STATUS_NO_TRANSACTION:
+ throw new IllegalStateException("No transaction, tx=" + toString());
+ case Status.STATUS_UNKNOWN:
+ throw new IllegalStateException("Unknown state, tx=" + toString());
+ case Status.STATUS_MARKED_ROLLBACK:
+ endResources();
+ rollbackResourcesAndCompleteTransaction();
+ checkHeuristicsButDoNotThrowHeuristicHazard();
+ throw new RollbackException("Already marked for rollback, tx=" +
+ toString());
+ case Status.STATUS_ACTIVE:
+ break;
+ default:
+ throw new IllegalStateException("Illegal status: " +
+ TxUtils.getStatusAsString(status) +
+ ", tx=" + toString());
+ }
+ }
+
+ /**
+ * Complete the transaction
+ */
+ private void completeTransaction()
+ {
+ cancelTimeout();
+ doAfterCompletion();
+ instanceDone();
+ }
+
+ /**
+ * Checks if the transaction state allows calls to the methods
+ * <code>enlistResource</code> and <code>registerSynchronization</code>
+ *
+ * @throws RollbackException
+ * @throws IllegalStateException
+ */
+ private void checkStatus()
+ throws RollbackException
+ {
+ switch (status)
+ {
+ case Status.STATUS_ACTIVE:
+ case Status.STATUS_PREPARING:
+ break;
+ case Status.STATUS_PREPARED:
+ throw new IllegalStateException("Already prepared, tx=" + toString());
+ case Status.STATUS_COMMITTING:
+ throw new IllegalStateException("Already started committing, tx=" +
+ toString());
+ case Status.STATUS_COMMITTED:
+ throw new IllegalStateException("Already committed, tx=" + toString());
+ case Status.STATUS_MARKED_ROLLBACK:
+ throw new RollbackException("Already marked for rollback, tx=" +
+ toString());
+ case Status.STATUS_ROLLING_BACK:
+ throw new RollbackException("Already started rolling back, tx=" +
+ toString());
+ case Status.STATUS_ROLLEDBACK:
+ throw new RollbackException("Already rolled back, tx=" + toString());
+ case Status.STATUS_NO_TRANSACTION:
+ throw new IllegalStateException("No transaction, tx=" + toString());
+ case Status.STATUS_UNKNOWN:
+ throw new IllegalStateException("Unknown state, tx=" + toString());
+ default:
+ throw new IllegalStateException("Illegal status: " +
+ TxUtils.getStatusAsString(status) +
+ ", tx=" + toString());
+ }
+ }
+
+ /**
+ * Interrupt all threads involved with transaction
+ * This is called on timeout
+ */
+ private void interruptThreads()
+ {
+ TxManager manager = TxManager.getInstance();
+ if (manager.isInterruptThreads())
+ {
+ HashSet clone = (HashSet) threads.clone();
+ threads.clear();
+ for (Iterator i = clone.iterator(); i.hasNext();)
+ {
+ Thread thread = (Thread) i.next();
+ try
+ {
+ thread.interrupt();
+ }
+ catch (Throwable ignored)
+ {
+ if (trace)
+ log.trace("Ignored error interrupting thread: " +
+ thread, ignored);
+ }
+ }
+ }
+ }
+
+ private void logXAException(XAException xae)
+ {
+ log.warn("XAException: tx=" + toString() + " errorCode=" +
+ TxUtils.getXAErrorCodeAsString(xae.errorCode), xae);
+ if (xaExceptionFormatter != null)
+ xaExceptionFormatter.formatXAException(xae, log);
+ }
+
+ /**
+ * Mark this transaction as non-existing.
+ */
+ private synchronized void instanceDone()
+ {
+ TxManager manager = TxManager.getInstance();
+
+ if (status == Status.STATUS_COMMITTED)
+ manager.incCommitCount();
+ else
+ manager.incRollbackCount();
+
+ // Clear tables refering to external objects.
+ // Even if a client holds on to this instance forever, the objects
+ // that we have referenced may be garbage collected.
+ parentCoordinator = null;
+ sync = null;
+ xaResources = null;
+ remoteResources = null;
+ transactionLocalMap.clear();
+ threads.clear();
+
+ // Garbage collection
+ manager.releaseTransactionImpl(this);
+
+ // Set the status
+ status = Status.STATUS_NO_TRANSACTION;
+
+ // Notify all threads waiting for the lock.
+ notifyAll();
+
+ // set the done flag
+ done = true;
+ }
+
+ /**
+ * Cancel the timeout.
+ * This will release the lock while calling out.
+ */
+ private void cancelTimeout()
+ {
+ if (timeout != null)
+ {
+ unlock();
+ try
+ {
+ timeout.cancel();
+ }
+ catch (Exception e)
+ {
+ if (trace)
+ log.trace("failed to cancel timeout, tx=" + toString(), e);
+ }
+ finally
+ {
+ lock();
+ }
+ timeout = null;
+ }
+ }
+
+ /**
+ * Create prepared timeout.
+ */
+ private void createPreparedTimeout()
+ {
+ preparedTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance().getPreparedTimeoutMillis(),
+ new TimeoutTarget()
+ {
+ public void timedOut(Timeout timeout)
+ {
+ if (trace)
+ log.trace("Prepared timeout expired for tx=" +
+ TransactionImpl.this.toString() +
+ ", calling replayCompletion " +
+ "on the recovery coordinator");
+ try
+ {
+ recoveryCoordinator.replayCompletion(registeredResource);
+ }
+ catch (NoSuchObjectException noCoordinator)
+ {
+ if (trace)
+ {
+ log.trace("Exception in replayCompletion: " +
+ "no coordinator for tx=" + toString(),
+ noCoordinator);
+ log.trace("Rolling back transaction branch, tx=" +
+ TransactionImpl.this.toString());
+ }
+ try
+ {
+ rollbackBranch();
+ }
+ catch (Exception e)
+ {
+ if (trace)
+ log.trace("Exception in transaction branch rollback,"
+ + " tx=" + TransactionImpl.this.toString(),
+ e);
+
+ }
+ }
+ catch (Exception ignore)
+ {
+ if (trace)
+ log.trace("Ignoring exception in replayCompletion, tx="
+ + TransactionImpl.this.toString(), ignore);
+ }
+ if (!done)
+ {
+ lock();
+ try
+ {
+ if (preparedTimeout != null)
+ {
+ preparedTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance()
+ .getPreparedTimeoutMillis(),
+ this);
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Cancel prepared timeout.
+ */
+ private void cancelPreparedTimeout()
+ {
+ if (preparedTimeout != null)
+ {
+ Timeout pt = preparedTimeout;
+ preparedTimeout = null;
+
+ unlock();
+ try
+ {
+ pt.cancel();
+ }
+ catch (Exception e)
+ {
+ if (trace)
+ log.trace("failed to cancel prepared timeout, tx=" + toString(),
+ e);
+ }
+ finally
+ {
+ lock();
+ }
+ }
+ }
+
+ /**
+ * Return the resource for the given XAResource
+ */
+ private EnlistedXAResource findResource(XAResource xaRes)
+ {
+ // A linear search may seem slow, but please note that
+ // the number of XA resources registered with a transaction
+ // are usually low.
+ // Note: This searches backwards intentionally! It ensures that
+ // if this resource was enlisted multiple times, then the last one
+ // will be returned. All others should be in the state RS_ENDED.
+ // This allows ResourceManagers that always return false from isSameRM
+ // to be enlisted and delisted multiple times.
+ for (int idx = xaResources.size() - 1; idx >= 0; --idx)
+ {
+ EnlistedXAResource resource =
+ (EnlistedXAResource) xaResources.get(idx);
+ if (xaRes == resource.getXAResource())
+ return resource;
+ }
+
+ return null;
+ }
+
+ private EnlistedXAResource findResourceManager(XAResource xaRes)
+ throws XAException
+ {
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+ EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+ if (resource.isResourceManager(xaRes))
+ return resource;
+ }
+ return null;
+ }
+
+ /**
+ * Add a resource, expanding tables if needed.
+ *
+ * @param xaRes The new XA resource to add. It is assumed that the
+ * resource is not already in the table of XA resources.
+ * @param branchXid The Xid for the transaction branch that is to
+ * be used for associating with this resource.
+ * @param sameRMResource The resource of the first
+ * XA resource having the same resource manager as
+ * <code>xaRes</code>, or <code>null</code> if <code>xaRes</code>
+ * is the first resource seen with this resource manager.
+ * @return the new resource
+ */
+ private EnlistedXAResource addResource(XAResource xaRes,
+ Xid branchXid,
+ EnlistedXAResource sameRMResource)
+ {
+ EnlistedXAResource resource =
+ new EnlistedXAResource(xaRes, branchXid, sameRMResource);
+ xaResources.add(resource);
+
+ // Remember the first resource that wants the last resource gambit
+ if (lastResource == null && xaRes instanceof LastResource)
+ lastResource = resource;
+
+ return resource;
+ }
+
+ /**
+ * End Tx association for all resources.
+ */
+ private void endResources()
+ {
+ for (int idx = 0; idx < xaResources.size(); ++idx)
+ {
+ EnlistedXAResource resource =
+ (EnlistedXAResource) xaResources.get(idx);
+ try
+ {
+ resource.endResource();
+ }
+ catch (XAException xae)
+ {
+ logXAException(xae);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = xae;
+ }
+ }
+ resourcesEnded = true; // Too late to enlist new resources.
+ }
+
+
+ /**
+ * Call synchronization <code>beforeCompletion()</code>.
+ * This will release the lock while calling out.
+ */
+ private void doBeforeCompletion()
+ {
+ unlock();
+ try
+ {
+ for (int i = 0; i < syncCount; i++)
+ {
+ try
+ {
+ if (trace)
+ log.trace("calling sync " + i + ", " + sync[i] +
+ " tx=" + toString());
+
+ sync[i].beforeCompletion();
+ }
+ catch (Throwable t)
+ {
+ if (trace)
+ log.trace("failed before completion " + sync[i], t);
+
+ status = Status.STATUS_MARKED_ROLLBACK;
+
+ // save the cause off so the user can inspect it
+ cause = t;
+ break;
+ }
+ }
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ /**
+ * Call synchronization <code>afterCompletion()</code>.
+ * This will release the lock while calling out.
+ */
+ private void doAfterCompletion()
+ {
+ // Assert: Status indicates: Too late to add new synchronizations.
+ unlock();
+ try
+ {
+ for (int i = 0; i < syncCount; i++)
+ {
+ try
+ {
+ sync[i].afterCompletion(status);
+ }
+ catch (Throwable t)
+ {
+ if (trace)
+ log.trace("failed after completion " + sync[i], t);
+ }
+ }
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ /**
+ * Groups two heuristic codes into a composite code.
+ *
+ * @param code1 a heuristic code (one of
+ * <code>XAException.XA_HEURxxx</code>)
+ * @param code2 another heuristic code
+ * @return the composite heuristic code.
+ */
+ private static int groupHeuristics(int code1, int code2)
+ {
+ switch (code2)
+ {
+ case XAException.XA_HEURMIX:
+ code1 = XAException.XA_HEURMIX;
+ break;
+ case XAException.XA_HEURRB:
+ if (code1 == HEUR_NONE)
+ code1 = XAException.XA_HEURRB;
+ else if (code1 == XAException.XA_HEURCOM ||
+ code1 == XAException.XA_HEURHAZ)
+ code1 = XAException.XA_HEURMIX;
+ break;
+ case XAException.XA_HEURCOM:
+ if (code1 == HEUR_NONE)
+ code1 = XAException.XA_HEURCOM;
+ else if (code1 == XAException.XA_HEURRB ||
+ code1 == XAException.XA_HEURHAZ)
+ code1 = XAException.XA_HEURMIX;
+ break;
+ case XAException.XA_HEURHAZ:
+ if (code1 == HEUR_NONE)
+ code1 = XAException.XA_HEURHAZ;
+ else if (code1 == XAException.XA_HEURCOM ||
+ code1 == XAException.XA_HEURRB)
+ code1 = XAException.XA_HEURMIX;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return code1;
+ }
+
+ /**
+ * We got another heuristic.
+ * <p/>
+ * Promote <code>heuristicCode</code> if needed and remember to call forget
+ * on the resource at a later time, upon request from the coordinator
+ * or from a management application.
+ *
+ * This will release the lock while calling out.
+ *
+ * @param resource the resource of the XA resource that got a
+ * heuristic in our internal tables, or <code>null</code>
+ * if the heuristic came from here.
+ * @param code the heuristic code, one of
+ * <code>XAException.XA_HEURxxx</code>.
+ */
+ private void gotHeuristic(EnlistedResource resource, int code)
+ {
+ heuristicCode = groupHeuristics(heuristicCode, code);
+ if (resource != null)
+ resource.remember(code);
+ }
+
+ /**
+ * Checks if a mixed decision happened and promote the
+ * <code>heuristicCode</code> in that case.
+ *
+ * @return the <code>heuristicCode</code>.
+ */
+ private int consolidateHeuristics()
+ {
+ if (committedResources > 0 && rolledbackResources > 0)
+ heuristicCode = XAException.XA_HEURMIX;
+
+ return heuristicCode;
+ }
+
+ /**
+ * Gets the full heuristic code, which includes an heuristic hazard if this
+ * <code>TransactionImpl</code> could not reach a resource during the second
+ * phase of 2PC.
+ *
+ * @return the full heuristic code, one of
+ * <code>XAException.XA_HEURxxx</code>.
+ */
+ private int getFullHeuristicCode()
+ {
+ return (heuristicHazard) ?
+ groupHeuristics(heuristicCode, XAException.XA_HEURHAZ) :
+ heuristicCode;
+ }
+
+ /**
+ * Check for heuristics and throw exception if any found.
+ */
+ private void checkHeuristics()
+ throws HeuristicHazardException,
+ HeuristicMixedException,
+ HeuristicRollbackException
+ {
+ if (committedResources > 0 && rolledbackResources > 0)
+ heuristicCode = XAException.XA_HEURMIX;
+
+ switch (getFullHeuristicCode())
+ {
+ case XAException.XA_HEURHAZ:
+ if (trace)
+ log.trace("Throwing HeuristicHazardException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicHazardException();
+ case XAException.XA_HEURMIX:
+ if (trace)
+ log.trace("Throwing HeuristicMixedException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicMixedException();
+ case XAException.XA_HEURRB:
+ if (trace)
+ log.trace("Throwing HeuristicRollbackException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicRollbackException();
+ case XAException.XA_HEURCOM:
+ // Why isn't HeuristicCommitException used in JTA ?
+ // And why define something that is not used ?
+ if (trace)
+ log.trace("NOT Throwing HeuristicCommitException, tx=" + toString()
+ + ", status=" + TxUtils.getStatusAsString(status));
+ return;
+ }
+ }
+
+ private void checkHeuristicsButDoNotThrowHeuristicHazard()
+ throws HeuristicMixedException,
+ HeuristicRollbackException
+ {
+ if (committedResources > 0 && rolledbackResources > 0)
+ heuristicCode = XAException.XA_HEURMIX;
+
+ switch (getFullHeuristicCode())
+ {
+ case XAException.XA_HEURHAZ:
+ if (TxManager.getInstance().isReportHeuristicHazardAsHeuristicMixed())
+ {
+ if (trace)
+ log.trace("Throwing HeuristicMixedException instead of " +
+ "HeuristicHazardException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicMixedException();
+ }
+ else
+ {
+ if (trace)
+ log.trace("NOT throwing HeuristicHazardException, tx=" +
+ toString() + ", status=" +
+ TxUtils.getStatusAsString(status));
+ return;
+ }
+ case XAException.XA_HEURMIX:
+ if (trace)
+ log.trace("Throwing HeuristicMixedException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicMixedException();
+ case XAException.XA_HEURRB:
+ if (trace)
+ log.trace("Throwing HeuristicRollbackException, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status));
+ throw new HeuristicRollbackException();
+ case XAException.XA_HEURCOM:
+ // Why isn't HeuristicCommitException used in JTA ?
+ // And why define something that is not used ?
+ if (trace)
+ log.trace("NOT Throwing HeuristicCommitException, tx=" + toString()
+ + ", status=" + TxUtils.getStatusAsString(status));
+ return;
+ }
+ }
+
+ /**
+ * Registers this transaction as a resource with its parent coordinator.
+ * Called from <code>enlistResource</code>.
+ *
+ * @throws RollbackException
+ * @throws java.lang.IllegalStateException
+ *
+ * @throws SystemException
+ */
+ private void registerResourceWithParentCoordinator()
+ throws RollbackException
+ {
+ if (trace)
+ log.trace("registerResourceWithParentCoordinator(): Entered, tx=" +
+ toString() + ", status=" + TxUtils.getStatusAsString(status));
+
+ Resource r = null;
+
+ if (Proxy.isProxyClass(parentCoordinator.getClass()))
+ {
+ // DTM coordinator case
+ if (dtmResourceFactory != null)
+ r = dtmResourceFactory.createResource(getLocalIdValue());
+ else
+ {
+ log.warn("Missing DTM resource factory, tx=" + toString());
+ status = Status.STATUS_MARKED_ROLLBACK;
+ throw new RollbackException("Missing DTM resource factory");
+ }
+ }
+ else
+ {
+ // OTS coordinator case
+ if (otsResourceFactory != null)
+ r = otsResourceFactory.createResource(getLocalIdValue());
+ else
+ {
+ log.warn("Missing OTS resource factory, tx=" + toString());
+ status = Status.STATUS_MARKED_ROLLBACK;
+ throw new RollbackException("Missing OTS resource factory");
+ }
+ }
+
+ try
+ {
+ unlock();
+ try
+ {
+ recoveryCoordinator = parentCoordinator.registerResource(r);
+ registeredResource = r;
+ }
+ finally
+ {
+ lock();
+ }
+ }
+ catch (TransactionInactiveException e)
+ {
+ status = Status.STATUS_MARKED_ROLLBACK;
+ if (trace)
+ log.trace("Got TransactionInactiveException, " +
+ "throwing RollbackException");
+ throw new RollbackException(e.toString());
+ }
+ catch (TransactionRolledbackException e)
+ {
+ status = Status.STATUS_MARKED_ROLLBACK;
+ if (trace)
+ log.trace("Got TransactionRolledbackException, " +
+ "throwing RollbackException");
+ throw new RollbackException(e.toString());
+ }
+ catch (RemoteException e)
+ {
+ status = Status.STATUS_MARKED_ROLLBACK;
+ if (trace)
+ log.trace("Got RemoteException, throwing RollbackException");
+ throw new RollbackException(e.toString());
+ }
+ }
+
+ /**
+ * Prepare all enlisted resources.
+ * If the first phase of the commit process results in a decision
+ * to commit the <code>status</code> will be
+ * <code>Status.STATUS_PREPARED</code> on return.
+ * Otherwise the <code>status</code> will be
+ * <code>Status.STATUS_MARKED_ROLLBACK</code> on return.
+ * This will release the lock while calling out.
+ *
+ * @return True iff all resources voted read-only.
+ */
+ private boolean prepareResources()
+ {
+ boolean readOnly = true;
+
+ status = Status.STATUS_PREPARING;
+
+ // Prepare XA resources.
+
+ if (trace)
+ log.trace("Preparing " + xaResources.size() + " XA resource(s)");
+
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+ // Abort prepare on state change.
+ if (status != Status.STATUS_PREPARING)
+ return false;
+
+ EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+
+ if (resource.isResourceManager() == false)
+ continue; // This RM already prepared.
+
+ // Ignore the last resource it is done later
+ if (resource == lastResource)
+ continue;
+
+ try
+ {
+ int vote = resource.prepare();
+
+ if (trace)
+ {
+ String strVote =
+ (vote == RS_VOTE_OK) ?
+ "OK" :
+ ((vote == RS_VOTE_READONLY) ? "READONLY" : "ROLLBACK");
+
+ log.trace("XA resource voted " + strVote);
+ }
+
+ if (vote == RS_VOTE_OK)
+ readOnly = false;
+ else if (vote != RS_VOTE_READONLY)
+ {
+ // Neither commit vote nor readonly vote: rollback.
+ if (trace)
+ log.trace("prepareResources got a rollback vote " +
+ "from an XA resource, tx=" + toString() +
+ " resource=" + resource, new Exception());
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return false;
+ }
+ }
+ catch (XAException e)
+ {
+ readOnly = false;
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ case XAException.XA_HEURCOM:
+ // Heuristic commit is not that bad when preparing.
+ // But it means trouble if we have to rollback.
+ gotHeuristic(resource, e.errorCode);
+ break;
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ // Other heuristic exceptions cause a rollback
+ gotHeuristic(resource, e.errorCode);
+ // fall through
+ default:
+ cause = e;
+ status = Status.STATUS_MARKED_ROLLBACK;
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof RecoveryTestingException)
+ throw (RecoveryTestingException)t;
+
+ if (trace)
+ log.trace("unhandled throwable in prepareResources " +
+ toString(), t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = t;
+ return false;
+ }
+ }
+
+ // Prepare remote DTM/OTS resources.
+
+ if (trace)
+ log.trace("Preparing " + remoteResources.size() +
+ " remote resource(s)");
+
+ for (int i = 0; i < remoteResources.size(); ++i)
+ {
+ // Abort prepare on state change.
+ if (status != Status.STATUS_PREPARING)
+ return false;
+
+ EnlistedRemoteResource resource =
+ (EnlistedRemoteResource) remoteResources.get(i);
+
+ try
+ {
+ int vote = resource.prepare();
+
+ if (trace)
+ {
+ String strVote =
+ (vote == RS_VOTE_OK) ?
+ "OK" :
+ ((vote == RS_VOTE_READONLY) ? "READONLY" : "ROLLBACK");
+
+ log.trace("Remote resource voted " + strVote);
+ }
+
+ if (vote == RS_VOTE_OK)
+ readOnly = false;
+ else if (vote != RS_VOTE_READONLY)
+ {
+ // Neither commit vote nor readonly vote: rollback.
+ if (trace)
+ log.trace("prepareResources got a rollback vote " +
+ "from a remote resource, tx=" + toString() +
+ ", resource=" + resource, new Exception());
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ // Blanket catch for the following exception types:
+ // TransactionAlreadyPreparedException,
+ // HeuristicMixedException,
+ // HeuristicHazardException, and
+ // RemoteException, which includes TransactionRolledbackException.
+ if (trace)
+ log.trace("Exception in prepareResources, tx=" + toString(), e);
+
+ if (e instanceof HeuristicMixedException)
+ gotHeuristic(resource, XAException.XA_HEURMIX);
+ else if (e instanceof HeuristicHazardException)
+ gotHeuristic(resource, XAException.XA_HEURHAZ);
+
+ cause = e;
+ status = Status.STATUS_MARKED_ROLLBACK;
+ }
+ }
+
+ // Abort prepare on state change.
+ if (status != Status.STATUS_PREPARING)
+ return false;
+
+ // Are we doing the last resource gambit?
+ if (lastResource != null)
+ {
+ try
+ {
+ lastResource.prepareLastResource();
+ lastResource.commit(false);
+ }
+ catch (XAException e)
+ {
+ if (trace)
+ log.trace("prepareResources got XAException" +
+ " when committing last resource, tx=" + toString(), e);
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ // No need to handle the case of XAException.XA_HEURCOM,
+ // which is swallowed by EnlistedXAResource.commit()
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ //usually throws an exception, but not for a couple of cases.
+ gotHeuristic(lastResource, e.errorCode);
+ // fall through
+ default:
+ // take the XAException as a "no" vote from the last resource
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = e;
+ return false; // to make the caller look at
+ // at the "marked rollback" status
+ }
+ }
+ catch (Throwable t)
+ {
+ if (trace)
+ log.trace("unhandled throwable in prepareResources, tx=" +
+ toString(), t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ cause = t;
+ return false; // to make the caller look at
+ // the "marked rollback" status
+ }
+ }
+
+ if (status == Status.STATUS_PREPARING)
+ status = Status.STATUS_PREPARED;
+ else
+ return false;
+
+ return readOnly;
+ }
+
+ /**
+ * Commit all enlisted resources.
+ * This will release the lock while calling out.
+ */
+ private void commitResources(boolean onePhase)
+ {
+ status = Status.STATUS_COMMITTING;
+ doCommitResources(onePhase);
+ if (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0)
+ {
+ int retryLimit = TxManager.getInstance().getCompletionRetryLimit();
+ for (int n = 0;
+ n < retryLimit &&
+ (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0);
+ n++)
+ {
+ sleep(TxManager.getInstance().getCompletionRetryTimeoutMillis());
+ doCommitResources(onePhase);
+ }
+ }
+ checkCommitCompletion();
+ }
+
+ /**
+ * Do the actual resource commiting.
+ */
+ private void doCommitResources(boolean onePhase)
+ {
+ xaResourcesToRetry = 0;
+ remoteResourcesToRetry = 0;
+
+ // Commit XA resources.
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+
+ // Abort commit on state change.
+ if (status != Status.STATUS_COMMITTING)
+ return;
+
+ EnlistedXAResource resource =
+ (EnlistedXAResource) xaResources.get(i);
+
+ // Ignore the last resource, it is already committed
+ if (onePhase == false && lastResource == resource)
+ continue;
+
+ try
+ {
+ resource.commit(onePhase);
+ }
+ catch (XAException e)
+ {
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ // No need to handle the case of XAException.XA_HEURCOM,
+ // which is swallowed by EnlistedXAResource.commit()
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ //usually throws an exception, but not for a couple of cases.
+ gotHeuristic(resource, e.errorCode);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ break;
+ case XAException.XAER_RMERR:
+ // Not much we can do if there is an RMERR in the
+ // commit phase of 2pc. I guess we take it as a heuristic
+ // rollback and try the other RMs.
+ gotHeuristic(null, XAException.XA_HEURRB);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ break;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ // The XAResource remains prepared. Retry the commit later.
+ xaResourcesToRetry++;
+ break;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // This should never happen!
+ cause = e;
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ log.warn("Could not recover from unexpected XAException: " +
+ "tx=" + toString() + " errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) t;
+ if (trace)
+ log.trace("Unhandled throwable in doCommitResources " +
+ toString(), t);
+ }
+ }
+
+ // Commit remote DTM/OTS resources.
+ for (int i = 0; i < remoteResources.size(); ++i)
+ {
+ // Abort commit on state change.
+ if (status != Status.STATUS_COMMITTING)
+ return;
+
+ EnlistedRemoteResource resource =
+ (EnlistedRemoteResource) remoteResources.get(i);
+
+ try
+ {
+ resource.commit(onePhase);
+ }
+ catch (TransactionRolledbackException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ {
+ // The resource decided to rollback in the second phase of 2PC:
+ // this is a heuristic outcome.
+ gotHeuristic(null, XAException.XA_HEURRB);
+ }
+ }
+ catch (HeuristicRollbackException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURRB);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ }
+ catch (HeuristicMixedException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURMIX);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ }
+ catch (HeuristicHazardException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURHAZ);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ }
+ catch (TransactionNotPreparedException e)
+ {
+ // This should never happen!
+ cause = e;
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ log.warn("Could not recover from unexpected exception in " +
+ "doCommitResources: tx=" + toString() +
+ " exception=" + e);
+ }
+ catch (NoSuchObjectException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ {
+ // Do nothing -- we must be doing crash recovery and the
+ // remote resource has been committed before the crash.
+ log.warn("Ignoring NoSuchObjectException in doCommitResources: "
+ + " tx=" + toString() + " exception=" + e);
+ }
+ }
+ catch (RemoteException e)
+ {
+ if (trace)
+ log.trace("Exception in doCommitResources, tx=" + toString(), e);
+ if (onePhase)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ else
+ {
+ // The remote resource remains prepared. Retry the commit later.
+ remoteResourcesToRetry++;
+ }
+ }
+ }
+ }
+
+ /**
+ * Create retry timeout for calling retryCommitXAResources().
+ */
+ private void commitXAResourcesAfterTimeout()
+ {
+ xaRetryTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance().getXARetryTimeoutMillis(),
+ new TimeoutTarget()
+ {
+ public void timedOut(Timeout timeout)
+ {
+ if (trace)
+ log.trace("XA retry timeout expired for tx=" +
+ TransactionImpl.this.toString() +
+ ", retrying commit on XAResources");
+ lock();
+ try
+ {
+ retryCommitXAResources();
+ if (xaResourcesToRetry > 0)
+ {
+ xaRetryTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance().getXARetryTimeoutMillis(),
+ this);
+ }
+ else if (remoteResourcesToRetry == 0 &&
+ heuristicCode == HEUR_NONE)
+ {
+ completeTransaction();
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ });
+ }
+
+ // Pre-condition: this TransactionImpl instance is locked.
+ // Post-condition: this TransactionImpl instance remains locked.
+ private void retryCommitXAResources()
+ {
+ if (trace)
+ log.trace("Retrying commit XA resources, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status) +
+ ", xaResourcesToRetry=" + xaResourcesToRetry);
+
+ xaResourcesToRetry = 0;
+
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+ EnlistedXAResource resource =
+ (EnlistedXAResource) xaResources.get(i);
+
+ // Ignore the last resource, it is already committed
+ if (lastResource == resource)
+ continue;
+
+ try
+ {
+ resource.commit(false);
+ }
+ catch (XAException e)
+ {
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ // No need to handle the case of XAException.XA_HEURCOM,
+ // which is swallowed by EnlistedXAResource.commit()
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ //usually throws an exception, but not for a couple of cases.
+ gotHeuristic(resource, e.errorCode);
+ break;
+ case XAException.XAER_RMERR:
+ // Not much we can do if there is an RMERR in the
+ // commit phase of 2pc. I guess we take it as a heuristic
+ // rollback and try the other RMs.
+ gotHeuristic(null, XAException.XA_HEURRB);
+ break;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ // The XAResource remains prepared. Retry the commit later.
+ xaResourcesToRetry++;
+ break;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // This should never happen!
+ log.warn("Could not recover from unexpected XAException: " +
+ "tx=" + toString() + " errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) t;
+ if (trace)
+ log.trace("unhandled throwable in retryCommitXAResources " +
+ this, t);
+ }
+ }
+
+ if (trace)
+ log.trace("Finished retrying commit XA resources, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status) +
+ ", xaResourcesToRetry=" + xaResourcesToRetry);
+
+ checkCommitCompletion();
+ }
+
+ // Pre-condition: this TransactionImpl instance is locked.
+ // Post-condition: this TransactionImpl instance remains locked.
+ private void retryCommitRemoteResources()
+ {
+ if (trace)
+ log.trace("Retrying commit remote resources, tx=" + toString() +
+ ", status=" + TxUtils.getStatusAsString(status) +
+ ", " + remoteResources.size() + " remote resources");
+
+ remoteResourcesToRetry = 0;
+
+ // Commit remote DTM/OTS resources.
+ for (int i = 0; i < remoteResources.size(); ++i)
+ {
+ EnlistedRemoteResource resource =
+ (EnlistedRemoteResource) remoteResources.get(i);
+
+ try
+ {
+ resource.commit(false);
+ }
+ catch (HeuristicRollbackException e)
+ {
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURRB);
+ }
+ catch (HeuristicMixedException e)
+ {
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURMIX);
+ }
+ catch (HeuristicHazardException e)
+ {
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+
+ gotHeuristic(resource, XAException.XA_HEURHAZ);
+ }
+ catch (TransactionNotPreparedException e)
+ {
+ // This should never happen!
+ cause = e;
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+ log.warn("Could not recover from unexpected exception in " +
+ "retryCommitRemoteResources: tx=" + toString() +
+ " exception=" + e);
+ }
+ catch (NoSuchObjectException e)
+ {
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+
+ // Do nothing -- we must be doing crash recovery and the
+ // remote resource has been committed before the crash.
+ log.warn("Ignoring NoSuchObjectException in " +
+ "retryCommitRemoteResources: tx=" + toString() +
+ " exception=" + e);
+ }
+ catch (RemoteException e)
+ {
+ if (trace)
+ log.trace("Exception in retryCommitRemoteResources, tx=" +
+ toString(), e);
+
+ // The remote resource remains prepared. Retry the commit later.
+ remoteResourcesToRetry++;
+ }
+ }
+
+ if (trace)
+ log.trace("Finished retrying commit remote resources, tx=" + toString()
+ + ", status=" + TxUtils.getStatusAsString(status) +
+ ", remoteResourcesToRetry=" + remoteResourcesToRetry +
+ " of " + remoteResources.size() + " remote resources");
+
+ checkCommitCompletion();
+ }
+
+ private void checkCommitCompletion()
+ {
+ if (status != Status.STATUS_COMMITTING)
+ return;
+
+ consolidateHeuristics();
+
+ if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)
+ {
+ status = Status.STATUS_COMMITTED;
+ if (heuristicHazard || heuristicCode != HEUR_NONE)
+ {
+ heuristicHazard = false;
+
+ // Save the heuristic status to stable storage. Do not include
+ // a locally-detected heuristic hazard, as the actual heuristic
+ // outcome is known at this point.
+ RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ logger.saveHeuristicStatus(xid.getLocalIdValue(),
+ foreignTx,
+ xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ inboundBranchQualifier,
+ status,
+ heuristicCode,
+ heuristicHazard,
+ getXAResourceHeuristics(),
+ getRemoteResourceHeuristics());
+ if (!foreignTx &&
+ !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions() &&
+ forgetResources())
+ {
+ // Clear heuristic status
+ if (logger != null)
+ {
+ long localId = xid.getLocalIdValue();
+ logger.clearHeuristicStatus(localId);
+ if (completionHandler != null)
+ completionHandler.handleTxCompletion(localId);
+ }
+ }
+ }
+ else if (completionHandler != null)
+ completionHandler.handleTxCompletion(xid.getLocalIdValue());
+ }
+ else if (!heuristicHazard)
+ {
+ heuristicHazard = true;
+
+ // Save the heuristic status to stable storage, including the
+ // locally-detected heuristic hazard.
+ RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ logger.saveHeuristicStatus(xid.getLocalIdValue(),
+ foreignTx,
+ xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ inboundBranchQualifier,
+ status,
+ heuristicCode,
+ heuristicHazard,
+ getXAResourceHeuristics(),
+ getRemoteResourceHeuristics());
+
+ if (!foreignTx &&
+ !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions())
+ forgetResources();
+ }
+ }
+
+ /**
+ * Rollback all enlisted resources and complete the transaction.
+ * This will release the lock while calling out.
+ */
+ private void rollbackResourcesAndCompleteTransaction()
+ {
+ status = Status.STATUS_ROLLING_BACK;
+ doRollbackResources();
+ if (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0)
+ {
+ int retryLimit = TxManager.getInstance().getCompletionRetryLimit();
+ for (int n = 0;
+ n < retryLimit &&
+ (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0);
+ n++)
+ {
+ sleep(TxManager.getInstance().getCompletionRetryTimeoutMillis());
+ doRollbackResources();
+ }
+ }
+
+ if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)
+ {
+ status = Status.STATUS_ROLLEDBACK;
+
+ if (consolidateHeuristics() != HEUR_NONE || heuristicHazard)
+ {
+ heuristicHazard = false;
+
+ // Save the heuristic status to stable storage. Do not include
+ // a locally-generated heuristic hazard, as the actual heuristic
+ // outcome is known at this point.
+ RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ logger.saveHeuristicStatus(xid.getLocalIdValue(),
+ foreignTx,
+ xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ inboundBranchQualifier,
+ status,
+ heuristicCode,
+ heuristicHazard,
+ getXAResourceHeuristics(),
+ getRemoteResourceHeuristics());
+ if (!foreignTx &&
+ !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions() &&
+ forgetResources())
+ {
+ // Clear heuristic status
+ if (logger != null)
+ {
+ long localId = xid.getLocalIdValue();
+ logger.clearHeuristicStatus(localId);
+ }
+ }
+ }
+ else
+ completeTransaction();
+ }
+ else
+ {
+ if (!heuristicHazard)
+ {
+ heuristicHazard = true;
+ consolidateHeuristics();
+
+ // Save the heuristic status to stable storage, including the
+ // locally-detected heuristic hazard.
+ RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+ if (logger != null)
+ logger.saveHeuristicStatus(xid.getLocalIdValue(),
+ foreignTx,
+ xid.getFormatId(),
+ xid.getGlobalTransactionId(),
+ inboundBranchQualifier,
+ status,
+ heuristicCode,
+ heuristicHazard,
+ getXAResourceHeuristics(),
+ getRemoteResourceHeuristics());
+ }
+ if (xaResourcesToRetry > 0)
+ rollbackXAResourcesAfterTimeout();
+ }
+ }
+
+ /**
+ * Do the actual resource rolling back.
+ */
+ private void doRollbackResources()
+ {
+ xaResourcesToRetry = 0;
+ remoteResourcesToRetry = 0;
+
+ // Rollback XA resources.
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+ EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+ try
+ {
+ resource.rollback();
+ }
+ catch (XAException e)
+ {
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ // No need to handle the case of XAException.XA_HEURRB,
+ // which is swallowed by EnlistedXAResource.rollback()
+ case XAException.XA_HEURCOM:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ gotHeuristic(resource, e.errorCode);
+ continue;
+ default:
+ cause = e;
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) t;
+ if (trace)
+ log.trace("unhandled throwable in doRollbackResources " + this,
+ t);
+ }
+ if (resource.getState() == RS_VOTE_OK)
+ xaResourcesToRetry++;
+ }
+
+ // Rollback remote DTM/OTS resources.
+ for (int i = 0; i < remoteResources.size(); ++i)
+ {
+ EnlistedRemoteResource resource =
+ (EnlistedRemoteResource) remoteResources.get(i);
+
+ try
+ {
+ resource.rollback();
+ }
+ catch (HeuristicCommitException e)
+ {
+ gotHeuristic(resource, XAException.XA_HEURCOM);
+ continue;
+ }
+ catch (HeuristicMixedException e)
+ {
+ gotHeuristic(resource, XAException.XA_HEURMIX);
+ continue;
+ }
+ catch (HeuristicHazardException e)
+ {
+ gotHeuristic(resource, XAException.XA_HEURHAZ);
+ continue;
+ }
+ catch (RemoteException e)
+ {
+ // No need to do much here. If the resource didn't get the
+ // rollback then it will eventually call replay completion on
+ // the recovery coordinator. We increase remoteResourcesToRetry
+ // for heuristic hazard reporting purposes only.
+ remoteResourcesToRetry++;
+ if (trace)
+ log.trace("Ignoring exception in remote resource rollback, tx=" +
+ toString(), e);
+ }
+ }
+ }
+
+ /**
+ * Create timeout for calling retryRollbackXAResources().
+ */
+ private void rollbackXAResourcesAfterTimeout()
+ {
+ xaRetryTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance().getXARetryTimeoutMillis(),
+ new TimeoutTarget()
+ {
+ public void timedOut(Timeout timeout)
+ {
+ if (trace)
+ log.trace("XA retry timeout expired for tx=" +
+ TransactionImpl.this.toString() +
+ ", retrying rollback on XAResources");
+ lock();
+ try
+ {
+ retryRollbackXAResources();
+ if (xaResourcesToRetry > 0)
+ {
+ xaRetryTimeout = timeoutFactory.createTimeout(
+ System.currentTimeMillis() +
+ TxManager.getInstance().getXARetryTimeoutMillis(),
+ this);
+ }
+ else if (remoteResourcesToRetry == 0 &&
+ heuristicCode == HEUR_NONE)
+ {
+ completeTransaction();
+ }
+ }
+ finally
+ {
+ unlock();
+ }
+ }
+ });
+ }
+
+ /**
+ * Cancel XA retry timeout.
+ */
+ private void cancelXARetryTimeout()
+ {
+ if (xaRetryTimeout != null)
+ {
+ Timeout rt = xaRetryTimeout;
+ xaRetryTimeout = null;
+
+ unlock();
+ try
+ {
+ rt.cancel();
+ }
+ catch (Exception e)
+ {
+ if (trace)
+ log.trace("failed to cancel XA retry timeout, tx=" + toString(),
+ e);
+ }
+ finally
+ {
+ lock();
+ }
+ }
+ }
+
+ private int[] getXAResourceHeuristics()
+ {
+ if (xaResourcesWithHeuristicDecisions == null)
+ return null;
+ else
+ {
+ int[] heurCodes = new int[xaResourcesWithHeuristicDecisions.size()];
+ for (int i = 0; i < xaResourcesWithHeuristicDecisions.size(); ++i)
+ {
+ EnlistedResource resource =
+ (EnlistedResource) xaResourcesWithHeuristicDecisions.get(i);
+ heurCodes[i] = resource.getHeuristicCode();
+ }
+ return heurCodes;
+ }
+ }
+
+ private HeuristicStatus[] getRemoteResourceHeuristics()
+ {
+ if (remoteResourcesWithHeuristicDecisions == null)
+ return null;
+ else
+ {
+ HeuristicStatus s[] =
+ new HeuristicStatus[remoteResourcesWithHeuristicDecisions.size()];
+ for (int i = 0; i < remoteResourcesWithHeuristicDecisions.size(); ++i)
+ {
+ EnlistedRemoteResource resource =
+ (EnlistedRemoteResource) remoteResourcesWithHeuristicDecisions.get(i);
+ s[i] = new HeuristicStatus(
+ resource.getHeuristicCode(),
+ resourceToString(resource.getRemoteResource()));
+ }
+ return s;
+ }
+ }
+
+
+ private void retryRollbackXAResources()
+ {
+ xaResourcesToRetry = 0;
+
+ // Rollback XA resources.
+ for (int i = 0; i < xaResources.size(); ++i)
+ {
+ EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+ try
+ {
+ resource.rollback();
+ }
+ catch (XAException e)
+ {
+ logXAException(e);
+ switch (e.errorCode)
+ {
+ // No need to handle the case of XAException.XA_HEURRB,
+ // which is swallowed by EnlistedXAResource.rollback()
+ case XAException.XA_HEURCOM:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ gotHeuristic(resource, e.errorCode);
+ continue;
+ default:
+ cause = e;
+ break;
+ }
+ }
+ catch (Throwable t)
+ {
+ if (t instanceof RecoveryTestingException)
+ throw (RecoveryTestingException) t;
+ if (trace)
+ log.trace("unhandled throwable in retryRollbackXAResources " +
+ this, t);
+ }
+ if (resource.getState() == RS_VOTE_OK)
+ xaResourcesToRetry++;
+ }
+
+ if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)
+ status = Status.STATUS_ROLLEDBACK;
+ }
+
+ private boolean forgetResources()
+ {
+ boolean success = true;
+ if (xaResourcesWithHeuristicDecisions != null)
+ {
+ for (int i = 0; i < xaResourcesWithHeuristicDecisions.size(); ++i)
+ {
+ EnlistedResource resource =
+ (EnlistedResource) xaResourcesWithHeuristicDecisions.get(i);
+ resource.forget();
+ if (resource.getState() != RS_FORGOT)
+ success = false;
+ }
+ }
+
+ if (remoteResourcesWithHeuristicDecisions != null)
+ {
+ for (int i = 0; i < remoteResourcesWithHeuristicDecisions.size(); ++i)
+ {
+ EnlistedResource resource =
+ (EnlistedResource) remoteResourcesWithHeuristicDecisions.get(i);
+ resource.forget();
+ if (resource.getState() != RS_FORGOT)
+ success = false;
+ }
+ }
+ return success;
+ }
+
+ /**
+ * Create an Xid representing a new branch of this transaction.
+ */
+ private Xid createXidBranch()
+ {
+ long branchId = ++lastBranchId;
+
+ return xidFactory.newBranch(xid, branchId);
+ }
+
+ /**
+ * Determine the commit strategy
+ *
+ * @return 0 for nothing to do, 1 for one phase and 2 for two phase
+ */
+ private int getCommitStrategy()
+ {
+ int xaResourceCount = xaResources.size();
+ int remoteResourceCount = remoteResources.size();
+ int resourceCount = xaResourceCount + remoteResourceCount;
+
+ if (resourceCount == 0)
+ return 0;
+
+ if (resourceCount == 1)
+ return 1;
+
+ // At least two resources have participated in this transaction.
+
+ if (remoteResourceCount > 0)
+ {
+ // They cannot be all XA resources on the same RM,
+ // as at least one of them is a remote DTM/OTS resource.
+ return 2;
+ }
+ else // They are all XA resources, look if the're all on the same RM.
+ {
+ // First XAResource surely isResourceManager, it's the first!
+ for (int i = 1; i < xaResourceCount; ++i)
+ {
+ EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+ if (resource.isResourceManager())
+ {
+ // this one is not the same rm as previous ones,
+ // there must be at least 2
+ return 2;
+ }
+
+ }
+ // All RMs are the same one, one phase commit is ok.
+ return 1;
+ }
+ }
+
+ /**
+ * Gets stringfied references for all enlisted resources that voted commit.
+ *
+ * @return an array of stringfied <code>Resource</code> references.
+ */
+ private String[] getStringfiedRemoteResourcesThatVotedCommit()
+ {
+ List resourcesThatVotedCommit = new ArrayList(remoteResources.size());
+
+ for (int i = 0; i < remoteResources.size(); ++i)
+ {
+ EnlistedRemoteResource enlistedRemoteResource =
+ (EnlistedRemoteResource) remoteResources.get(i);
+ if (enlistedRemoteResource.getState() == RS_VOTE_OK)
+ {
+ Resource r = enlistedRemoteResource.getRemoteResource();
+ resourcesThatVotedCommit.add(resourceToString(r));
+ }
+ }
+ return (String[]) resourcesThatVotedCommit.toArray(
+ new String[resourcesThatVotedCommit.size()]);
+ }
+
+ /**
+ * Check we have no outstanding work
+ *
+ * @throws IllegalStateException when there is still work
+ */
+ private void checkWork()
+ {
+ if (work != null)
+ throw new IllegalStateException("Work still outstanding: " + work +
+ ", tx=" + toString());
+ }
+
+ /**
+ * Waits (blocking the caller) for the specified number of milliseconds.
+ *
+ * @param millis the length of time to sleep in milliseconds.
+ */
+ private void sleep(long millis)
+ {
+ try
+ {
+ unlock();
+ Thread.sleep(millis);
+ }
+ catch (InterruptedException e)
+ {
+ // ignore
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ // Inner classes -------------------------------------------------
+
+ /**
+ * Represents a resource enlisted in the transaction.
+ * The resource can be either an XA resource or a remote DTM/OTS resource.
+ */
+ private interface EnlistedResource
+ {
+ public int getState();
+ public void remember(int heuristicCode);
+ int getHeuristicCode();
+ public void forget();
+ }
+
+ /**
+ * Represents an XA resource enlisted in the transaction
+ */
+ private class EnlistedXAResource
+ implements EnlistedResource
+ {
+ /**
+ * The XAResource
+ */
+ private XAResource xaResource;
+
+ /**
+ * The state of the resources
+ */
+ private int resourceState;
+
+ /**
+ * The related XA resource from the same resource manager
+ */
+ private EnlistedXAResource resourceSameRM;
+
+ /**
+ * The Xid of this resource
+ */
+ private Xid resourceXid;
+
+ /**
+ * The heuristic status of this resource
+ */
+ private int heurCode;
+
+ /**
+ * The XAResourceAccess instance to be released after this
+ * EnlistedXAResource is committed or rolled back, or null if no
+ * XAResourceAccess instance needs to be released.
+ */
+ private XAResourceAccess xaResourceAccess = null;
+
+ /**
+ * Create a new resource
+ */
+ public EnlistedXAResource(XAResource xaResource,
+ Xid resourceXid,
+ EnlistedXAResource resourceSameRM)
+ {
+ this.xaResource = xaResource;
+ this.resourceXid = resourceXid;
+ this.resourceSameRM = resourceSameRM;
+ resourceState = RS_NEW;
+ heurCode = HEUR_NONE;
+ }
+
+ /**
+ * Creates a prepared resource.
+ * This constructor is intended to be used during recovery only.
+ */
+ public EnlistedXAResource(XAWork xaWork)
+ {
+ this.xaResource = xaWork.res;
+ this.resourceXid = xaWork.xid;
+ this.xaResourceAccess = xaWork.xaResourceAccess;
+ this.resourceSameRM = null;
+ resourceState = RS_VOTE_OK;
+ heurCode = HEUR_NONE;
+ }
+
+ /**
+ * Creates a resource in a heuristically completed state.
+ * This constructor is intended to be used during recovery only.
+ */
+ public EnlistedXAResource(XAResource xaResource,
+ Xid resourceXid,
+ boolean committed)
+ {
+ this.xaResource = xaResource;
+ this.resourceXid = resourceXid;
+ this.resourceSameRM = null;
+ this.resourceState = (committed) ? RS_COMMITTED: RS_ROLLEDBACK;
+ heurCode = HEUR_UNKNOWN;
+ }
+
+ /**
+ * Get the XAResource for this resource
+ */
+ public XAResource getXAResource()
+ {
+ return xaResource;
+ }
+
+ /**
+ * Get the Xid for this resource
+ */
+ public Xid getXid()
+ {
+ return resourceXid;
+ }
+
+ /**
+ * Gets the state of the XAResource represented by this
+ * <code>EnlistedXAResource</code> instance.
+ */
+ public int getState()
+ {
+ return resourceState;
+ }
+
+ /**
+ * Is the resource enlisted?
+ */
+ public boolean isEnlisted()
+ {
+ return resourceState == RS_ENLISTED;
+ }
+
+ /**
+ * Is this a resource manager
+ */
+ public boolean isResourceManager()
+ {
+ return resourceSameRM == null;
+ }
+
+ /**
+ * Is this the resource manager for the passed XA resource
+ */
+ public boolean isResourceManager(XAResource xaRes)
+ throws XAException
+ {
+ return resourceSameRM == null && xaRes.isSameRM(xaResource);
+ }
+
+ /**
+ * Is the resource delisted and the XAResource always returns false
+ * for isSameRM
+ */
+ public boolean isDelisted(XAResource xaRes)
+ throws XAException
+ {
+ return resourceState == RS_ENDED && xaResource.isSameRM(xaRes) == false;
+ }
+
+ /**
+ * Call <code>start()</code> on a XAResource and update
+ * internal state information.
+ * This will release the lock while calling out.
+ *
+ * @return true when started, false otherwise
+ */
+ public boolean startResource()
+ throws XAException
+ {
+ int flags = XAResource.TMJOIN;
+
+ if (resourceSameRM == null)
+ {
+ switch (resourceState)
+ {
+ case RS_NEW:
+ flags = XAResource.TMNOFLAGS;
+ break;
+ case RS_SUSPENDED:
+ flags = XAResource.TMRESUME;
+ break;
+
+ default:
+ if (trace)
+ log.trace("Unhandled resource state: " + resourceState +
+ " (not RS_NEW or RS_SUSPENDED, using TMJOIN flags)");
+ }
+ }
+
+ if (trace)
+ log.trace("startResource(" +
+ xidFactory.toString(resourceXid) +
+ ") entered: " + xaResource.toString() +
+ " flags=" + flags);
+
+ unlock();
+ // OSH FIXME: resourceState could be incorrect during this callout.
+ try
+ {
+ try
+ {
+ xaResource.start(resourceXid, flags);
+ }
+ catch (XAException e)
+ {
+ throw e;
+ }
+ catch (Throwable t)
+ {
+ if (trace)
+ log.trace("unhandled throwable error in startResource",
+ t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ return false;
+ }
+
+ // Now the XA resource is associated with a transaction.
+ resourceState = RS_ENLISTED;
+ }
+ finally
+ {
+ lock();
+ if (trace)
+ log.trace("startResource(" +
+ xidFactory.toString(resourceXid) +
+ ") leaving: " + xaResource.toString() +
+ " flags=" + flags);
+ }
+ return true;
+ }
+
+ /**
+ * Delist the resource unless we already did it
+ */
+ public boolean delistResource(XAResource xaRes, int flag)
+ throws XAException
+ {
+ if (isDelisted(xaRes))
+ {
+ // This RM always returns false on isSameRM. Further,
+ // the last resource has already been delisted.
+ log.warn("Resource already delisted. tx=" +
+ TransactionImpl.this.toString());
+ return false;
+ }
+ endResource(flag);
+ return true;
+ }
+
+ /**
+ * End the resource
+ */
+ public void endResource()
+ throws XAException
+ {
+ if (resourceState == RS_ENLISTED || resourceState == RS_SUSPENDED)
+ {
+ if (trace)
+ log.trace("endresources(" + xaResource + "): state=" +
+ resourceState);
+ endResource(XAResource.TMSUCCESS);
+ }
+ }
+
+ /**
+ * Call <code>end()</code> on the XAResource and update
+ * internal state information.
+ * This will release the lock while calling out.
+ *
+ * @param flag The flag argument for the end() call.
+ */
+ private void endResource(int flag)
+ throws XAException
+ {
+ if (trace)
+ log.trace("endResource(" +
+ xidFactory.toString(resourceXid) +
+ ") entered: " + xaResource.toString() +
+ " flag=" + flag);
+
+ unlock();
+ // OSH FIXME: resourceState could be incorrect during this callout.
+ try
+ {
+ try
+ {
+ xaResource.end(resourceXid, flag);
+ }
+ catch (XAException e)
+ {
+ throw e;
+ }
+ catch (Throwable t)
+ {
+ if (trace)
+ log.trace("unhandled throwable error in endResource", t);
+ status = Status.STATUS_MARKED_ROLLBACK;
+ // Resource may or may not be ended after illegal exception.
+ // We just assume it ended.
+ resourceState = RS_ENDED;
+ return;
+ }
+
+ // Update our internal state information
+ if (flag == XAResource.TMSUSPEND)
+ resourceState = RS_SUSPENDED;
+ else
+ {
+ if (flag == XAResource.TMFAIL)
+ status = Status.STATUS_MARKED_ROLLBACK;
+ resourceState = RS_ENDED;
+ }
+ }
+ finally
+ {
+ lock();
+ if (trace)
+ log.trace("endResource(" +
+ xidFactory.toString(resourceXid) +
+ ") leaving: " + xaResource.toString() +
+ " flag=" + flag);
+ }
+ }
+
+ /**
+ * Remember that this resource has a heuristic outcome.
+ */
+ public void remember(int heuristicCode)
+ {
+ heurCode = heuristicCode;
+ if (xaResourcesWithHeuristicDecisions == null)
+ xaResourcesWithHeuristicDecisions = new ArrayList();
+ xaResourcesWithHeuristicDecisions.add(this);
+ }
+
+ /**
+ * Get the heuristic status of this resource.
+ */
+ public int getHeuristicCode()
+ {
+ return heurCode;
+ }
+
+ /**
+ * Forget the resource
+ */
+ public void forget()
+ {
+ unlock();
+ if (trace)
+ log.trace("Forget: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid));
+ try
+ {
+ xaResource.forget(resourceXid);
+ resourceState = RS_FORGOT;
+ }
+ catch (XAException xae)
+ {
+ logXAException(xae);
+ cause = xae;
+ if (xae.errorCode == XAException.XAER_NOTA)
+ {
+ if (trace)
+ log.trace("XAER_NOTA in forget: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid) +
+ "\nAssuming that the xid has been previously " +
+ "forgotten.", xae);
+ resourceState = RS_FORGOT;
+ }
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ /**
+ * Prepare the resource
+ */
+ public int prepare()
+ throws XAException
+ {
+ int vote;
+ unlock();
+ if (trace)
+ log.trace("Prepare: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid));
+ try
+ {
+ vote = xaResource.prepare(resourceXid);
+
+ if (vote == XAResource.XA_OK)
+ resourceState = RS_VOTE_OK;
+ else if (vote == XAResource.XA_RDONLY)
+ resourceState = RS_VOTE_READONLY;
+ }
+ catch (XAException e)
+ {
+ if (e.errorCode >= XAException.XA_RBBASE
+ && e.errorCode <= XAException.XA_RBEND)
+ {
+ if (trace)
+ log.trace("Got rollback vote from XAResource " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid) +
+ ", tx=" + TransactionImpl.this.toString() +
+ ", errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+
+ resourceState = RS_ROLLEDBACK;
+ }
+ else
+ throw e;
+ }
+ finally
+ {
+ lock();
+ }
+ return resourceState;
+ }
+
+ /**
+ * Prepare the last resource
+ */
+ public void prepareLastResource()
+ throws XAException
+ {
+ resourceState = RS_VOTE_OK;
+ }
+
+ /**
+ * Commit the resource
+ */
+ public void commit(boolean onePhase)
+ throws XAException
+ {
+ if (!onePhase && resourceState != RS_VOTE_OK)
+ return; // Voted read-only at prepare phase.
+
+ if (resourceSameRM != null)
+ return; // This RM already committed.
+
+ unlock();
+ if (trace)
+ log.trace("Commit: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid) +
+ " onePhase=" + onePhase);
+ try
+ {
+ xaResource.commit(resourceXid, onePhase);
+ committedResources++;
+ resourceState = RS_COMMITTED;
+ }
+ catch (XAException e)
+ {
+ switch (e.errorCode)
+ {
+ case XAException.XA_HEURCOM:
+ // Ignore this exception, as the heuristic outcome
+ // is the one we wanted anyway.
+ logXAException(e);
+ if (trace)
+ log.trace("Ignoring XAException.XA_HEURCOM" +
+ " in XAResource.commit: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid) +
+ " onePhase=" + onePhase);
+ forget();
+ committedResources++;
+ resourceState = RS_COMMITTED;
+ break;
+ case XAException.XAER_RMERR:
+ case XAException.XA_HEURRB:
+ rolledbackResources++;
+ // fall through
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ // stay in RS_VOTE_OK state - the commit will be retried
+ throw e;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // no point retrying
+ resourceState = RS_ERROR;
+ throw e;
+ }
+ }
+ finally
+ {
+ if (xaResourceAccess != null && resourceState != RS_VOTE_OK)
+ xaResourceAccess.release();
+ lock();
+ }
+ }
+
+ /**
+ * Rollback the resource
+ */
+ public void rollback()
+ throws XAException
+ {
+ if (resourceState == RS_VOTE_READONLY ||
+ resourceState == RS_ROLLEDBACK || resourceState == RS_FORGOT)
+ return;
+
+ if (resourceSameRM != null)
+ return; // This RM already rolled back.
+
+ unlock();
+ if (trace)
+ log.trace("Rollback: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid));
+ try
+ {
+ xaResource.rollback(resourceXid);
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ }
+ catch (XAException e)
+ {
+ switch (e.errorCode)
+ {
+ case XAException.XAER_RMERR:
+ // Ignore this exception, as the transaction has been
+ // rolled back, which is what we wanted anyway.
+ if (trace)
+ log.trace("Ignoring XAException.XAER_RMERR" +
+ " in XAResource.rollback: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid));
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ break;
+ case XAException.XA_HEURRB:
+ // Ignore this exception, as the heuristic outcome
+ // is the one we wanted anyway.
+ if (trace)
+ log.trace("Ignoring XAException.XA_HEURRB" +
+ " in XAResource.rollback: " + xaResource +
+ " xid=" + xidFactory.toString(resourceXid));
+ forget();
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ break;
+ case XAException.XA_HEURCOM:
+ committedResources++;
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ // stay in RS_VOTE_OK state - the rollback will be retried
+ throw e;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // no point retrying
+ resourceState = RS_ERROR;
+ throw e;
+ }
+ }
+ finally
+ {
+ if (xaResourceAccess != null && resourceState != RS_VOTE_OK)
+ xaResourceAccess.release();
+ lock();
+ }
+ }
+ }
+
+ /**
+ * Represents a remote resource enlisted in the transaction.
+ */
+ private class EnlistedRemoteResource
+ implements EnlistedResource
+ {
+ /**
+ * The remote resource.
+ */
+ private Resource remoteResource;
+
+ /**
+ * The state of the resource.
+ */
+ private int resourceState;
+
+ /**
+ * The heuristic status of this resource
+ */
+ private int heurCode;
+
+ /**
+ * Creates a new <code>EnlistedRemoteResource</code>.
+ */
+ public EnlistedRemoteResource(Resource remoteResource)
+ {
+ this.remoteResource = remoteResource;
+ resourceState = RS_NEW;
+ heurCode = HEUR_NONE;
+ }
+
+ /**
+ * Creates a new <code>EnlistedRemoteResource</code>.
+ * This constructor is intended to be used during recovery only.
+ */
+ public EnlistedRemoteResource(Resource remoteResource, boolean prepared)
+ {
+ this.remoteResource = remoteResource;
+ if (prepared)
+ resourceState = RS_VOTE_OK;
+ else
+ resourceState = RS_NEW;
+ heurCode = HEUR_NONE;
+ }
+
+ /**
+ * Creates a resource in a heuristically completed state.
+ * This constructor is intended to be used during recovery only.
+ */
+ public EnlistedRemoteResource(Resource remoteResource,
+ boolean committed,
+ int heurCode)
+ {
+ this.remoteResource = remoteResource;
+ resourceState = (committed) ? RS_COMMITTED : RS_ROLLEDBACK;
+ this.heurCode = heurCode;
+ }
+
+ /**
+ * Gets the remote resource represented by this
+ * <code>EnlistedRemoteResource</code> instance.
+ */
+ public Resource getRemoteResource()
+ {
+ return remoteResource;
+ }
+
+ /**
+ * Gets the state of the remote resource represented by this
+ * <code>EnlistedRemoteResource</code> instance.
+ */
+ public int getState()
+ {
+ return resourceState;
+ }
+
+ /**
+ * Prepare the resource
+ */
+ public int prepare()
+ throws RemoteException,
+ TransactionAlreadyPreparedException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ Vote vote = null;
+ unlock();
+ try
+ {
+ vote = remoteResource.prepare();
+ }
+ finally
+ {
+ lock();
+ }
+
+ if (vote == Vote.COMMIT)
+ resourceState = RS_VOTE_OK;
+ else if (vote == Vote.READONLY)
+ resourceState = RS_VOTE_READONLY;
+ else /* (vote == Vote.ROLLBACK) */
+ resourceState = RS_ROLLEDBACK;
+
+ return resourceState;
+ }
+
+ /**
+ * Commit the resource
+ *
+ * @throws RemoteException
+ * @throws HeuristicHazardException
+ * @throws HeuristicMixedException
+ * @throws HeuristicRollbackException
+ * @throws TransactionNotPreparedException
+ *
+ */
+ public void commit(boolean onePhase)
+ throws RemoteException,
+ TransactionNotPreparedException,
+ HeuristicRollbackException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (trace)
+ log.trace("Committing resource " + remoteResource +
+ " state=" + resourceState);
+
+ if (!onePhase && resourceState != RS_VOTE_OK)
+ return; // Voted read-only at prepare phase.
+
+ unlock();
+ try
+ {
+ if (onePhase)
+ remoteResource.commitOnePhase();
+ else
+ remoteResource.commit();
+
+ committedResources++;
+ resourceState = RS_COMMITTED;
+ }
+ catch (TransactionRolledbackException e)
+ {
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ throw e;
+ }
+ catch (RemoteException e)
+ {
+ // stay in RS_VOTE_OK state - the commit will be retried
+ throw e;
+ }
+ catch (TransactionNotPreparedException e)
+ {
+ resourceState = RS_ERROR;
+ throw e;
+ }
+ catch (HeuristicRollbackException e)
+ {
+ rolledbackResources++;
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ catch (HeuristicMixedException e)
+ {
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ catch (HeuristicHazardException e)
+ {
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ /**
+ * Rollback the resource
+ */
+ public void rollback()
+ throws RemoteException,
+ HeuristicCommitException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (resourceState == RS_VOTE_READONLY ||
+ resourceState == RS_ROLLEDBACK || resourceState == RS_FORGOT)
+ return;
+
+ unlock();
+ try
+ {
+ remoteResource.rollback();
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ }
+ catch (TransactionRolledbackException e)
+ {
+ rolledbackResources++;
+ resourceState = RS_ROLLEDBACK;
+ throw e;
+ }
+ catch (RemoteException e)
+ {
+ // stay in RS_VOTE_OK state - the rollback will be retried
+ throw e;
+ }
+ catch (HeuristicCommitException e)
+ {
+ committedResources++;
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ catch (HeuristicMixedException e)
+ {
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ catch (HeuristicHazardException e)
+ {
+ resourceState = RS_HEUR_OUTCOME;
+ throw e;
+ }
+ finally
+ {
+ lock();
+ }
+ }
+
+ /**
+ * Remember that this resource has a heuristic outcome.
+ */
+ public void remember(int heuristicCode)
+ {
+ heurCode = heuristicCode;
+ if (remoteResourcesWithHeuristicDecisions == null)
+ remoteResourcesWithHeuristicDecisions = new ArrayList();
+ remoteResourcesWithHeuristicDecisions.add(this);
+ }
+
+ /**
+ * Get the heuristic status of this resource.
+ */
+ public int getHeuristicCode()
+ {
+ return heurCode;
+ }
+
+ /**
+ * Forget the resource
+ */
+ public void forget()
+ {
+ unlock();
+ if (trace)
+ log.trace("Forget: " + remoteResource);
+ try
+ {
+ remoteResource.forget();
+ resourceState = RS_FORGOT;
+ }
+ catch (NoSuchObjectException e)
+ {
+ if (trace)
+ log.trace("NoSuchObjectException in forget: remoteResource=" +
+ remoteResource + "\nAssuming that the resource has " +
+ "been previously forgotten.", e);
+ }
+ catch (RemoteException e)
+ {
+ if (trace)
+ log.trace("RemoteException in forget: remoteResource=" +
+ remoteResource + "\nThe resource still needs to be " +
+ "forgotten.", e);
+ }
+ finally
+ {
+ lock();
+ }
+ resourceState = RS_FORGOT;
+ }
+
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,692 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAException;
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLogger;
+
+/**
+ * This is a JMX service which manages the TransactionManager.
+ * The service creates it and binds a Reference to it into JNDI.
+ *
+ * @see TxManager
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ *
+ * @jmx.mbean extends="org.jboss.system.ServiceMBean"
+ */
+public class TransactionManagerInitializer implements XAExceptionFormatter, ObjectFactory
+{
+ private static Logger log = Logger.getLogger(TransactionManagerInitializer.class);
+
+ // Constants -----------------------------------------------------
+ public static String JNDI_NAME = "java:/TransactionManager";
+ public static String JNDI_IMPORTER = "java:/TransactionPropagationContextImporter";
+ public static String JNDI_EXPORTER = "java:/TransactionPropagationContextExporter";
+
+ // Attributes ----------------------------------------------------
+
+ private boolean globalIdsEnabled = false; // flag duplicated in TM
+
+ /** Whether to interrupt threads at transaction timeout */
+ private boolean interruptThreads = false;
+
+ private int timeout = 300; // default tx timeout, dupl. in TM when it exists.
+
+ private int completionRetryLimit = 0;
+
+ private int completionRetryTimeout = 1;
+
+ private int xaRetryTimeout;
+
+ private int preparedTimeout;
+
+ private boolean rootBranchRemembersHeuristicDecisions = true;
+
+ private boolean reportHeuristicHazardAsHeuristicMixed = false;
+
+ private final Map xaExceptionFormatters = new HashMap();
+
+ private RecoveryLogger recoveryLogger;
+
+ private XidFactoryBase xidFactory;
+
+ private TransactionIntegrityFactory integrityFactory;
+
+ private InitialContext initialContext;
+
+ protected Hashtable initialContextProperties;
+
+ // Static --------------------------------------------------------
+
+ static TxManager tm;
+
+ // ServiceMBeanSupport overrides ---------------------------------
+
+ public void setInitialContextProperties(Hashtable initialContextProperties)
+ {
+ this.initialContextProperties = initialContextProperties;
+ }
+
+
+ public void start() throws Exception
+ {
+ if (initialContextProperties == null) initialContext = new InitialContext();
+ else initialContext = new InitialContext(initialContextProperties);
+
+ TransactionImpl.xidFactory = xidFactory;
+ TransactionImpl.xaExceptionFormatter = this;
+
+ // Get a reference to the TxManager singleton.
+ tm = TxManager.getInstance();
+ // Set its default timeout.
+ tm.setDefaultTransactionTimeout(timeout);
+ // Set the TxManager limit and timout for retrying a completion operation
+ // before reporting a heuristic hazard outcome.
+ tm.setCompletionRetryLimit(completionRetryLimit);
+ tm.setCompletionRetryTimeout(completionRetryTimeout);
+ // Set the TxManager timouts for retrying a XA operation and
+ // for a foreign transaction to remain in the prepared state.
+ tm.setXARetryTimeout(xaRetryTimeout);
+ tm.setPreparedTimeout(preparedTimeout);
+ // Set the TxManager flags for remembering heuristic decisions
+ // at the root branch and for heuristic hazard reporting.
+ tm.setRootBranchRemembersHeuristicDecisions(
+ rootBranchRemembersHeuristicDecisions);
+ tm.setReportHeuristicHazardAsHeuristicMixed(
+ reportHeuristicHazardAsHeuristicMixed);
+ // Initialize its globalIdsEnabled flag.
+ tm.setGlobalIdsEnabled(globalIdsEnabled);
+ tm.setInterruptThreads(interruptThreads);
+ if (integrityFactory != null)
+ tm.setTransactionIntegrity(integrityFactory.createTransactionIntegrity());
+ else
+ tm.setTransactionIntegrity(null);
+
+ // Bind reference to TM in JNDI
+ // Our TM also implement the tx importer and exporter
+ // interfaces, so we bind it under those names too.
+ // Other transaction managers may have seperate
+ // implementations of these objects, so they are
+ // accessed under separate names.
+ bindRef(JNDI_NAME, "org.jboss.tm.TxManager");
+ bindRef(JNDI_IMPORTER, "org.jboss.tm.TransactionPropagationContextImporter");
+ bindRef(JNDI_EXPORTER, "org.jboss.tm.TransactionPropagationContextFactory");
+ }
+
+ public void stop()
+ {
+ try
+ {
+ // Remove TM, importer and exporter from JNDI
+ Context ctx = initialContext;
+ ctx.unbind(JNDI_NAME);
+ ctx.unbind(JNDI_IMPORTER);
+ ctx.unbind(JNDI_EXPORTER);
+ }
+ catch (Exception e)
+ {
+ log.error("Failed to clear JNDI bindings", e);
+ }
+ }
+
+ /**
+ * Set the Recover logger
+ *
+ * @param recoveryLogger
+ */
+ public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+ {
+ this.recoveryLogger = recoveryLogger;
+ // inject the logger into the TxManager
+ TxManager.getInstance().setRecoveryLogger(this.recoveryLogger);
+ }
+
+
+ /**
+ * Set the Transaciton integrity factory
+ *
+ * @param factory the transaction integrity factory
+ */
+ public void setTransactionIntegrityFactory(TransactionIntegrityFactory factory)
+ {
+ this.integrityFactory = factory;
+ if (tm != null)
+ {
+ if (factory != null)
+ tm.setTransactionIntegrity(factory.createTransactionIntegrity());
+ else
+ tm.setTransactionIntegrity(null);
+ }
+ }
+
+ /**
+ * Gets the "global ids enabled" flag.
+ *
+ * @return an <code>boolean</code> value
+ */
+ public boolean getGlobalIdsEnabled()
+ {
+ return globalIdsEnabled;
+ }
+
+ /**
+ * Sets the "global ids enabled" flag.
+ *
+ * @param newValue an <code>boolean</code> value
+ */
+ public void setGlobalIdsEnabled(boolean newValue)
+ {
+ globalIdsEnabled = newValue;
+ if (tm != null) // Update TM setting
+ tm.setGlobalIdsEnabled(newValue);
+ }
+
+ /**
+ * Is thread interruption enabled at transaction timeout
+ *
+ * @return true for interrupt threads, false otherwise
+ */
+ public boolean isInterruptThreads()
+ {
+ return interruptThreads;
+ }
+
+ /**
+ * Enable/disable thread interruption at transaction timeout.
+ *
+ * @param interruptThreads pass true to interrupt threads, false otherwise
+ */
+ public void setInterruptThreads(boolean interruptThreads)
+ {
+ this.interruptThreads = interruptThreads;
+ if (tm != null)
+ tm.setInterruptThreads(interruptThreads);
+ }
+
+ /**
+ * Describe <code>getTransactionTimeout</code> method here.
+ *
+ * @return an <code>int</code> value
+ */
+ public int getTransactionTimeout()
+ {
+ if (tm != null) // Get timeout value from TM (in case it was changed).
+ timeout = tm.getDefaultTransactionTimeout();
+ return timeout;
+ }
+
+ /**
+ * Describe <code>setTransactionTimeout</code> method here.
+ *
+ * @param timeout an <code>int</code> value
+ * @jmx:managed-attribute
+ */
+ public void setTransactionTimeout(int timeout)
+ {
+ this.timeout = timeout;
+ if (tm != null) // Update TM default timeout
+ tm.setDefaultTransactionTimeout(timeout);
+ }
+
+ /**
+ * Sets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @param maxCompletionRetries the completion retry limit.
+ */
+ public void setCompletionRetryLimit(int maxCompletionRetries)
+ {
+ this.completionRetryLimit = maxCompletionRetries;
+ if (tm != null) // Update TM setting
+ tm.setCompletionRetryLimit(maxCompletionRetries);
+ }
+
+ /**
+ * Gets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @return the completion retry limit.
+ */
+ public int getCompletionRetryLimit()
+ {
+ return completionRetryLimit;
+ }
+
+ /**
+ * Sets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @param seconds the timeout (in seconds) for retrying a completion
+ * operation after a transient exception and before the
+ * transaction manager reports a heuristic hazard.
+ */
+ public void setCompletionRetryTimeout(int seconds)
+ {
+ this.completionRetryTimeout = seconds;
+ if (tm != null) // Update TM setting
+ tm.setCompletionRetryTimeout(seconds);
+ }
+
+ /**
+ * Gets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @return the timeout (in seconds) for retrying a completion operation
+ * after a transient exception and before the transaction manager
+ * reports a heuristic hazard.
+ */
+ public int getCompletionRetryTimeout()
+ {
+ return completionRetryTimeout;
+ }
+
+ /**
+ * Sets the XA retry timeout.
+ *
+ * @param xaRetryTimeout the timeout (in seconds) for retrying operations on
+ * XA resources.
+ */
+ public void setXARetryTimeout(int xaRetryTimeout)
+ {
+ this.xaRetryTimeout = xaRetryTimeout;
+ if (tm != null) // Update TM setting
+ tm.setXARetryTimeout(xaRetryTimeout);
+ }
+
+ /**
+ * Gets the XA retry timeout.
+ *
+ * @return the timeout (in seconds) for retrying operations on XA resources.
+ */
+ public int getXARetryTimeout()
+ {
+ return xaRetryTimeout;
+ }
+
+ /**
+ * Sets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @param preparedTimeout the timeout (in seconds) for a transaction branch
+ * in the prepared state.
+ */
+ public void setPreparedTimeout(int preparedTimeout)
+ {
+ this.preparedTimeout = preparedTimeout;
+ if (tm != null) // Update TM setting
+ tm.setPreparedTimeout(preparedTimeout);
+ }
+
+ /**
+ * Gets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @return the timeout (in seconds) for a transaction branch in the prepared
+ * state.
+ */
+ public int getPreparedTimeout()
+ {
+ return preparedTimeout;
+ }
+
+ /**
+ * Sets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @param newValue true if the root branch should remember heuristic
+ * decisions, false otherwise.
+ */
+ public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+ {
+ this.rootBranchRemembersHeuristicDecisions = newValue;
+ if (tm != null) // Update TM setting
+ tm.setRootBranchRemembersHeuristicDecisions(newValue);
+ }
+
+ /**
+ * Gets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @return true if the root branch remember heuristic decisions,
+ * false otherwise.
+ */
+ public boolean isRootBranchRemembersHeuristicDecisions()
+ {
+ return rootBranchRemembersHeuristicDecisions;
+ }
+
+ /**
+ * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @param newValue true if a JTA commit should throw
+ * <code>HeuristicMixedException</code> to report a heuristic hazard
+ * to its caller, or false if a JTA commit should not report a
+ * heuristic hazard to its caller.
+ */
+ public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+ {
+ this.reportHeuristicHazardAsHeuristicMixed = newValue;
+ if (tm != null) // Update TM setting
+ tm.setReportHeuristicHazardAsHeuristicMixed(newValue);
+
+ }
+
+ /**
+ * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+ * to report a heuristic hazard to its caller, or false if a JTA
+ * commit does not report a heuristic hazard to its caller.
+ */
+ boolean isReportHeuristicHazardAsHeuristicMixed()
+ {
+ return reportHeuristicHazardAsHeuristicMixed;
+ }
+
+ /**
+ * mbean get-set pair for field xidFactory
+ * Get the value of xidFactory
+ * @return value of xidFactory
+ *
+ */
+ public XidFactoryBase getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * Set the value of xidFactory
+ * @param xidFactory Value to assign to xidFactory
+ *
+ */
+ public void setXidFactory(XidFactoryBase xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+
+ /**
+ * mbean get-set pair for field transactionManager
+ * Get the value of transactionManager
+ * @return value of transactionManager
+ *
+ */
+ public TransactionManager getTransactionManager()
+ {
+ return tm;
+ }
+
+ /**
+ * Get the xa terminator
+ *
+ * @return the xa terminator
+ */
+ public JBossXATerminator getXATerminator()
+ {
+ return tm;
+ }
+
+ /**
+ * Counts the number of transactions
+ *
+ * @return the number of active transactions
+ *
+ */
+ public long getTransactionCount()
+ {
+ return tm.getTransactionCount();
+ }
+ /** The number of commits.
+ *
+ * @return the number of transactions that have been committed
+ *
+ */
+ public long getCommitCount()
+ {
+ return tm.getCommitCount();
+ }
+ /** The number of rollbacks.
+ *
+ * @return the number of transactions that have been rolled back
+ *
+ */
+ public long getRollbackCount()
+ {
+ return tm.getRollbackCount();
+ }
+
+ /**
+ * Lists the in-doubt transactions.
+ *
+ * @return a string with a text listing of in-doubt transactions.
+ */
+ public String listInDoubtTransactions()
+ {
+ return tm.listInDoubtTransactions();
+ }
+
+ /**
+ * Heuristically commits an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically committed.
+ */
+ public void heuristicallyCommit(long localTransactionId)
+ {
+ tm.heuristicallyCommit(localTransactionId);
+ }
+
+ /**
+ * Heuristically commits all in-doubt transactions.
+ */
+ public void heuristicallyCommitAll()
+ {
+ tm.heuristicallyCommitAll();
+ }
+
+ /**
+ * Heuristically rolls back an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically rolled back.
+ */
+ public void heuristicallyRollback(long localTransactionId)
+ {
+ tm.heuristicallyRollback(localTransactionId);
+ }
+
+ /**
+ * Heuristically rolls back all in-doubt transactions.
+ */
+ public void heuristicallyRollbackAll()
+ {
+ tm.heuristicallyRollbackAll();
+ }
+
+ /**
+ * Lists the heuristically completed transactions coordinated by this
+ * transaction manager. A transaction that was heuristically completed
+ * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+ * <code>heuristicallyCommitAll()</code>,
+ * <code>heuristicallyRollback(long localTransactionId)</code>, or
+ * <code>heuristicallyRollbackAll()</code> does not appear in the listing,
+ * as that transaction had a foreign coordinator.
+ *
+ * @return a string with a text listing of heuristically completed
+ * transactions.
+ */
+ public String listHeuristicallyCompletedTransactions()
+ {
+ return tm.listHeuristicallyCompletedTransactions();
+ }
+
+ /**
+ * Forgets a heuristically completed transaction coordinated by this
+ * transaction manager.
+ *
+ * @param localTransactionId the local id of a heuristically completed
+ * transaction coordinated by this transaction
+ * manager.
+ */
+ public void forget(long localTransactionId)
+ {
+ tm.forget(localTransactionId);
+ }
+
+ /**
+ * Forgets all heuristically completed transactions coordinated by this
+ * transaction manager.
+ */
+ public void forgetAll()
+ {
+ tm.forgetAll();
+ }
+
+ /**
+ * The <code>registerXAExceptionFormatter</code> method
+ *
+ * @param clazz a <code>Class</code> value
+ * @param formatter a <code>XAExceptionFormatter</code> value
+ *
+ */
+ public void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter)
+ {
+ xaExceptionFormatters.put(clazz, formatter);
+ }
+
+ /**
+ * The <code>unregisterXAExceptionFormatter</code> method
+ *
+ * @param clazz a <code>Class</code> value
+ *
+ */
+ public void unregisterXAExceptionFormatter(Class clazz)
+ {
+ xaExceptionFormatters.remove(clazz);
+ }
+
+ public void formatXAException(XAException xae, Logger log)
+ {
+ Class clazz = xae.getClass();
+ while (clazz != XAException.class)
+ {
+ XAExceptionFormatter formatter = (XAExceptionFormatter) xaExceptionFormatters.get(clazz);
+ if (formatter != null)
+ {
+ formatter.formatXAException(xae, log);
+ return;
+ } // end of if ()
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ // ObjectFactory implementation ----------------------------------
+
+ public Object getObjectInstance(Object obj, Name name,
+ Context nameCtx, Hashtable environment)
+ throws Exception
+ {
+ // Return the transaction manager
+ return tm;
+ }
+
+
+ // Private -------------------------------------------------------
+
+ private void bindRef(String jndiName, String className)
+ throws Exception
+ {
+ Reference ref = new Reference(className, getClass().getName(), null);
+ initialContext.bind(jndiName, ref);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,284 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.management.ObjectName;
+import javax.transaction.TransactionManager;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.RecoveryLoggerInstance;
+
+/**
+ * This is a JMX service which manages the TransactionManager.
+ * The service creates it and binds a Reference to it into JNDI.
+ *
+ * @see TxManager
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ *
+ * @jmx.mbean extends="org.jboss.system.ServiceMBean"
+ */
+public class TransactionManagerService
+ extends ServiceMBeanSupport
+ implements TransactionManagerServiceMBean
+{
+ private ObjectName xidFactory;
+
+ private TransactionManagerInitializer initializer = new TransactionManagerInitializer();
+
+ // Constants -----------------------------------------------------
+ public static String JNDI_NAME = "java:/TransactionManager";
+ public static String JNDI_IMPORTER = "java:/TransactionPropagationContextImporter";
+ public static String JNDI_EXPORTER = "java:/TransactionPropagationContextExporter";
+
+ protected void startService()
+ throws Exception
+ {
+ XidFactoryMBean xidFactoryObj = (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+ initializer.setXidFactory(xidFactoryObj);
+ initializer.start();
+ }
+
+ protected void stopService()
+ {
+ initializer.stop();
+ }
+
+ /**
+ * Set the Recover logger
+ *
+ * @param recoveryLogger
+ * @jmx:managed-attribute
+ */
+ public void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger)
+ {
+ initializer.setRecoveryLogger(recoveryLogger.getInstance());
+ }
+
+ /**
+ * Set the Transaction integrity factory
+ *
+ * @param factory the factory
+ */
+ public void setTransactionIntegrityFactory(TransactionIntegrityFactory factory)
+ {
+ initializer.setTransactionIntegrityFactory(factory);
+ }
+
+ /**
+ * mbean get-set pair for field xidFactory
+ * Get the value of xidFactory
+ * @return value of xidFactory
+ *
+ * @jmx:managed-attribute
+ */
+ public ObjectName getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * Set the value of xidFactory
+ * @param xidFactory Value to assign to xidFactory
+ *
+ * @jmx:managed-attribute
+ */
+ public void setXidFactory(ObjectName xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+ {
+ initializer.setRecoveryLogger(recoveryLogger);
+ }
+
+ public boolean getGlobalIdsEnabled()
+ {
+ return initializer.getGlobalIdsEnabled();
+ }
+
+ public void setGlobalIdsEnabled(boolean newValue)
+ {
+ initializer.setGlobalIdsEnabled(newValue);
+ }
+
+ public boolean isInterruptThreads()
+ {
+ return initializer.isInterruptThreads();
+ }
+
+ public void setInterruptThreads(boolean interruptThreads)
+ {
+ initializer.setInterruptThreads(interruptThreads);
+ }
+
+ public int getTransactionTimeout()
+ {
+ return initializer.getTransactionTimeout();
+ }
+
+ public void setTransactionTimeout(int timeout)
+ {
+ initializer.setTransactionTimeout(timeout);
+ }
+
+ public int getCompletionRetryLimit()
+ {
+ return initializer.getCompletionRetryLimit();
+ }
+
+ public void setCompletionRetryLimit(int limit)
+ {
+ initializer.setCompletionRetryLimit(limit);
+ }
+
+ public int getCompletionRetryTimeout()
+ {
+ return initializer.getCompletionRetryTimeout();
+ }
+
+ public void setCompletionRetryTimeout(int timeout)
+ {
+ initializer.setCompletionRetryTimeout(timeout);
+ }
+
+ public int getXARetryTimeout()
+ {
+ return initializer.getXARetryTimeout();
+ }
+
+ public void setXARetryTimeout(int timeout)
+ {
+ initializer.setXARetryTimeout(timeout);
+ }
+
+ public int getPreparedTimeout()
+ {
+ return initializer.getPreparedTimeout();
+ }
+
+ public void setPreparedTimeout(int timeout)
+ {
+ initializer.setPreparedTimeout(timeout);
+ }
+
+ public boolean isRootBranchRemembersHeuristicDecisions()
+ {
+ return initializer.isRootBranchRemembersHeuristicDecisions();
+ }
+
+ public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+ {
+ initializer.setRootBranchRemembersHeuristicDecisions(newValue);
+ }
+
+ public boolean isReportHeuristicHazardAsHeuristicMixed()
+ {
+ return initializer.isReportHeuristicHazardAsHeuristicMixed();
+ }
+
+ public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+ {
+ initializer.setReportHeuristicHazardAsHeuristicMixed(newValue);
+ }
+
+ public TransactionManager getTransactionManager()
+ {
+ return initializer.getTransactionManager();
+ }
+
+ public JBossXATerminator getXATerminator()
+ {
+ return initializer.getXATerminator();
+ }
+
+ public long getTransactionCount()
+ {
+ return initializer.getTransactionCount();
+ }
+
+ public long getCommitCount()
+ {
+ return initializer.getCommitCount();
+ }
+
+ public long getRollbackCount()
+ {
+ return initializer.getRollbackCount();
+ }
+
+ public String listInDoubtTransactions()
+ {
+ return initializer.listInDoubtTransactions();
+ }
+
+ public void heuristicallyCommit(long localTransactionId)
+ {
+ initializer.heuristicallyCommit(localTransactionId);
+ }
+
+ public void heuristicallyCommitAll()
+ {
+ initializer.heuristicallyCommitAll();
+ }
+
+ public void heuristicallyRollback(long localTransactionId)
+ {
+ initializer.heuristicallyRollback(localTransactionId);
+ }
+
+ public void heuristicallyRollbackAll()
+ {
+ initializer.heuristicallyRollbackAll();
+ }
+
+ public String listHeuristicallyCompletedTransactions()
+ {
+ return initializer.listHeuristicallyCompletedTransactions();
+ }
+
+ public void forget(long localTransactionId)
+ {
+ initializer.forget(localTransactionId);
+ }
+
+ public void forgetAll()
+ {
+ initializer.forgetAll();
+ }
+
+ public void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter)
+ {
+ initializer.registerXAExceptionFormatter(clazz, formatter);
+ }
+
+ public void unregisterXAExceptionFormatter(Class clazz)
+ {
+ initializer.unregisterXAExceptionFormatter(clazz);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,375 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.management.ObjectName;
+
+import org.jboss.mx.util.ObjectNameFactory;
+import org.jboss.system.ServiceMBean;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLoggerInstance;
+
+/**
+ * TransactionManagerService MBean interface.
+ *
+ * @see TxManager
+ * @version $Revision: 44337 $
+ */
+public interface TransactionManagerServiceMBean extends ServiceMBean, TransactionManagerFactory
+{
+ ObjectName OBJECT_NAME = ObjectNameFactory.create("jboss:service=TransactionManager");
+
+ /**
+ * Set the Recover logger
+ * @param recoveryLogger
+ */
+ void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger);
+
+ /**
+ * Set the Integrity checker factory
+ *
+ * @param factory the integrity checker factory
+ */
+ void setTransactionIntegrityFactory(TransactionIntegrityFactory factory);
+
+ /**
+ * Describe <code>getGlobalIdsEnabled</code> method here.
+ * @return an <code>boolean</code> value
+ */
+ boolean getGlobalIdsEnabled();
+
+ /**
+ * Describe <code>setGlobalIdsEnabled</code> method here.
+ * @param newValue an <code>boolean</code> value
+ */
+ void setGlobalIdsEnabled(boolean newValue);
+
+ /**
+ * Is thread interruption enabled at transaction timeout
+ * @return true for interrupt threads, false otherwise
+ */
+ boolean isInterruptThreads();
+
+ /**
+ * Enable/disable thread interruption at transaction timeout.
+ * @param interruptThreads pass true to interrupt threads, false otherwise
+ */
+ void setInterruptThreads(boolean interruptThreads);
+
+ /**
+ * Describe <code>getTransactionTimeout</code> method here.
+ * @return an <code>int</code> value
+ */
+ int getTransactionTimeout();
+
+ /**
+ * Describe <code>setTransactionTimeout</code> method here.
+ * @param timeout an <code>int</code> value
+ */
+ void setTransactionTimeout(int timeout);
+
+ /**
+ * Gets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @return the completion retry limit.
+ */
+ int getCompletionRetryLimit();
+
+ /**
+ * Sets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @param maxCompletionRetries the completion retry limit.
+ */
+ void setCompletionRetryLimit(int maxCompletionRetries);
+
+ /**
+ * Gets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @return the timeout (in seconds) for retrying a completion operation
+ * after a transient exception and before the transaction manager
+ * reports a heuristic hazard.
+ */
+ int getCompletionRetryTimeout();
+
+ /**
+ * Sets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @param seconds the timeout (in seconds) for retrying a completion
+ * operation after a transient exception and before the
+ * transaction manager reports a heuristic hazard.
+ */
+ void setCompletionRetryTimeout(int seconds);
+
+ /**
+ * Gets the XA retry timeout. After reaching the completion retry limit and
+ * reporting a heuristic hazard to its caller, the transaction manager will
+ * still attempt to commit or rollback an XA resource that raised a
+ * transient exception. This is the time interval (in seconds) between XA
+ * completion retries that is applicable if a heuristic hazard has been
+ * reported for a transaction.
+ *
+ * @return the timeout (in seconds) for retrying commit or rollback
+ * operations on XA resources.
+ */
+ int getXARetryTimeout();
+
+ /**
+ * Sets the XA retry timeout. After reaching the completion retry limit and
+ * reporting a heuristic hazard to its caller, the transaction manager will
+ * still attempt to commit or rollback an XA resource that raised a
+ * transient exception. This is the time interval (in seconds) between XA
+ * completion retries that is applicable if a heuristic hazard has been
+ * reported for a transaction.
+ *
+ * @param seconds the timeout (in seconds) for retrying commit or rollback
+ * operations on XA resources.
+ */
+ void setXARetryTimeout(int seconds);
+
+ /**
+ * Gets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @return the timeout (in seconds) for a transaction branch in the prepared
+ * state.
+ */
+ int getPreparedTimeout();
+
+ /**
+ * Sets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @param seconds the timeout (in seconds) for a transaction branch in the
+ * prepared state.
+ */
+ void setPreparedTimeout(int seconds);
+
+ /**
+ * Gets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @return true if the root branch remember heuristic decisions,
+ * false otherwise.
+ */
+ boolean isRootBranchRemembersHeuristicDecisions();
+
+ /**
+ * Sets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @param newValue true if the root branch should remember heuristic
+ * decisions, false otherwise.
+ */
+ void setRootBranchRemembersHeuristicDecisions(boolean newValue);
+
+ /**
+ * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+ * to report a heuristic hazard to its caller, or false if a JTA
+ * commit does not report a heuristic hazard to its caller.
+ */
+ boolean isReportHeuristicHazardAsHeuristicMixed();
+
+ /**
+ * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @param newValue true if a JTA commit should throw
+ * <code>HeuristicMixedException</code> to report a heuristic hazard
+ * to its caller, or false if a JTA commit should not report a
+ * heuristic hazard to its caller.
+ */
+ void setReportHeuristicHazardAsHeuristicMixed(boolean newValue);
+
+ /**
+ * mbean get-set pair for field xidFactory Get the value of xidFactory
+ * @return value of xidFactory
+ */
+ ObjectName getXidFactory();
+
+ /**
+ * Set the value of xidFactory
+ * @param xidFactory Value to assign to xidFactory
+ */
+ void setXidFactory(ObjectName xidFactory);
+
+ /**
+ * Get the xa terminator
+ * @return the xa terminator
+ */
+ JBossXATerminator getXATerminator();
+
+ /**
+ * Counts the number of transactions
+ * @return the number of active transactions
+ */
+ long getTransactionCount();
+
+ /**
+ * The number of commits.
+ * @return the number of transactions that have been committed
+ */
+ long getCommitCount();
+
+ /**
+ * The number of rollbacks.
+ * @return the number of transactions that have been rolled back
+ */
+ long getRollbackCount();
+
+ /**
+ * Lists the in-doubt transactions.
+ *
+ * @return a string with a text listing of in-doubt transactions.
+ */
+ String listInDoubtTransactions();
+
+ /**
+ * Heuristically commits an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically committed.
+ */
+ void heuristicallyCommit(long localTransactionId);
+
+ /**
+ * Heuristically commits all in-doubt transactions.
+ */
+ void heuristicallyCommitAll();
+
+ /**
+ * Heuristically rolls back an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically rolled back.
+ */
+ void heuristicallyRollback(long localTransactionId);
+
+ /**
+ * Heuristically rolls back all in-doubt transactions.
+ */
+ void heuristicallyRollbackAll();
+
+ /**
+ * Lists the heuristically completed transactions coordinated by this
+ * transaction manager. A transaction that was heuristically completed
+ * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+ * <code>heuristicallyCommitAll()</code>,
+ * <code>heuristicallyRollback(long localTransactionId)</code>, or
+ * <code>heuristicallyRollbackAll()</code> does not appear in the listing,
+ * as that transaction had a foreign coordinator.
+ *
+ * @return a string with a text listing of heuristically completed
+ * transactions.
+ */
+ String listHeuristicallyCompletedTransactions();
+
+ /**
+ * Forgets a heuristically completed transaction coordinated by this
+ * transaction manager.
+ *
+ * @param localTransactionId the local id of a heuristically completed
+ * transaction coordinated by this transaction
+ * manager.
+ */
+ void forget(long localTransactionId);
+
+ /**
+ * Forgets all heuristically completed transactions coordinated by this
+ * transaction manager.
+ */
+ void forgetAll();
+
+ /**
+ * The <code>registerXAExceptionFormatter</code> method
+ * @param clazz a <code>Class</code> value
+ * @param formatter a <code>XAExceptionFormatter</code> value
+ */
+ void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter);
+
+ /**
+ * The <code>unregisterXAExceptionFormatter</code> method
+ * @param clazz a <code>Class</code> value
+ */
+ void unregisterXAExceptionFormatter(Class clazz);
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/TxManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TxManager.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TxManager.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1833 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrity;
+import org.jboss.tm.recovery.LogRecord;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.TxCompletionHandler;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.util.UnexpectedThrowable;
+import org.jboss.util.UnreachableStatementException;
+
+/**
+ * Our TransactionManager implementation.
+ *
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:marc.fleury at telkel.com">Marc Fleury</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @author <a href="dimitris at jboss.org">Dimitris Andreadis</a>
+ * @version $Revision: 43424 $
+ * @deprecated Do not reference directly, use org.jboss.tm.TransactionManagerLocator
+ */
+public class TxManager
+ implements TransactionManager,
+ TransactionPropagationContextImporter,
+ TransactionPropagationContextFactory,
+ TransactionLocalDelegate,
+ TransactionTimeoutConfiguration,
+ JBossXATerminator,
+ StringRemoteRefConverter
+{
+ // Constants -----------------------------------------------------
+
+ // Attributes ----------------------------------------------------
+
+ /**
+ * True if the TxManager should keep a map from GlobalIds to transactions.
+ */
+ private boolean globalIdsEnabled = false;
+
+ /**
+ * True if distributed transaction management is enabled.
+ */
+ private boolean dtmEnabled = false;
+
+ /**
+ * Whether to interrupt threads at transaction timeout
+ */
+ private boolean interruptThreads = false;
+
+ /**
+ * Instance logger.
+ */
+ private Logger log = Logger.getLogger(this.getClass());
+
+ /**
+ * True if trace messages should be logged.
+ */
+ private boolean trace = log.isTraceEnabled();
+
+ /**
+ * Default timeout in milliseconds.
+ * Must be >= 1000!
+ */
+ private long timeOut = 5 * 60 * 1000;
+
+ // The following two fields are ints (not longs) because
+ // volatile 64Bit types are broken (i.e. access is not atomic) in most VMs, and we
+ // don't want to lock just for a statistic. Additionaly,
+ // it will take several years on a highly loaded system to
+ // exceed the int range. Note that we might loose an
+ // increment every now and then, since the ++ operation is
+ // not atomic on volatile data types.
+ /**
+ * A count of the transactions that have been committed
+ */
+ private volatile int commitCount;
+ /**
+ * A count of the transactions that have been rolled back
+ */
+ private volatile int rollbackCount;
+
+ /** The recovery logger */
+ private RecoveryLogger recoveryLogger;
+
+ /** Indicates that the recovery process was not yet performed */
+ private boolean recoveryPending = true;
+
+ /** The transaction integrity policy */
+ private TransactionIntegrity integrity;
+
+ /** Number of completion retries before reporting a heuristic hazard. */
+ private int completionRetryLimit = 0; // default: no retries
+
+ /** Blocking timeout between completion retries. */
+ private int completionRetryTimeout = 1 * 1000; // default: one sec
+
+ /** Timeout (in milliseconds) for retrying operations on XA resources */
+ private int xaRetryTimeout = 60 * 1000;
+
+ /** Timeout (in milliseconds) for a tx branch in the prepared state */
+ private int preparedTimeout = 60 * 1000;
+
+ /** True if the root branch should remember heuristic decisions */
+ private boolean rootBranchRemembersHeuristicDecisions = true;
+
+ /** True if heuristic hazards generate JTA heuristic mixed exceptions */
+ private boolean reportHeuristicHazardAsHeuristicMixed = false;
+
+ // Static --------------------------------------------------------
+
+ /**
+ * The singleton instance.
+ */
+ private static TxManager singleton = new TxManager();
+
+ /**
+ * Get a reference to the singleton instance.
+ */
+ public static TxManager getInstance()
+ {
+ return singleton;
+ }
+
+ // Constructors --------------------------------------------------
+
+ /**
+ * Private constructor for singleton. Use getInstance() to obtain
+ * a reference to the singleton.
+ */
+ private TxManager()
+ {
+ //make sure TxCapsule can be used
+ TransactionImpl.defaultXidFactory();
+ }
+
+ // Public --------------------------------------------------------
+
+ /**
+ * Clears all transactions. <p/>
+ * This method exists for testing purposes only. It should not be
+ * called during normal operation.
+ */
+ public void clear()
+ {
+ Collection txCollection = localIdTx.values();
+ synchronized (localIdTx)
+ {
+ Iterator i = txCollection.iterator();
+ while (i.hasNext())
+ {
+ TransactionImpl tx = (TransactionImpl) i.next();
+ tx.deactivate();
+ }
+ }
+ localIdTx.clear();
+ if (globalIdsEnabled)
+ globalIdTx.clear();
+ }
+
+ /**
+ * Setter for attribute <code>globalIdsEnabled</code>.
+ */
+ public void setGlobalIdsEnabled(boolean newValue)
+ {
+ trace = log.isTraceEnabled(); // hack!
+ XidImpl.setTrulyGlobalIdsEnabled(newValue);
+ globalIdsEnabled = newValue;
+ }
+
+ /**
+ * Getter for attribute <code>globalIdsEnabled</code>.
+ */
+ public boolean getGlobalIdsEnabled()
+ {
+ return globalIdsEnabled;
+ }
+
+ /**
+ * Enable/disable thread interruption at transaction timeout.
+ *
+ * @param interruptThreads pass true to interrupt threads, false otherwise
+ */
+ public void setInterruptThreads(boolean interruptThreads)
+ {
+ this.interruptThreads = interruptThreads;
+ }
+
+ /**
+ * Is thread interruption enabled at transaction timeout
+ *
+ * @return true for interrupt threads, false otherwise
+ */
+ public boolean isInterruptThreads()
+ {
+ return interruptThreads;
+ }
+
+ /**
+ * Set the recovery logger
+ *
+ * @param recoveryLogger the recovery logger
+ */
+ public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+ {
+ this.recoveryLogger = recoveryLogger;
+ }
+
+ /**
+ * Get the recovery logger
+ *
+ * @return the recovery logger
+ */
+ public RecoveryLogger getRecoveryLogger()
+ {
+ return recoveryLogger;
+ }
+
+ /**
+ * Clears the recovery pending flag. Called by the recovery manager, at
+ * the end of the recovery proccess.
+ */
+ public void clearRecoveryPending()
+ {
+ this.recoveryPending = false;
+ }
+
+ /**
+ * Returns the value of the recovery pending flag.
+ *
+ * @return true is recovery is pending, false otherwise.
+ */
+ public boolean isRecoveryPending()
+ {
+ return recoveryPending;
+ }
+
+ /**
+ * Set the transaction integrity policy
+ *
+ * @param integrity the transaction integrity policy
+ */
+ public void setTransactionIntegrity(TransactionIntegrity integrity)
+ {
+ this.integrity = integrity;
+ }
+
+ /**
+ * Get the transaction integrity policy
+ *
+ * @return the transaction integrity policy
+ */
+ public TransactionIntegrity getTransactionIntegrity()
+ {
+ return integrity;
+ }
+
+ /**
+ * Sets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @param maxCompletionRetries the completion retry limit.
+ */
+ public void setCompletionRetryLimit(int maxCompletionRetries)
+ {
+ this.completionRetryLimit = maxCompletionRetries;
+ }
+
+ /**
+ * Gets the completion retry limit. This is the maximum number of times that
+ * the transaction manager retries a completion operation (either commit or
+ * rollback) on a resource (either a remote <code>Resource</code> or an
+ * <code>XAResource</code>) that raised a transient exception. Whoever called
+ * the transaction manager is blocked while the completion retries are
+ * performed. If the completion retry limit is reached, the transaction
+ * manager abandons the retries and throws a heuristic hazard exception.
+ *
+ * @return the completion retry limit.
+ */
+ public int getCompletionRetryLimit()
+ {
+ return completionRetryLimit;
+ }
+
+ /**
+ * Sets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @param seconds the timeout (in seconds) for retrying a completion
+ * operation after a transient exception and before the
+ * transaction manager reports a heuristic hazard.
+ */
+ public void setCompletionRetryTimeout(int seconds)
+ {
+ this.completionRetryTimeout = 1000 * seconds;
+ }
+
+ /**
+ * Gets the completion retry timeout. The completion retry timeout is the
+ * number of seconds that the transaction manager waits before retrying a
+ * completion operation (either commit or rollback) on a resource (either a
+ * remote <code>Resource</code> or an <code>XAResource</code>) that raised a
+ * transient exception. This is a blocking timeout (whoever called the
+ * transaction manager is blocked until the commit or rollback is retried)
+ * and is applicable if the transaction manager did not report a heuristic
+ * hazard for the transaction. If a heuristic hazard has been reported, then
+ * the applicable timouts are the non-blocking ones: the XA retry timeout and
+ * the prepared timeout.
+ *
+ * @return the timeout (in seconds) for retrying a completion operation
+ * after a transient exception and before the transaction manager
+ * reports a heuristic hazard.
+ */
+ public int getCompletionRetryTimeout()
+ {
+ return completionRetryTimeout / 1000;
+ }
+
+ /**
+ * Gets the completion retry timeout in milliseconds.
+ *
+ * @return the timeout (in milliseconds) for retrying a completion operation
+ * after a transient exception and before the transaction manager
+ * reports a heuristic hazard.
+ */
+ public int getCompletionRetryTimeoutMillis()
+ {
+ return xaRetryTimeout;
+ }
+
+ /**
+ * Sets the XA retry timeout.
+ *
+ * @param seconds the timeout (in seconds) for retrying operations on XA
+ * resources.
+ */
+ public void setXARetryTimeout(int seconds)
+ {
+ this.xaRetryTimeout = 1000 * seconds;
+ }
+
+ /**
+ * Gets the XA retry timeout.
+ *
+ * @return the timeout (in seconds) for retrying operations on XA resources.
+ */
+ public int getXARetryTimeout()
+ {
+ return xaRetryTimeout / 1000;
+ }
+
+ /**
+ * Gets the XA retry timeout in milliseconds.
+ *
+ * @return the timeout (in milliseconds) for retrying operations on XA
+ * resources.
+ */
+ public int getXARetryTimeoutMillis()
+ {
+ return xaRetryTimeout;
+ }
+
+ /**
+ * Sets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @param seconds the timeout (in seconds) for a transaction branch in the
+ * the prepared state.
+ */
+ public void setPreparedTimeout(int seconds)
+ {
+ this.preparedTimeout = 1000 * seconds;
+ }
+
+ /**
+ * Gets the prepared timeout. A transaction branch that is the prepared
+ * state waits for an amount of time equal to the prepared timeout before
+ * generating a call to <code>replayCompletion</code> on its recovery
+ * coordinator.
+ *
+ * @return the timeout (in seconds) for a transaction branch in the prepared
+ * state.
+ */
+ public int getPreparedTimeout()
+ {
+ return preparedTimeout / 1000;
+ }
+
+ /**
+ * Gets the prepared timeout in milliseconds.
+ *
+ * @return the timeout (in milliseconds) for a transaction branch in the
+ * prepared state.
+ */
+ public int getPreparedTimeoutMillis()
+ {
+ return preparedTimeout;
+ }
+
+ /**
+ * Sets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @param newValue true if the root branch should remember heuristic
+ * decisions, false otherwise.
+ */
+ public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+ {
+ rootBranchRemembersHeuristicDecisions = newValue;
+ }
+
+ /**
+ * Gets the boolean attribute "root branch remembers heuristic decisions".
+ * If this attribute is true, the root branch remembers a heuristically
+ * completed transaction until explicitly told (through a call to the MBean
+ * operation <code>forget</code>) to forget that transaction. If this
+ * attribute is false, the root branch immediately forgets a transaction
+ * when the transaction completes.
+ *
+ * @return true if the root branch remember heuristic decisions,
+ * false otherwise.
+ */
+ public boolean isRootBranchRemembersHeuristicDecisions()
+ {
+ return rootBranchRemembersHeuristicDecisions;
+ }
+
+ /**
+ * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @param newValue true if a JTA commit should throw
+ * <code>HeuristicMixedException</code> to report a heuristic hazard
+ * to its caller, or false if a JTA commit should not report a
+ * heuristic hazard to its caller.
+ */
+ public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+ {
+ reportHeuristicHazardAsHeuristicMixed = newValue;
+ }
+
+ /**
+ * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+ * If this attribute is true, each of the commit methods of the JTA API
+ * (<code>javax.transaction.Transaction.commit()</code>,
+ * <code>javax.transaction.TransactionManager.commit()</code>, and
+ * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+ * <code>HeuristicMixedException</code> to report a heuristic hazard to its
+ * caller. If the attribute is false, those methods do not report heuristic
+ * hazards to their callers. In any case, transactions with heuristic hazard
+ * status are listed by the MBean operation
+ * <code>listHeuristicallyCompletedTransactions()</code>.
+ *
+ * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+ * to report a heuristic hazard to its caller, or false if a JTA
+ * commit does not report a heuristic hazard to its caller.
+ */
+ public boolean isReportHeuristicHazardAsHeuristicMixed()
+ {
+ return reportHeuristicHazardAsHeuristicMixed;
+ }
+
+ /**
+ * Begin a new transaction.
+ * The new transaction will be associated with the calling thread.
+ */
+ public void begin()
+ throws NotSupportedException,
+ SystemException
+ {
+ trace = log.isTraceEnabled(); // hack!
+
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ if (current.isDone())
+ disassociateThread(ti);
+ else
+ throw new NotSupportedException
+ ("Transaction already active, cannot nest transactions.");
+ }
+
+ long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
+ TransactionImpl tx = new TransactionImpl(timeout);
+ associateThread(ti, tx);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("began tx: " + tx);
+ }
+
+ /**
+ * Commit the transaction associated with the currently running thread.
+ */
+ public void commit()
+ throws RollbackException,
+ HeuristicMixedException,
+ HeuristicRollbackException,
+ SecurityException,
+ IllegalStateException,
+ SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ current.commit();
+ disassociateThread(ti);
+ if (trace)
+ log.trace("commited tx: " + current);
+ }
+ else
+ throw new IllegalStateException("No transaction.");
+ }
+
+ /**
+ * Return the status of the transaction associated with the currently
+ * running thread, or <code>Status.STATUS_NO_TRANSACTION</code> if no
+ * active transaction is currently associated.
+ */
+ public int getStatus()
+ throws SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ if (current.isDone())
+ disassociateThread(ti);
+ else
+ return current.getStatus();
+ }
+ return Status.STATUS_NO_TRANSACTION;
+ }
+
+ /**
+ * Return the transaction currently associated with the invoking thread,
+ * or <code>null</code> if no active transaction is currently associated.
+ */
+ public Transaction getTransaction()
+ throws SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null && current.isDone())
+ {
+ current = null;
+ disassociateThread(ti);
+ }
+
+ return current;
+ }
+
+ /**
+ * Resume a transaction.
+ * <p/>
+ * Note: This will not enlist any resources involved in this
+ * transaction. According to JTA1.0.1 specification section 3.2.3,
+ * that is the responsibility of the application server.
+ */
+ public void resume(Transaction transaction)
+ throws InvalidTransactionException,
+ IllegalStateException,
+ SystemException
+ {
+ if (transaction != null && !(transaction instanceof TransactionImpl))
+ throw new RuntimeException("Not a TransactionImpl, but a " +
+ transaction.getClass().getName());
+
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ if (current.isDone())
+ current = ti.tx = null;
+ else
+ throw new IllegalStateException("Already associated with a tx");
+ }
+
+ if (current != transaction)
+ {
+ associateThread(ti, (TransactionImpl) transaction);
+ }
+
+ if (trace)
+ log.trace("resumed tx: " + ti.tx);
+ }
+
+ /**
+ * Suspend the transaction currently associated with the current
+ * thread, and return it.
+ * <p/>
+ * Note: This will not delist any resources involved in this
+ * transaction. According to JTA1.0.1 specification section 3.2.3,
+ * that is the responsibility of the application server.
+ */
+ public Transaction suspend()
+ throws SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ current.disassociateCurrentThread();
+ ti.tx = null;
+
+ if (trace)
+ log.trace("suspended tx: " + current);
+
+ if (current.isDone())
+ current = null;
+ }
+
+ return current;
+ }
+
+ /**
+ * Roll back the transaction associated with the currently running thread.
+ */
+ public void rollback()
+ throws IllegalStateException,
+ SecurityException,
+ SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ if (!current.isDone())
+ {
+ current.rollback();
+
+ if (trace)
+ log.trace("rolled back tx: " + current);
+ return;
+ }
+ disassociateThread(ti);
+ }
+ throw new IllegalStateException("No transaction.");
+ }
+
+ /**
+ * Mark the transaction associated with the currently running thread
+ * so that the only possible outcome is a rollback.
+ */
+ public void setRollbackOnly()
+ throws IllegalStateException,
+ SystemException
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+
+ if (current != null)
+ {
+ if (!current.isDone())
+ {
+ current.setRollbackOnly();
+
+ if (trace)
+ log.trace("tx marked for rollback only: " + current);
+ return;
+ }
+ ti.tx = null;
+ }
+ throw new IllegalStateException("No transaction.");
+ }
+
+ public int getTransactionTimeout()
+ {
+ return (int) (getThreadInfo().timeout / 1000);
+ }
+
+ /**
+ * Set the transaction timeout for new transactions started by the
+ * calling thread.
+ */
+ public void setTransactionTimeout(int seconds)
+ throws SystemException
+ {
+ getThreadInfo().timeout = 1000 * seconds;
+
+ if (trace)
+ log.trace("tx timeout is now: " + seconds + "s");
+ }
+
+ /**
+ * Set the default transaction timeout for new transactions.
+ * This default value is used if <code>setTransactionTimeout()</code>
+ * was never called, or if it was called with a value of <code>0</code>.
+ */
+ public void setDefaultTransactionTimeout(int seconds)
+ {
+ timeOut = 1000L * seconds;
+
+ if (trace)
+ log.trace("default tx timeout is now: " + seconds + "s");
+ }
+
+ /**
+ * Get the default transaction timeout.
+ *
+ * @return Default transaction timeout in seconds.
+ */
+ public int getDefaultTransactionTimeout()
+ {
+ return (int) (timeOut / 1000);
+ }
+
+ public long getTimeLeftBeforeTransactionTimeout(boolean errorRollback) throws RollbackException
+ {
+ try
+ {
+ ThreadInfo ti = getThreadInfo();
+ TransactionImpl current = ti.tx;
+ if (current != null && current.isDone())
+ {
+ disassociateThread(ti);
+ return -1;
+ }
+ return current.getTimeLeftBeforeTimeout(errorRollback);
+ }
+ catch (RollbackException e)
+ {
+ throw e;
+ }
+ catch (Exception ignored)
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * The following 2 methods are here to provide association and
+ * disassociation of the thread.
+ */
+ public Transaction disassociateThread()
+ {
+ return disassociateThread(getThreadInfo());
+ }
+
+ private Transaction disassociateThread(ThreadInfo ti)
+ {
+ TransactionImpl current = ti.tx;
+ ti.tx = null;
+ current.disassociateCurrentThread();
+ return current;
+ }
+
+ public void associateThread(Transaction transaction)
+ {
+ if (transaction != null && !(transaction instanceof TransactionImpl))
+ throw new RuntimeException("Not a TransactionImpl, but a " +
+ transaction.getClass().getName());
+
+ // Associate with the thread
+ TransactionImpl transactionImpl = (TransactionImpl) transaction;
+ ThreadInfo ti = getThreadInfo();
+ ti.tx = transactionImpl;
+ transactionImpl.associateCurrentThread();
+ }
+
+ private void associateThread(ThreadInfo ti, TransactionImpl transaction)
+ {
+ // Associate with the thread
+ ti.tx = transaction;
+ transaction.associateCurrentThread();
+ }
+
+ /**
+ * Return the number of active transactions
+ */
+ public int getTransactionCount()
+ {
+ return localIdTx.size();
+ }
+
+ /**
+ * A count of the transactions that have been committed
+ */
+ public long getCommitCount()
+ {
+ return commitCount;
+ }
+
+ /**
+ * A count of the transactions that have been rolled back
+ */
+ public long getRollbackCount()
+ {
+ return rollbackCount;
+ }
+
+ /**
+ * Lists the in-doubt transactions.
+ *
+ * @return a string with a text listing of in-doubt transactions.
+ */
+ public String listInDoubtTransactions()
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Heuristically commits an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically committed.
+ */
+ public void heuristicallyCommit(long localTransactionId)
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Heuristically commits all in-doubt transactions.
+ */
+ public void heuristicallyCommitAll()
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Heuristically rolls back an in-doubt transaction.
+ *
+ * @param localTransactionId the local id of the in-doubt transaction to be
+ * heuristically rolled back.
+ */
+ public void heuristicallyRollback(long localTransactionId)
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Heuristically rolls back all in-doubt transactions.
+ */
+ public void heuristicallyRollbackAll()
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Lists the heuristically completed transactions coordinated by this
+ * transaction manager. A transaction that was heuristically completed
+ * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+ * <code>heuristicallyCommitAll()</code>,
+ * <code>heuristicallyRollback(long localTransactionId)</code>, or
+ * <code>heuristicallyRollbackAll()</code> does not appear in the listing,
+ * as that transaction had a foreign coordinator.
+ *
+ * @return a string with a text listing of heuristically completed
+ * transactions.
+ */
+ public String listHeuristicallyCompletedTransactions()
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Forgets a heuristically completed transaction coordinated by this
+ * transaction manager.
+ *
+ * @param localTransactionId the local id of a heuristically completed
+ * transaction coordinated by this transaction
+ * manager.
+ */
+ public void forget(long localTransactionId)
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ /**
+ * Forgets all heuristically completed transactions coordinated by this
+ * transaction manager.
+ */
+ public void forgetAll()
+ {
+ // TODO
+ throw new org.jboss.util.NotImplementedException();
+ }
+
+ // Implements TransactionPropagationContextImporter ---------------
+
+ /**
+ * Import a transaction propagation context into this TM.
+ * The TPC is loosely typed, as we may (at a later time) want to
+ * import TPCs that come from other transaction domains without
+ * offloading the conversion to the client.
+ *
+ * @param tpc The transaction propagation context that we want to
+ * import into this TM. Currently this is an instance
+ * of LocalId. At some later time this may be an instance
+ * of a transaction propagation context from another
+ * transaction domain like
+ * org.omg.CosTransactions.PropagationContext.
+ * @return a transaction representing this transaction propagation
+ * context, or null if this TPC cannot be imported.
+ */
+ public Transaction importTransactionPropagationContext(Object tpc)
+ {
+ if (tpc instanceof LocalId)
+ {
+ LocalId id = (LocalId) tpc;
+ return (Transaction) localIdTx.get(id);
+ }
+ else if (tpc instanceof GlobalId && globalIdsEnabled)
+ {
+ GlobalId id = (GlobalId) tpc;
+ Transaction tx = (Transaction) globalIdTx.get(id);
+ if (trace)
+ {
+ if (tx != null)
+ log.trace("Successfully imported transaction context " + tpc);
+ else
+ log.trace("Could not import transaction context " + tpc);
+ }
+ return tx;
+ }
+ else if (tpc instanceof TxPropagationContext && dtmEnabled)
+ {
+ TxPropagationContext fullTpc = (TxPropagationContext) tpc;
+ Transaction tx = importExternalTransaction(fullTpc.formatId,
+ fullTpc.globalId,
+ fullTpc.coordinator,
+ fullTpc.timeout * 1000);
+ if (trace)
+ {
+ if (tx != null)
+ log.trace("Successfully imported transaction context " + tpc);
+ else
+ log.trace("Could not import transaction context " + tpc);
+ }
+ return tx;
+ }
+ log.warn("Cannot import transaction propagation context: " + tpc);
+ return null;
+ }
+
+ // Implements TransactionPropagationContextFactory ---------------
+
+ /**
+ * Return a TPC for the current transaction.
+ */
+ public Object getTransactionPropagationContext()
+ {
+ return getTransactionPropagationContext(getThreadInfo().tx);
+ }
+
+ /**
+ * Return a TPC for the argument transaction.
+ */
+ public Object getTransactionPropagationContext(Transaction t)
+ {
+ // If no transaction or unknown transaction class, return null.
+ if (t == null)
+ return null;
+ if (!(t instanceof TransactionImpl))
+ {
+ log.warn("Cannot export transaction propagation context: " + t);
+ return null;
+ }
+
+ TransactionImpl tx = (TransactionImpl) t;
+
+ if (!dtmEnabled)
+ {
+ return tx.getLocalId();
+ }
+ else
+ {
+ return tx.getPropagationContext();
+ }
+ }
+
+ // Implements XATerminator ----------------------------------
+
+ public void registerWork(Work work, Xid xid, long timeout)
+ throws WorkCompletedException
+ {
+ if (trace)
+ log.trace("registering work=" + work + " xid=" + xid +
+ " timeout=" + timeout);
+ try
+ {
+ TransactionImpl tx = importExternalTransaction(xid, timeout);
+ tx.setWork(work);
+ }
+ catch (WorkCompletedException e)
+ {
+ throw e;
+ }
+ catch (Throwable t)
+ {
+ WorkCompletedException e =
+ new WorkCompletedException("Error registering work", t);
+ e.setErrorCode(WorkException.TX_RECREATE_FAILED);
+ throw e;
+ }
+ if (trace)
+ log.trace("registered work= " + work + " xid=" + xid +
+ " timeout=" + timeout);
+ }
+
+ public void startWork(Work work, Xid xid)
+ throws WorkCompletedException
+ {
+ if (trace)
+ log.trace("starting work=" + work + " xid=" + xid);
+ TransactionImpl tx = getExternalTransaction(xid);
+ associateThread(tx);
+ if (trace)
+ log.trace("started work= " + work + " xid=" + xid);
+ }
+
+ public void endWork(Work work, Xid xid)
+ {
+ if (trace)
+ log.trace("ending work=" + work + " xid=" + xid);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ tx.setWork(null);
+ disassociateThread();
+ }
+ catch (WorkCompletedException e)
+ {
+ log.error("Unexpected error from endWork ", e);
+ throw new UnexpectedThrowable(e.toString());
+ }
+ if (trace)
+ log.trace("ended work=" + work + " xid=" + xid);
+ }
+
+ public void cancelWork(Work work, Xid xid)
+ {
+ if (trace)
+ log.trace("cancling work=" + work + " xid=" + xid);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ tx.setWork(null);
+ }
+ catch (WorkCompletedException e)
+ {
+ log.error("Unexpected error from cancelWork ", e);
+ throw new UnexpectedThrowable(e.toString());
+ }
+ if (trace)
+ log.trace("cancled work=" + work + " xid=" + xid);
+ }
+
+ public int prepare(Xid xid)
+ throws XAException
+ {
+ if (trace)
+ log.trace("preparing xid=" + xid);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ resume(tx);
+ int result = tx.prepare(xid);
+ if (trace)
+ log.trace("prepared xid=" + xid + " result=" + result);
+ return result;
+ }
+ catch (Throwable t)
+ {
+ JBossXAException.rethrowAsXAException("Error during prepare", t);
+ throw new UnreachableStatementException();
+ }
+ finally
+ {
+ try
+ {
+ suspend();
+ }
+ catch (SystemException e)
+ {
+ JBossXAException.rethrowAsXAException("Error during prepare", e);
+ throw new UnreachableStatementException();
+ }
+ }
+ }
+
+ public void rollback(Xid xid)
+ throws XAException
+ {
+ if (trace)
+ log.trace("rolling back xid=" + xid);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ tx.rollback();
+ }
+ catch (Throwable t)
+ {
+ JBossXAException.rethrowAsXAException("Error during rollback", t);
+ }
+ if (trace)
+ log.trace("rolled back xid=" + xid);
+ }
+
+ public void commit(Xid xid, boolean onePhase)
+ throws XAException
+ {
+ if (trace)
+ log.trace("committing xid=" + xid + " onePhase=" + onePhase);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ tx.commit(onePhase);
+ }
+ catch (Throwable t)
+ {
+ JBossXAException.rethrowAsXAException("Error during commit", t);
+ }
+ if (trace)
+ log.trace("committed xid=" + xid);
+ }
+
+ public void forget(Xid xid)
+ throws XAException
+ {
+ if (trace)
+ log.trace("forgetting xid=" + xid);
+ try
+ {
+ TransactionImpl tx = getExternalTransaction(xid);
+ tx.forget();
+ }
+ catch (Throwable t)
+ {
+ JBossXAException.rethrowAsXAException("Error during forget", t);
+ }
+ if (trace)
+ log.trace("forgot xid=" + xid);
+ }
+
+ public Xid[] recover(int flag)
+ throws XAException
+ {
+ List xidList = new ArrayList();
+ Collection txCollection = localIdTx.values();
+ synchronized (localIdTx)
+ {
+ Iterator i = txCollection.iterator();
+ while (i.hasNext())
+ {
+ TransactionImpl tx = (TransactionImpl) i.next();
+ if (tx.isPreparedOrHeuristicallyCompletedJCAInboundTx())
+ xidList.add(tx.getInboundXid());
+ }
+ }
+ return (Xid[]) xidList.toArray(new Xid[xidList.size()]);
+ }
+
+ /**
+ * Imports an external transaction. This method is called for foreign
+ * transaction imported through DTM or OTS transaction propagation contexts.
+ */
+ public TransactionImpl importExternalTransaction(int formatId,
+ byte[] globalId,
+ Coordinator coordinator,
+ long txTimeOut)
+ {
+ GlobalId gid = new GlobalId(formatId, globalId);
+ TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("imported existing transaction gid: " + gid +
+ " tx=" + tx);
+ }
+ else
+ {
+ ThreadInfo ti = getThreadInfo();
+ long timeout = (ti.timeout == 0) ? txTimeOut : ti.timeout;
+ tx = new TransactionImpl(gid, coordinator, timeout);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(gid, tx);
+
+ if (trace)
+ log.trace("imported new transaction gid: " + gid + " tx=" + tx +
+ " timeout=" + timeout);
+ }
+ return tx;
+ }
+
+ /**
+ * Imports an external transaction. This method is called for foreign
+ * transactions imported through the JCA transaction inflow mechanism.
+ */
+ TransactionImpl importExternalTransaction(Xid xid, long txTimeOut)
+ {
+ GlobalId gid = new GlobalId(xid.getFormatId(),
+ xid.getGlobalTransactionId());
+ TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("imported existing transaction gid: " + gid +
+ " tx=" + tx);
+ }
+ else
+ {
+ ThreadInfo ti = getThreadInfo();
+ long timeout = (ti.timeout == 0) ? txTimeOut : ti.timeout;
+ tx = new TransactionImpl(gid, xid.getBranchQualifier(), timeout);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(gid, tx);
+
+ if (trace)
+ log.trace("imported new transaction gid: " + gid + " tx=" + tx +
+ " timeout=" + timeout);
+ }
+ return tx;
+ }
+
+ TransactionImpl getExternalTransaction(Xid xid)
+ throws WorkCompletedException
+ {
+ GlobalId gid = new GlobalId(xid);
+ TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+ if (tx == null)
+ throw new WorkCompletedException("Xid not found " + xid,
+ WorkException.TX_RECREATE_FAILED);
+ return tx;
+ }
+
+ /**
+ * Recreates a locally-started transaction that does not involve other
+ * transaction managers. This method is intended to be called at recovery
+ * time, for recreating transactions that were in the committing state when
+ * the server crashed. Such a transaction completed the first phase of the
+ * 2PC protocol and logged the commit decision, but it must still send
+ * commit messages to some or all of its <code>XAResource</code>s.
+ *
+ * @param localId the local id of a locally-started transaction that is in
+ * the committing state and does not involve other transaction
+ * managers
+ * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is enlisted with the transaction
+ * and is still in the prepared state
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ public void recreateTransaction(long localId,
+ List pendingXAWorkList,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ LocalId localIdObject = new LocalId(localId);
+ TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("recreateTransaction called for existing transaction, " +
+ "localId=" + localId + ", tx=" + tx,
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ tx = new TransactionImpl(localId,
+ pendingXAWorkList,
+ completionHandler,
+ heurData);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("recreated transaction with localId=" + localId +
+ ", tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+ }
+ }
+
+ /**
+ * Recreates a locally-started transaction that involves other transaction
+ * managers. Involving other transaction managers means that there are
+ * remote <code>Resource</code>s enlisted with the transaction. This method
+ * is intended to be called at recovery time, for recreating transactions
+ * that were in the committing state when the server crashed. Such a
+ * transaction completed the first phase of the 2PC protocol and logged the
+ * commit decision, but it must still send commit messages to some or all of
+ * its resources (<code>XAResource</code>s and remote
+ * <code>Resource</code>s).
+ *
+ * @param localId the local id of a locally-started transaction that is in
+ * the committing state and involves other transaction managers
+ * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is enlisted with the transaction
+ * and is still in the prepared state
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ public void recreateTransaction(long localId,
+ List pendingXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ LocalId localIdObject = new LocalId(localId);
+ TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("recreateTransaction called for existing transaction, " +
+ "localId=" + localId + ", tx=" + tx,
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ tx = new TransactionImpl(localId,
+ pendingXAWorkList,
+ resources,
+ completionHandler,
+ heurData);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("recreated transaction with localId=" + localId +
+ ", tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+ }
+ }
+
+ /**
+ * Recreates a foreign transaction that entered this virtual machine in a
+ * transaction context propagated along with a remote method invocation.
+ * This method is intended to be called at recovery time, for recreating
+ * transactions that were in the prepared state when the server crashed.
+ *
+ * @param localId the local id of a foreign transaction that entered this
+ * virtual machine in a transaction propagation context and is
+ * propagated along with a remote method invocation and is in
+ * the prepared state
+ * @param inboundFormatId format id part of the foreign transaction
+ * identifier that entered this virtual machine
+ * @param globalTransactionId global id part of the foreign transaction
+ * identifier that entered this virtual machine
+ * @param recoveryCoord an stringfied reference to the transaction branch's
+ * <code>RecovertyCoordinator</code>
+ * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> enlisted with the transaction
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ public void recreateTransaction(long localId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ String recoveryCoord,
+ List pendingXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ LocalId localIdObject = new LocalId(localId);
+ TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("recreateTransaction called for existing transaction, " +
+ "localId=" + localId + ", tx=" + tx,
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ tx = new TransactionImpl(localId,
+ inboundFormatId,
+ globalTransactionId,
+ recoveryCoord,
+ pendingXAWorkList,
+ resources,
+ completionHandler,
+ heurData);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("recreated transaction with localId=" + localId +
+ " tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+ }
+ }
+
+ /**
+ * Recreates a foreign transaction that entered this virtual machine through
+ * JCA transaction inflow. This method is intended to be called at recovery
+ * time, for recreating transactions that were in the prepared state when
+ * the server crashed.
+ *
+ * @param localId the local id of a foreign transaction that entered this
+ * virtual machine in a transaction propagation context and is
+ * propagated along with a remote method invocation and is in
+ * the prepared state
+ * @param inboundFormatId format id part of the foreign <code>Xid</code>
+ * @param globalTransactionId global id part of the foreign <code>Xid</code>
+ * @param inboundBranchQualifier the branch qualifier part of the foreign
+ * <code>Xid</code>
+ * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> enlisted with the transaction
+ * @param resources an array with stringfied references for the remote
+ * <code>Resource</code>s enlisted with the transaction
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes
+ * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+ * contaning information on a locally-detected heuristic hazard.
+ */
+ public void recreateTransaction(long localId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ byte[] inboundBranchQualifier,
+ List pendingXAWorkList,
+ String[] resources,
+ TxCompletionHandler completionHandler,
+ LogRecord.HeurData heurData)
+ {
+ LocalId localIdObject = new LocalId(localId);
+ TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("recreateTransaction called for existing transaction, " +
+ "localId=" + localId + ", tx=" + tx,
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ tx = new TransactionImpl(localId,
+ inboundFormatId,
+ globalTransactionId,
+ inboundBranchQualifier,
+ pendingXAWorkList,
+ resources,
+ completionHandler,
+ heurData);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("recreated transaction with localId=" + localId +
+ ", tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+ }
+ }
+
+ /**
+ * Recreates a transaction that is in a heuristically completed state
+ * (either committed or rolledback). This method is intended to be called
+ * at recovery time, for recreating heuristically completed transactions
+ * that were not yet forgotten when the server crashed.
+ *
+ * @param heurData an instance of <code>LogRecord.HeurData</code> with
+ * information on the heuristically completed transaction
+ * @param xaResourcesWithHeuristics a list of
+ * <code>org.jboss.tm.XAWork</code>
+ * instances containing one element for each
+ * <code>XAResource</code> that is in a a heuristic status
+ * @param completionHandler the
+ * <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+ * notifed when the second phase of the 2PC completes.
+ */
+ public void recreateTransaction(LogRecord.HeurData heurData,
+ List xaResourcesWithHeuristics,
+ TxCompletionHandler completionHandler)
+ {
+ LocalId localIdObject = new LocalId(heurData.localTransactionId);
+ TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+ if (tx != null)
+ {
+ if (trace)
+ log.trace("recreateTransaction called for existing transaction, " +
+ "localId=" + heurData.localTransactionId + ", tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()) +
+ ", heuristicStatus=" + TxUtils.getXAErrorCodeAsString(
+ heurData.heuristicStatusCode),
+ new Throwable("[Stack trace]"));
+ }
+ else
+ {
+ tx = new TransactionImpl(heurData,
+ xaResourcesWithHeuristics,
+ completionHandler);
+ localIdTx.put(tx.getLocalId(), tx);
+ if (globalIdsEnabled)
+ globalIdTx.put(tx.getGlobalId(), tx);
+
+ if (trace)
+ log.trace("recreated transaction with localId=" +
+ heurData.localTransactionId + ", tx=" + tx +
+ ", status=" + TxUtils.getStatusAsString(tx.getStatus()) +
+ ", heuristicStatus=" + TxUtils.getXAErrorCodeAsString(
+ heurData.heuristicStatusCode));
+
+ //if (!tx.isImported())
+ //{
+ // tx.forget();
+ //}
+ }
+ }
+
+ // Implements TransactionLocalDelegate ----------------------
+
+ public void lock(TransactionLocal local, Transaction tx) throws InterruptedException
+ {
+ TransactionImpl tximpl = (TransactionImpl) tx;
+ tximpl.lock();
+ }
+
+ public void unlock(TransactionLocal local, Transaction tx)
+ {
+ TransactionImpl tximpl = (TransactionImpl) tx;
+ tximpl.unlock();
+ }
+
+ public Object getValue(TransactionLocal local, Transaction tx)
+ {
+ TransactionImpl tximpl = (TransactionImpl) tx;
+ return tximpl.getTransactionLocalValue(local);
+ }
+
+ public void storeValue(TransactionLocal local, Transaction tx, Object value)
+ {
+ TransactionImpl tximpl = (TransactionImpl) tx;
+ tximpl.putTransactionLocalValue(local, value);
+ }
+
+ public boolean containsValue(TransactionLocal local, Transaction tx)
+ {
+ TransactionImpl tximpl = (TransactionImpl) tx;
+ return tximpl.containsTransactionLocal(local);
+ }
+
+ // Implements StringRemoteRefConverter ----------------------------
+
+ public Resource stringToResource(String strResource)
+ {
+ return TransactionImpl.stringToResource(strResource);
+ }
+
+ public RecoveryCoordinator stringToRecoveryCoordinator(
+ String strRecCoordinator)
+ {
+ return TransactionImpl.stringToRecoveryCoordinator(strRecCoordinator);
+ }
+
+ public String resourceToString(Resource res)
+ {
+ return TransactionImpl.resourceToString(res);
+ }
+
+ public String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+ {
+ return TransactionImpl.recoveryCoordinatorToString(recoveryCoord);
+ }
+
+ // Attribute getters and setters ----------------------------------
+
+ /**
+ * Setter for attribute <code>dtmEnabled</code>.
+ */
+ public void setDTMEnabled(boolean newValue)
+ {
+ if (newValue == true && !globalIdsEnabled)
+ {
+ // DTM requires global ids
+ setGlobalIdsEnabled(newValue);
+ }
+ dtmEnabled = newValue;
+ }
+
+ /**
+ * Setter for the DTM <code>CoordinatorFactory</code>.
+ */
+ public void setDTMCoordinatorFactory(
+ CoordinatorFactory dtmCoordinatorFactory)
+ {
+ TransactionImpl.setDTMCoordinatorFactory(dtmCoordinatorFactory);
+ }
+
+ /**
+ * Setter for the DTM <code>ResourceFactory</code>.
+ */
+ public void setDTMResourceFactory(ResourceFactory dtmResourceFactory)
+ {
+ TransactionImpl.setDTMResourceFactory(dtmResourceFactory);
+ }
+
+ /**
+ * Setter for the OTS <code>ResourceFactory</code>.
+ */
+ public void setOTSResourceFactory(ResourceFactory otsResourceFactory)
+ {
+ TransactionImpl.setOTSResourceFactory(otsResourceFactory);
+ }
+
+ /**
+ * Setter for the <code>OTSContextFactory</code>.
+ */
+ public void setOTSContextFactory(OTSContextFactory otsContextFactory)
+ {
+ TransactionImpl.setOTSContextFactory(otsContextFactory);
+ }
+
+ /**
+ * Setter for the <code>interpositionEnabled</code> flag.
+ */
+ public void setInterpositionEnabled(boolean newValue)
+ {
+ TransactionImpl.setInterpositionEnabled(newValue);
+ }
+
+ /**
+ * Getter for the <code>interpositionEnabled</code> flag.
+ */
+ public boolean getInterpositionEnabled()
+ {
+ return TransactionImpl.getInterpositionEnabled();
+ }
+
+ public void setDTMStringRemoteRefConverter(
+ StringRemoteRefConverter otsStrRemoteRefConverter)
+ {
+ TransactionImpl.setDTMStrRemoteRefConverter(otsStrRemoteRefConverter);
+ }
+
+ public void setOTSStringRemoteRefConverter(
+ StringRemoteRefConverter otsStrRemoteRefConverter)
+ {
+ TransactionImpl.setOTSStrRemoteRefConverter(otsStrRemoteRefConverter);
+ }
+
+ // Package protected ---------------------------------------------
+
+ /**
+ * Release the given TransactionImpl.
+ */
+ void releaseTransactionImpl(TransactionImpl tx)
+ {
+ localIdTx.remove(tx.getLocalId());
+ if (globalIdsEnabled)
+ globalIdTx.remove(tx.getGlobalId());
+ }
+
+ /**
+ * Increment the commit count
+ */
+ void incCommitCount()
+ {
+ ++commitCount;
+ }
+
+ /**
+ * Increment the rollback count
+ */
+ void incRollbackCount()
+ {
+ ++rollbackCount;
+ }
+
+ // Protected -----------------------------------------------------
+
+ // Private -------------------------------------------------------
+
+ /**
+ * This keeps track of the thread association with transactions
+ * and timeout values.
+ * In some cases terminated transactions may not be cleared here.
+ */
+ private ThreadLocal threadTx = new ThreadLocal();
+
+ /**
+ * This map contains the active transactions as values.
+ * The keys are the <code>LocalId</code>s of the transactions.
+ */
+ private Map localIdTx = Collections.synchronizedMap(new HashMap());
+
+
+ /**
+ * If <code>globalIdsEnabled</code> is true, this map associates
+ * <code>GlobalId</code>s to active transactions.
+ */
+ private Map globalIdTx = Collections.synchronizedMap(new HashMap());
+
+
+ /**
+ * Return the ThreadInfo for the calling thread, and create if not
+ * found.
+ */
+ private ThreadInfo getThreadInfo()
+ {
+ ThreadInfo ret = (ThreadInfo) threadTx.get();
+
+ if (ret == null)
+ {
+ ret = new ThreadInfo();
+ ret.timeout = timeOut;
+ threadTx.set(ret);
+ }
+
+ return ret;
+ }
+
+ // Inner classes -------------------------------------------------
+
+ /**
+ * A simple aggregate of a thread-associated timeout value
+ * and a thread-associated transaction.
+ */
+ static class ThreadInfo
+ {
+ long timeout;
+ TransactionImpl tx;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/XidFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,163 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.system.ServiceMBeanSupport;
+
+/**
+ * XidFactory.java
+ * <p/>
+ * <p/>
+ * Created: Sat Jun 15 19:01:18 2002
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @jmx.mbean
+ */
+public class XidFactory
+ extends ServiceMBeanSupport
+ implements XidFactoryMBean
+{
+
+ //default object name of the naming service
+ private static final javax.management.ObjectName NAMING_OBJECT_NAME =
+ org.jboss.mx.util.ObjectNameFactory.create("jboss:service=Naming");
+
+ private XidFactoryImpl factory = new XidFactoryImpl();
+
+ protected void startService()
+ throws Exception
+ {
+ long jndiPortOrStartUpTime;
+ try
+ {
+ Integer jndiPort =
+ (Integer) getServer().getAttribute(NAMING_OBJECT_NAME, "Port");
+ jndiPortOrStartUpTime = jndiPort.intValue();
+ }
+ catch (Exception e)
+ {
+ jndiPortOrStartUpTime = System.currentTimeMillis();
+ }
+ factory.setUniqueInstanceId(jndiPortOrStartUpTime);
+ factory.start();
+ }
+
+ /**
+ * mbean get-set pair for field instance
+ * Get the value of instance
+ *
+ * @return value of instance
+ * @jmx:managed-attribute
+ */
+ public XidFactoryMBean getInstance()
+ {
+ return this;
+ }
+
+ public String getBaseGlobalId()
+ {
+ return factory.getBaseGlobalId();
+ }
+
+ public void setBaseGlobalId(String baseGlobalId)
+ {
+ factory.setBaseGlobalId(baseGlobalId);
+ }
+
+ public long getGlobalIdNumber()
+ {
+ return factory.getGlobalIdNumber();
+ }
+
+ public void setGlobalIdNumber(long globalIdNumber)
+ {
+ factory.setGlobalIdNumber(globalIdNumber);
+ }
+
+ public String getBranchQualifier()
+ {
+ return factory.getBranchQualifier();
+ }
+
+ public void setBranchQualifier(String branchQualifier)
+ {
+ factory.setBranchQualifier(branchQualifier);
+ }
+
+ public boolean isPad()
+ {
+ return factory.isPad();
+ }
+
+ public void setPad(boolean pad)
+ {
+ factory.setPad(pad);
+ }
+
+ public XidImpl newXid()
+ {
+ return factory.newXid();
+ }
+
+ public XidImpl newBranch(GlobalId globalId)
+ {
+ return factory.newBranch(globalId);
+ }
+
+ public XidImpl newBranch(XidImpl xid,long branchIdNum)
+ {
+ return factory.newBranch(xid, branchIdNum);
+ }
+
+ public XidImpl recreateXid(long localId)
+ {
+ return factory.recreateXid(localId);
+ }
+
+ public XidImpl recreateXid(long localId, GlobalId globalId)
+ {
+ return factory.recreateXid(localId, globalId);
+ }
+
+ public byte[] localIdToGlobalId(long localId)
+ {
+ return factory.localIdToGlobalId(localId);
+ }
+
+ public long extractLocalIdFrom(byte[] globalId)
+ {
+ return factory.extractLocalIdFrom(globalId);
+ }
+
+ public String getBaseBranchQualifier(byte[] branchQualifier)
+ {
+ return factory.getBaseBranchQualifier(branchQualifier);
+ }
+
+ public String toString(Xid xid)
+ {
+ return factory.toString(xid);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,146 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: wburke
+ * Date: May 3, 2005
+ * Time: 2:48:26 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public interface XidFactoryBase
+{
+ /**
+ * mbean get-set pair for field BaseGlobalId Get the value of BaseGlobalId
+ * @return value of BaseGlobalId
+ */
+ String getBaseGlobalId() ;
+
+ /**
+ * Set the value of BaseGlobalId
+ * @param BaseGlobalId Value to assign to BaseGlobalId
+ */
+ void setBaseGlobalId(String baseGlobalId) ;
+
+ /**
+ * mbean get-set pair for field globalIdNumber Get the value of globalIdNumber
+ * @return value of globalIdNumber
+ */
+ long getGlobalIdNumber() ;
+
+ /**
+ * Set the value of globalIdNumber
+ * @param globalIdNumber Value to assign to globalIdNumber
+ */
+ void setGlobalIdNumber(long globalIdNumber) ;
+
+ /**
+ * mbean get-set pair for field branchQualifier
+ * Get the value of BranchQualifier
+ * @return value of BranchQualifier
+ */
+ String getBranchQualifier() ;
+
+ /**
+ * Set the value of BranchQualifier
+ * @param branchQualifier Value to assign to BranchQualifier
+ */
+ void setBranchQualifier(String branchQualifier) ;
+
+ /**
+ * mbean get-set pair for field pad Get the value of pad
+ * @return value of pad
+ */
+ boolean isPad() ;
+
+ /**
+ * Set the value of pad
+ * @param pad Value to assign to pad
+ */
+ void setPad(boolean pad) ;
+
+ /**
+ * Creates a <code>XidImpl</code> for a new transaction.
+ * @return a <code>XidImpl</code> value
+ */
+ XidImpl newXid() ;
+
+ /**
+ * Creates a <code>XidImpl</code> for a branch of an existing transaction.
+ * @param globalId the code>GlobalId</code> of the existing transaction
+ * @return a <code>XidImpl</code> tor the new transaction branch.
+ */
+ XidImpl newBranch(GlobalId globalId);
+
+ /**
+ * Creates a <code>XidImpl</code> for a "fake branch" of an existing
+ * transaction. This method exists for a single reason: some
+ * <code>XAResource</code> implementations return false on all calls to
+ * <code>isSameRM</code>. If <code>isSameRM</code> worked properly, then
+ * there would be no need to generate a "fake branch" Xid for each
+ * <code>XAResource</code> that may or may not represent a new RM. A same
+ * <code>Xid</code> (the real <code>Xid</code> for the transaction branch)
+ * could (and should) be used for all <code>XAResource</code>s that
+ * represent different RMs. If <code>isSameRM</code> may return false
+ * negatives, however, the real branch Xid may be passed more than once
+ * to a same RM in calls to <code>XAResource.start</code>. This would result
+ * in <code>XAException</code>s with error code <code>XAER_DUPID</code>.
+ * We avoid this problem by generating a "fake branch" <code>Xid</code>
+ * for each <code>XAResource</code> that corresponds to a "new" RM according
+ * to <code>isSameRM</code>, but might actually not represent a new one.
+ *
+ * @param xid a <code>XidImpl</code> for the existing transaction
+ * @param branchNum a branch number to be included in the branch qualifier.
+ * @return a <code>XidImpl</code> for the new transaction branch.
+ */
+ XidImpl newBranch(XidImpl xid,long branchIdNum);
+
+ XidImpl recreateXid(long localId);
+
+ XidImpl recreateXid(long localId, GlobalId globalId);
+
+ /**
+ * Converts a local id into a global transaction id.
+ *
+ * @param localId the local transaction id
+ * @return the global transaction id
+ */
+ public byte[] localIdToGlobalId(long localId);
+
+ /**
+ * Extracts the local id contained in a global id.
+ * @param globalId a global id
+ * @return the local id extracted from the global id
+ */
+ long extractLocalIdFrom(byte[] globalId) ;
+
+ String getBaseBranchQualifier(byte[] branchQualifier);
+
+ /**
+ * Describe <code>toString</code> method here.
+ * @param xid a <code>Xid</code> value
+ * @return a <code>String</code> value
+ */
+ String toString(javax.transaction.xa.Xid xid) ;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,405 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.system.server.ServerConfigUtil;
+
+/**
+ * XidFactory.java
+ * <p/>
+ * <p/>
+ * Created: Sat Jun 15 19:01:18 2002
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @jmx.mbean
+ */
+public class XidFactoryImpl
+ implements XidFactoryBase
+{
+
+ /**
+ * The default value of baseGlobalId is the host name, followed by a
+ * colon (":"), the server's JNDI port, and a slash. It the server's JNDI
+ * port cannot be obtained, then the startService time is used instead.
+ * <p/>
+ * This is used for building globally unique transaction identifiers.
+ * It would be safer to use the IP address, but a host name is better
+ * for humans to read and will do for now.
+ */
+ private String baseGlobalId;
+
+ /**
+ * The next transaction id to use on this host.
+ */
+ private long globalIdNumber = 0;
+
+ /**
+ * The variable <code>pad</code> says whether the byte[] should be their
+ * maximum 64 byte length or the minimum.
+ * The max length is required for Oracle..
+ */
+ private boolean pad = false;
+
+ /**
+ * The <code>branchQualifier</code> is the host name, followed by a
+ * colon (":") and the server's JNDI port. It the server's JNDI
+ * port cannot be obtained, then the startService time is used instead.
+ */
+ private String branchQualifier;
+
+ /**
+ * This field stores the byte reprsentation of baseGlobalId
+ * to avoid the expensive getBytes() call on that String object
+ * when we create a new Xid, which we do VERY often!
+ */
+ private byte[] baseGlobalIdBytes;
+
+ /**
+ * This field stores the byte reprsentation of branchQualifier
+ * to avoid the expensive getBytes() call on that String object.
+ */
+ private byte[] branchQualifierBytes;
+
+ /**
+ * This field stores the byte reprsentation of base branchQualifier
+ * to avoid the expensive getBytes() call on that String object.
+ */
+ private byte[] baseBranchQualifierBytes;
+
+ private long uniqueInstanceId = System.currentTimeMillis();
+
+ public void start()
+ {
+ String hostName = ServerConfigUtil.getSpecificBindAddress();
+
+ baseGlobalId = hostName + ":" + uniqueInstanceId;
+ // Ensure room for 14 digits of serial no and timestamp
+ if (baseGlobalId.length() > Xid.MAXGTRIDSIZE - 15)
+ baseGlobalId = baseGlobalId.substring(0, Xid.MAXGTRIDSIZE - 15);
+ baseGlobalId = baseGlobalId + "/";
+ baseGlobalIdBytes = baseGlobalId.getBytes();
+
+ branchQualifier = hostName + ":" + uniqueInstanceId;
+ int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+ branchQualifierBytes = new byte[len];
+ // this method is deprecated, but does exactly what we need in a very fast
+ // way the default conversion from String.getBytes() is way too expensive
+ branchQualifier.getBytes(0, branchQualifier.length(),
+ branchQualifierBytes, 0);
+
+ String baseBranchQualifier = branchQualifier + "/";
+ baseBranchQualifierBytes = new byte[baseBranchQualifier.length()];
+ // use deprecated method again
+ baseBranchQualifier.getBytes(0, baseBranchQualifier.length(),
+ baseBranchQualifierBytes, 0);
+
+ }
+
+ public long getUniqueInstanceId()
+ {
+ return uniqueInstanceId;
+ }
+
+ public void setUniqueInstanceId(long uniqueInstanceId)
+ {
+ this.uniqueInstanceId = uniqueInstanceId;
+ }
+
+ /**
+ * mbean get-set pair for field BaseGlobalId
+ * Get the value of BaseGlobalId
+ *
+ * @return value of BaseGlobalId
+ * @jmx:managed-attribute
+ */
+ public String getBaseGlobalId()
+ {
+ return baseGlobalId;
+ }
+
+ /**
+ * Set the value of BaseGlobalId
+ *
+ * @param BaseGlobalId Value to assign to BaseGlobalId
+ * @jmx:managed-attribute
+ */
+ public void setBaseGlobalId(final String baseGlobalId)
+ {
+ this.baseGlobalId = baseGlobalId;
+ baseGlobalIdBytes = baseGlobalId.getBytes();
+ }
+
+ /**
+ * mbean get-set pair for field globalIdNumber
+ * Get the value of globalIdNumber
+ *
+ * @return value of globalIdNumber
+ * @jmx:managed-attribute
+ */
+ public synchronized long getGlobalIdNumber()
+ {
+ return globalIdNumber;
+ }
+
+ /**
+ * Set the value of globalIdNumber
+ *
+ * @param globalIdNumber Value to assign to globalIdNumber
+ * @jmx:managed-attribute
+ */
+ public synchronized void setGlobalIdNumber(final long globalIdNumber)
+ {
+ this.globalIdNumber = globalIdNumber;
+ }
+
+ /**
+ * mbean get-set pair for field BranchQualifier
+ * Get the value of BranchQualifier
+ *
+ * @return value of BranchQualifier
+ * @jmx:managed-attribute
+ */
+ public String getBranchQualifier()
+ {
+ return branchQualifier;
+ }
+
+ /**
+ * Set the value of BranchQualifier
+ *
+ * @param branchQualifier Value to assign to BranchQualifier
+ * @jmx:managed-attribute
+ */
+ public void setBranchQualifier(final String branchQualifier)
+ {
+ this.branchQualifier = branchQualifier;
+
+ int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+ branchQualifierBytes = new byte[len];
+ // This method is deprecated, but does exactly what we need in a
+ // very fast way. The default conversion from String.getBytes()
+ // is way too expensive.
+ branchQualifier.getBytes(0, branchQualifier.length(),
+ branchQualifierBytes, 0);
+
+ String baseBranchQualifier = branchQualifier + "/";
+ baseBranchQualifierBytes = new byte[baseBranchQualifier.length()];
+ // use deprecated method again
+ baseBranchQualifier.getBytes(0, baseBranchQualifier.length(),
+ baseBranchQualifierBytes, 0);
+ }
+
+ /**
+ * mbean get-set pair for field pad
+ * Get the value of pad
+ *
+ * @return value of pad
+ * @jmx:managed-attribute
+ */
+ public boolean isPad()
+ {
+ return pad;
+ }
+
+ /**
+ * Set the value of pad
+ *
+ * @param pad Value to assign to pad
+ * @jmx:managed-attribute
+ */
+ public void setPad(boolean pad)
+ {
+ if (this.pad != pad)
+ {
+ this.pad = pad;
+ if (branchQualifier != null)
+ {
+ // update branchQualifierBytes according to the new value of pad
+ int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+ branchQualifierBytes = new byte[len];
+ // This method is deprecated, but does exactly what we need in a
+ // very fast way. The default conversion from String.getBytes()
+ // is way too expensive.
+ branchQualifier.getBytes(0, branchQualifier.length(),
+ branchQualifierBytes, 0);
+ }
+ }
+
+ }
+
+ /**
+ * Creates a <code>XidImpl</code> for a new transaction.
+ *
+ * @return a <code>XidImpl</code> value
+ * @jmx.managed-operation
+ */
+ public XidImpl newXid()
+ {
+ long localId = getNextId();
+ return new XidImpl(localIdToGlobalId(localId),
+ branchQualifierBytes,
+ (int) localId,
+ localId);
+ }
+
+ /**
+ * Creates a <code>XidImpl</code> for a branch of an existing transaction.
+ *
+ * @param globalId the code>GlobalId</code> of the existing transaction
+ * @return a <code>XidImpl</code> tor the new transaction branch.
+ * @jmx.managed-operation
+ */
+ public XidImpl newBranch(GlobalId globalId)
+ {
+ long localId = getNextId();
+ return new XidImpl(globalId, branchQualifierBytes, localId);
+ }
+
+ /**
+ * Creates a <code>XidImpl</code> for a "fake branch" of an existing
+ * transaction. This method exists for a single reason: some
+ * <code>XAResource</code> implementations return false on all calls to
+ * <code>isSameRM</code>. If <code>isSameRM</code> worked properly, then
+ * there would be no need to generate a "fake branch" Xid for each
+ * <code>XAResource</code> that may or may not represent a new RM. A same
+ * <code>Xid</code> (the real <code>Xid</code> for the transaction branch)
+ * could (and should) be used for all <code>XAResource</code>s that
+ * represent different RMs. If <code>isSameRM</code> may return false
+ * negatives, however, the real branch Xid may be passed more than once
+ * to a same RM in calls to <code>XAResource.start</code>. This would result
+ * in <code>XAException</code>s with error code <code>XAER_DUPID</code>.
+ * We avoid this problem by generating a "fake branch" <code>Xid</code>
+ * for each <code>XAResource</code> that corresponds to a "new" RM according
+ * to <code>isSameRM</code>, but might actually not represent a new one.
+ *
+ * @param xid a <code>XidImpl</code> for the existing transaction
+ * @param branchNum a branch number to be included in the branch qualifier.
+ * @return a <code>XidImpl</code> for the new transaction branch.
+ * @jmx.managed-operation
+ */
+ public XidImpl newBranch(XidImpl xid, long branchNum)
+ {
+ String id = Long.toString(branchNum);
+ int len = pad ? Xid.MAXBQUALSIZE
+ : baseBranchQualifierBytes.length + id.length();
+ byte[] branchQualifier = new byte[len];
+ System.arraycopy(baseBranchQualifierBytes, 0,
+ branchQualifier, 0,
+ baseBranchQualifierBytes.length);
+ // This method is deprecated, but does exactly what we need in a very
+ // fast way. The conversion via String.getBytes() is way too expensive.
+ id.getBytes(0, id.length(), branchQualifier,
+ baseBranchQualifierBytes.length);
+ return new XidImpl(xid, branchQualifier);
+ }
+
+ public XidImpl recreateXid(long localId)
+ {
+ return new XidImpl(localIdToGlobalId(localId),
+ branchQualifierBytes,
+ (int) localId,
+ localId);
+ }
+
+ public XidImpl recreateXid(long localId, GlobalId globalId)
+ {
+ return new XidImpl(globalId, branchQualifierBytes, localId);
+ }
+
+ /**
+ * Converts a local id into a global transaction id.
+ *
+ * @param localId the local transaction id
+ * @return the global transaction id
+ */
+ public byte[] localIdToGlobalId(long localId)
+ {
+ String id = Long.toString(localId);
+ int len = pad ? Xid.MAXGTRIDSIZE : id.length() + baseGlobalIdBytes.length;
+ byte[] globalId = new byte[len];
+ System.arraycopy(baseGlobalIdBytes, 0, globalId, 0, baseGlobalIdBytes.length);
+ // this method is deprecated, but does exactly what we need in a very fast way
+ // the default conversion from String.getBytes() is way too expensive
+ id.getBytes(0, id.length(), globalId, baseGlobalIdBytes.length);
+ return globalId;
+ }
+
+ /**
+ * Extracts the local id contained in a global id.
+ *
+ * @param globalId a global id
+ * @return the local id extracted from the global id
+ * @jmx:managed-operation
+ */
+ public long extractLocalIdFrom(byte[] globalId)
+ {
+ int i, start;
+ int len = globalId.length;
+
+ for (i = 0; globalId[i++] != (byte) '/';)
+ ;
+ start = i;
+ while (i < len && globalId[i] != 0)
+ i++;
+ String globalIdNumber = new String(globalId, 0, start, i - start);
+ return Long.parseLong(globalIdNumber);
+ }
+
+ /**
+ * @param branchQualifier
+ * @return
+ * @jmx:managed-operation
+ */
+ public String getBaseBranchQualifier(byte[] branchQualifier)
+ {
+ int len = branchQualifier.length;
+ int i = 0;
+
+ while (branchQualifier[i] != (byte) '/'
+ && branchQualifier[i] != (byte) 0
+ && i < len)
+ i++;
+
+ String base = new String(branchQualifier, 0, 0, i);
+ return base;
+ }
+
+ /**
+ * Describe <code>toString</code> method here.
+ *
+ * @param xid a <code>Xid</code> value
+ * @return a <code>String</code> value
+ * @jmx.managed-operation
+ */
+ public String toString(Xid xid)
+ {
+ return XidImpl.toString(xid);
+ }
+
+ private synchronized long getNextId()
+ {
+ return ++globalIdNumber;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+/**
+ * MBean interface.
+ */
+public interface XidFactoryMBean extends org.jboss.system.ServiceMBean, XidFactoryBase {
+
+ /**
+ * mbean get-set pair for field instance Get the value of instance
+ * @return value of instance
+ */
+ XidFactoryMBean getInstance() ;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/XidImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidImpl.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidImpl.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,351 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * This object encapsulates the ID of a transaction.
+ * This implementation is immutable and always serializable at runtime.
+ *
+ * @see TransactionImpl
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard ???berg</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class XidImpl
+ implements Xid, java.io.Serializable
+{
+ // Constants -----------------------------------------------------
+
+ static final long serialVersionUID = -4175838107150528488L;
+
+ public static final int JBOSS_FORMAT_ID = 0x0101;
+
+ // Static variable -----------------------------------------------
+
+ private static boolean trulyGlobalIdsEnabled = false;
+
+ // Attributes ----------------------------------------------------
+
+ /**
+ * Format id of this instance.
+ * A JBoss-generated Xids has JBOSS_FORMAT_ID in this field.
+ */
+ private final int formatId;
+
+ /**
+ * Global transaction id of this instance.
+ * The coding of this class depends on the fact that this variable is
+ * initialized in the constructor and never modified. References to
+ * this array are never given away, instead a clone is delivered.
+ */
+ private final byte[] globalId;
+
+ /**
+ * Branch qualifier of this instance.
+ * This identifies the branch of a transaction.
+ */
+ private final byte[] branchId;
+
+ /**
+ * Hash code of this instance. This is really a sequence number.
+ */
+ private final int hash;
+
+ /**
+ * Local id of this instance. This field uniquely identifies a
+ * transaction within a given JBoss server.
+ */
+ private final long localId;
+
+ /**
+ * Global id of this instance. This field uniquely identifies a
+ * transaction in a distributed environment.
+ */
+ private final GlobalId trulyGlobalId;
+
+
+ // Static --------------------------------------------------------
+
+ /**
+ * Setter for class variable trulyGlobalIdsEnabled.
+ */
+ public static void setTrulyGlobalIdsEnabled(boolean newValue)
+ {
+ trulyGlobalIdsEnabled = newValue;
+ }
+
+ /**
+ * Getter for class variable trulyGlobalIdsEnabled.
+ */
+ public static boolean getTrulyGlobalIdsEnabled()
+ {
+ return trulyGlobalIdsEnabled;
+ }
+
+ /**
+ * Return a string that describes any Xid instance.
+ */
+ public static String toString(Xid id)
+ {
+ if (id == null)
+ return "[NULL Xid]";
+
+ String s = id.getClass().getName();
+ s = s.substring(s.lastIndexOf('.') + 1);
+ s = s + "[FormatId=" + id.getFormatId()
+ + ", GlobalId=" + new String(id.getGlobalTransactionId()).trim()
+ + ", BranchQual=" + new String(id.getBranchQualifier()).trim()
+ + ((id instanceof XidImpl)
+ ? ", localId=" + LocalId.toString(((XidImpl)id).localId )
+ : "")
+ + "]";
+
+ return s;
+ }
+
+ // Constructors --------------------------------------------------
+
+ /**
+ * Create a new instance.
+ */
+ public XidImpl(int formatId,
+ byte[] globalId, byte[] branchId, int hash, long localId)
+ {
+ this.formatId = formatId;
+ this.globalId = globalId;
+ this.branchId = branchId;
+ this.hash = hash;
+ this.localId = localId;
+ this.trulyGlobalId = (trulyGlobalIdsEnabled)
+ ? new GlobalId(formatId, globalId)
+ : null;
+ }
+
+ /**
+ * Create a new instance with JBOSS_FORMAT_ID.
+ */
+ XidImpl(byte[] globalId, byte[] branchId, int hash, long localId)
+ {
+ this.formatId = JBOSS_FORMAT_ID;
+ this.globalId = globalId;
+ this.branchId = branchId;
+ this.hash = hash;
+ this.localId = localId;
+ this.trulyGlobalId = (trulyGlobalIdsEnabled)
+ ? new GlobalId(JBOSS_FORMAT_ID, globalId, hash)
+ : null;
+ }
+
+ /**
+ * Create a new branch of an existing global transaction ID.
+ *
+ * @param xidImpl The transaction ID to create a new branch of.
+ * @param branchId The ID of the new branch.
+ *
+ */
+ public XidImpl(final XidImpl xidImpl, final byte[] branchId)
+ {
+ this.formatId = xidImpl.formatId;
+ this.globalId = xidImpl.globalId; // reuse array, we never modify it
+ this.branchId = branchId;
+ this.hash = xidImpl.hash;
+ this.localId = xidImpl.localId;
+ this.trulyGlobalId = (trulyGlobalIdsEnabled)
+ ? xidImpl.trulyGlobalId
+ : null;
+ }
+
+ /**
+ * Create a new branch of an existing global transaction.
+ *
+ * @param globalId identifies the transaction to create a new branch of
+ * @param branchId the id that distiguishes the new branch from other
+ * branches of the same transaction
+ * @param localId the local id of the transaction
+ *
+ */
+ public XidImpl(final GlobalId globalId, final byte[] branchId, long localId)
+ {
+ this.formatId = globalId.getFormatId();
+ this.globalId = globalId.getGlobalTransactionId();
+ this.branchId = branchId;
+ this.hash = globalId.hashCode();
+ this.localId = localId;
+ this.trulyGlobalId = (trulyGlobalIdsEnabled)
+ ? globalId
+ : null;
+ }
+
+ // Public --------------------------------------------------------
+
+ // Xid implementation --------------------------------------------
+
+ /**
+ * Return the global transaction id of this transaction.
+ */
+ public byte[] getGlobalTransactionId()
+ {
+ return (byte[])globalId.clone();
+ }
+
+ /**
+ * Return the branch qualifier of this transaction.
+ */
+ public byte[] getBranchQualifier()
+ {
+ if (branchId.length == 0)
+ return branchId; // Zero length arrays are immutable.
+ else
+ return (byte[])branchId.clone();
+ }
+
+ /**
+ * Return the format identifier of this transaction.
+ *
+ * The format identifier augments the global id and specifies
+ * how the global id and branch qualifier should be interpreted.
+ */
+ public int getFormatId()
+ {
+ // The id we return here should be different from all other transaction
+ // implementations.
+ // Known IDs are:
+ // -1: Sometimes used to denote a null transaction id.
+ // 0: OSI TP (javadoc states OSI CCR, but that is a bit misleading
+ // as OSI CCR doesn't even have ACID properties. But OSI CCR and
+ // OSI TP do have the same id format.)
+ // 1: Was used by early betas of jBoss.
+ // 0x0101: The JBOSS_FORMAT_ID we use here.
+ // 0xBB14: Used by JONAS.
+ // 0xBB20: Used by JONAS.
+
+ return formatId;
+ }
+
+ /**
+ * Compare for equality.
+ *
+ * Instances are considered equal if they are both instances of XidImpl,
+ * and if they have the same format id, the same global transaction id
+ * and the same transaction branch qualifier.
+ */
+ public boolean equals(Object obj)
+ {
+ if(obj==this)
+ return true;
+ if (obj instanceof XidImpl) {
+ XidImpl other = (XidImpl)obj;
+
+ if (formatId != other.formatId ||
+ globalId.length != other.globalId.length ||
+ branchId.length != other.branchId.length)
+ return false;
+
+ for (int i = 0; i < globalId.length; ++i)
+ if (globalId[i] != other.globalId[i])
+ return false;
+
+ for (int i = 0; i < branchId.length; ++i)
+ if (branchId[i] != other.branchId[i])
+ return false;
+
+ return true;
+ }
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return hash;
+ }
+
+ public String toString()
+ {
+ return toString(this);
+ }
+
+ // Methods specific to JBoss Xid implementation ------------------
+
+ /**
+ * Return the local id that identifies this transaction
+ * within the JBoss server.
+ */
+ public long getLocalIdValue()
+ {
+ return localId;
+ }
+
+ /**
+ * Return a LocalId instance that identifies this transaction
+ * within the JBoss server.
+ */
+ public LocalId getLocalId()
+ {
+ return new LocalId(localId);
+ }
+
+ /**
+ * Return a GlobalId instance that identifies this transaction
+ * in a distributed environment.
+ */
+ public GlobalId getTrulyGlobalId()
+ {
+ return trulyGlobalId;
+ }
+
+ /**
+ * Compare for same transaction.
+ *
+ * Instances represent the same transaction if they have the same
+ * format id and global transaction id.
+ */
+ public boolean sameTransaction(XidImpl other)
+ {
+ if(other == this)
+ return true;
+ if (formatId != other.formatId ||
+ globalId.length != other.globalId.length)
+ return false;
+
+ for (int i = 0; i < globalId.length; ++i)
+ if (globalId[i] != other.globalId[i])
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Return the global transaction id of this transaction.
+ * Unlike the {@link #getGlobalTransactionId()} method, this one
+ * returns a reference to the global id byte array that may <em>not</em>
+ * be changed.
+ */
+ public byte[] getInternalGlobalTransactionId()
+ {
+ return globalId;
+ }
+
+}
+
Added: trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,63 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+import javax.transaction.Transaction;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A NOOP implementation of transaction integrity.<p>
+ *
+ * Implementations should extend this for future compatibility.
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class AbstractTransactionIntegrity implements TransactionIntegrity
+{
+ /** The log */
+ protected Logger log = Logger.getLogger(getClass());
+
+ public void checkTransactionIntegrity(TransactionImpl transaction)
+ {
+ // Do nothing
+ }
+
+ /**
+ * Mark the transaction for rollback
+ *
+ * @param transaction the transacton
+ */
+ protected void markRollback(Transaction transaction)
+ {
+ try
+ {
+ transaction.setRollbackOnly();
+ }
+ catch (Exception e)
+ {
+ log.warn("Unable to mark the transaction for rollback " + transaction, e);
+ }
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+import org.jboss.system.ServiceMBeanSupport;
+
+/**
+ * An MBean to configure the FailIncomplete policy.
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class FailIncompleteTransaction extends ServiceMBeanSupport
+ implements FailIncompleteTransactionMBean
+{
+ public TransactionIntegrity createTransactionIntegrity()
+ {
+ return new FailIncompleteTransactionIntegrity();
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+import java.util.Set;
+
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A transaction integrity that rolls back the transaction
+ * if there are other threads associated with it.
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class FailIncompleteTransactionIntegrity extends AbstractTransactionIntegrity
+{
+ public void checkTransactionIntegrity(TransactionImpl transaction)
+ {
+ // Assert the only thread is ourselves
+ Set threads = transaction.getAssociatedThreads();
+ String rollbackError = null;
+ synchronized (threads)
+ {
+ if (threads.size() > 1)
+ rollbackError = "Too many threads " + threads + " associated with transaction " + transaction;
+ else if (threads.size() != 0)
+ {
+ Thread other = (Thread) threads.iterator().next();
+ Thread current = Thread.currentThread();
+ if (current.equals(other) == false)
+ rollbackError = "Attempt to commit transaction " + transaction + " on thread " + current +
+ " with other threads still associated with the transaction " + other;
+ }
+ }
+ if (rollbackError != null)
+ {
+ log.error(rollbackError, new IllegalStateException("STACKTRACE"));
+ markRollback(transaction);
+ }
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,29 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+import org.jboss.system.ServiceMBean;
+
+public interface FailIncompleteTransactionMBean extends ServiceMBean, TransactionIntegrityFactory
+{
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,51 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A policy that checks a transaction before allowing a commit
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public interface TransactionIntegrity
+{
+ /**
+ * Checks whether a transaction can be committed.<p>
+ *
+ * The policy is allowed to wait, e.g. if there
+ * are other threads still associated with the transaction.<p>
+ *
+ * This method is invoked before any transaction synchronizations'
+ * beforeCompletions.<p>
+ *
+ * This policy should not invoke any methods that change the
+ * state of the transaction other than <code>setRollbackOnly()</code>
+ * to force a rollback or registering a transaction synchronization.
+ *
+ * @param transaction the transaction
+ * @throws SecurityException if a commit is not allowed from this context
+ */
+ void checkTransactionIntegrity(TransactionImpl transaction);
+}
Added: trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,38 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.integrity;
+
+/**
+ * A transaction integrity factory
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public interface TransactionIntegrityFactory
+{
+ /**
+ * Create a new transaction integrity policy
+ *
+ * @return the transaction integrity policy
+ */
+ TransactionIntegrity createTransactionIntegrity();
+}
Added: trunk/transaction/src/main/org/jboss/tm/package.html
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/package.html (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/package.html 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+ <head>
+ <!--
+ JBoss, Home of Professional Open Source
+ Copyright 2005, JBoss Inc., and individual contributors as indicated
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ -->
+ <!-- $Id: package.html 38414 2005-11-23 07:55:45Z adrian $ -->
+ </head>
+
+ <body bgcolor="white">
+ <p><em>The default Transaction Manager implementation</em>.
+
+
+ <h2>Package Specification</h2>
+ <ul>
+
+The classes in this package belong may be grouped into two:
+<ul>
+ <li><em>Application server interface</em>. This group consists of the
+ two classes {@link: TransactionPropagationContextFactory} and
+ {@link: TransactionPropagationContextImporter}. Together these two
+ classes form the JBoss-specific Communication Resource Manager (CRM).
+ </li>
+ <li><em>The default Transaction Service</em>. This is the rest of the
+ classes in this package. It is a very fast JTA implementation with
+ two drawbacks:
+ <ul>
+ <li>
+ It has no transactional logging or recovery after server crashes.
+ </li>
+ <li>
+ Transactions cannot be propagated from one server VM to another.
+ But the thin-client UserTransaction service works fine, in case
+ you just want to control transactions on a single server from your
+ stand-alone clients.
+ </li>
+ </ul>
+ </li>
+
+</ul>
+ </ul>
+
+ <h2>The JBoss-specific Communication Resource Manager</h2>
+ <p>A CRM is responsible for converting local transactions from/to a
+ form that can be serialized so it can be passed over the wire to
+ another Java VM.</p>
+ <p>The JTA specification does not define the CRM, so we have implemented
+ our own.</p>
+ <p>The J2EE specification suggests that the CRM used is the OTS
+ CosTSPortability CRM, but that depends heavily on CORBA, and JBoss is
+ not dependent on any particular object request broker technology, so
+ we do not use that.</p>
+ <p>The advantage of using our own CRM implementation instead of
+ CosTSPortablilty is twofold:
+ <ul>
+ <li>
+ JBoss does not depend on CORBA, and will run just fine without it.
+ </li>
+ <li>
+ JBoss is able to use JTA implementations that are completely
+ incompatible with OTS.
+ </li>
+ </ul>
+
+ <h2>Adapting an existing JTA implementation for JBoss</h2>
+ <p>In JBoss everything is a MBean. Implementing your JTA implementation
+ as an MBean makes it simpler to manage it in a standardized way.</p>
+ Since your JTA implementation is a service you probably want to make
+ your MBean implemementation extend from
+ <code>org.jboss.system.ServiceMBeanSupport</code>.</p>
+ When running in the server, your MBean should publish three objects
+ in JNDI:
+ <ul>
+ <li>
+ <em>java:/TransactionManager</em>
+ When this JNDI name is looked up, it should return an object instance
+ that implements <code>javax.transaction.TransactionManager</code>.
+ This is the standard JTA transaction manager that the JBoss server
+ uses to control transactions.
+ </li>
+ <li>
+ <em>java:/TransactionPropagationContextImporter</em>
+ When this JNDI name is looked up, it should return an object instance
+ that implements
+ <code>org.jboss.tm.TransactionPropagationContextImporter</code>.
+ This is used by the JBoss communication layers to convert the wire
+ representation of a transaction to a
+ <code>javax.transaction.Transaction</code> instance that can be used
+ locally.
+ </li>
+ <li>
+ <em>java:/TransactionPropagationContextFactory</em>
+ When this JNDI name is looked up, it should return an object instance
+ that implements
+ <code>org.jboss.tm.TransactionPropagationContextFactory</code>.
+ This is used by the JBoss communication layers to convert the wire
+ representation of a <code>javax.transaction.Transaction</code>
+ instance to a form that can be passed over the wire to another JBoss
+ server.
+ </li>
+ </ul>
+ <p>See the implementation of
+ <code>org.jboss.tm.TransactionManagerService</code> for an example of
+ such a MBean. Please note that the three names your MBean should publish
+ in JNDI need not refer to the same object instance as the
+ <code>TransactionManagerService</code> does.</p>
+
+ <p>Please note that the transaction propagation context (TPC) used by
+ the CRM interfaces <code>TransactionPropagationContextImporter</code>
+ and <code>TransactionPropagationContextFactory</code> is of type
+ <code>java.lang.Object</code>. This allows for the widest range of
+ possible TPC implementations. For your TPC to work with the JBoss
+ JRMP transport, it should be Java-serializable at runtime.</p>
+
+ <h2>Related Documentation</h2>
+ <ul>
+ <li>
+ <a href="http://java.sun.com/products/jta/">The JTA specification</a>
+ </li>
+ <li>
+ <a href="http://www.omg.org/technology/documents/formal/transaction_service.htm">The OTS specification</a>
+ </li>
+ </ul>
+
+ <h2>Package Status</h2>
+ <ul>
+ <li><font color="green"><b>STABLE</b></font>
+ </ul>
+
+ <!-- Put @see and @since tags down here. -->
+
+ </body>
+</html>
+
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,413 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Class that encapsulates a log file opened for writing. It provides
+ * <code>write</code> methods that append log records to the log file.
+ * A <code>BatchLog</code> instance belongs to some <code>BatchWriter</code>
+ * and its write methods are called only from the <code>BatchWriter</code>
+ * thread.
+ * <p>
+ * To avoid changes in its metadata, the log file is created with a fixed
+ * length, which should never change until the file is closed.
+ * <p>
+ * A newly created log file is clean: it contains a header object followed
+ * by a sequence of null bytes that takes the entire lenght of the file. Log
+ * files are reusable, but they must be cleaned up for reuse. Restarting a
+ * <code>BatchLog</code> means overwriting with null bytes all the data that
+ * follows the header and returning the <code>BatchLog</code> to the pool of
+ * clean <code>BatchLog</code> instances maintained by a
+ * <code>BatchWriter</code>.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class BatchLog
+ implements TxCompletionHandler
+{
+ /**
+ * Class <code>Logger</code>, for trace messages.
+ */
+ private static Logger errorLog = Logger.getLogger(BatchLog.class);
+
+ /**
+ * Length of the null-filled buffer used to clean log file.
+ */
+ private static final int CLEAN_LENGTH = 16 * 1024;
+
+ /**
+ * Buffer used to fill the log file with null bytes.
+ */
+ private static byte[] nulls = new byte[CLEAN_LENGTH];
+
+ /**
+ * The log file.
+ */
+ private File logFile;
+
+ /**
+ * A <code>RandomAccessFile</code> view of the log file.
+ */
+ private RandomAccessFile os;
+
+ /**
+ * The <code>RandomAccessFile</code>'s underlying <code>FileChannel</code>.
+ */
+ private FileChannel channel;
+
+ /**
+ * Number of transactions logged. Counts commit, multi-TM commit, prepare,
+ * and JCA prepare records written out to the log. Only the
+ * <code>BatchWriter</code> thread updates this field, which is declared
+ * as volatile so that its updates are seen by any threads that call
+ * <code>handleTxCompletion</code>.
+ */
+ private volatile int numLoggedTransactions;
+
+ /**
+ * Number of end records written out to the log. Only the
+ * <code>BatchWriter</code> thread updates this field, which is declared
+ * as volatile so that its updates are seen by any threads that call
+ * <code>handleTxCompletion</code>.
+ */
+ private volatile int numEndRecords;
+
+ /**
+ * Number of completed transactions that need no end records. Access to this
+ * field is synchronized, as it is concurrently updated via calls to
+ * <code>handleTxCompletion</code>.
+ */
+ private int numLocalTransactionsCompleted;
+
+ /**
+ * Indicates that this <code>BatchLog</code> will not receive additional
+ * records for new transactions. This <code>BatchLog</code> should be
+ * cleaned up and returned to the <code>BatchWriter</code>'s pool of clean
+ * <code>BatchLog</code> instances as soon as every outstanding transaction
+ * signals its completion either by writing out an end record or by invoking
+ * <code>handleTxCompletion</code>.
+ */
+ private boolean markedForRestart;
+
+ /**
+ * Header object written to the beginning of the log file via Java
+ * serialization. It is preceded by four bytes (an int value) with the
+ * length of the serialized header.
+ */
+ private Object header;
+
+ /**
+ * Log file position that follows the four bytes with the header lenght and
+ * the header object. (Its value is headerLenght + 4.)
+ */
+ private long topFp;
+
+ /**
+ * The <code>BatchWriter</code> that owns this <code>BatchLog</code>.
+ */
+ private BatchWriter writer;
+
+ /**
+ * Auxiliary buffer used to fill up with null bytes the part of the file
+ * that follows the header.
+ */
+ private ByteBuffer cleanBuffer = ByteBuffer.wrap(nulls);
+
+ /**
+ * Constructs a new <code>BatchLog</code>.
+ *
+ * @param writer the <code>BatchWriter</code> that will own
+ * the new <code>BatchLog</code>
+ * @param header an object to be written at the beginning of the log file
+ * @param dir the directory in which the log file will be created
+ * @param fileSize the fixed size of the log file
+ * @throws IOException
+ */
+ BatchLog(BatchWriter writer, Object header, File dir, int fileSize)
+ throws IOException
+ {
+ this.writer = writer;
+ this.header = header;
+ logFile = File.createTempFile("TX_RECOVERY_LOG", ".log", dir);
+ os = new RandomAccessFile(logFile, "rw");
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(header);
+ byte[] bytes = baos.toByteArray();
+
+ os.setLength(fileSize);
+ os.writeInt(bytes.length);
+ os.write(bytes);
+
+ channel = os.getChannel();
+ // Force also metadata. We do this just once, as the file size is fixed.
+ channel.force(true);
+ topFp = channel.position();
+
+ cleanUpLogFile();
+ }
+
+ /**
+ * Gets the <code>BatchWriter</code> that owns this <code>BatchLog</code>.
+ *
+ * @return the <code>BatchWriter</code> that issues write calls on this
+ * this <code>BatchLog</code>.
+ */
+ BatchWriter getBatchWriter()
+ {
+ return writer;
+ }
+
+ /**
+ * Gets this <code>BatchLog</code>'s current position, which is also the
+ * number of currently used bytes of its log file.
+ *
+ * @return the offset from the beginning of the log file, in bytes,
+ * at which the next write will happen.
+ * @throws IOException
+ */
+ int getPosition()
+ throws IOException
+ {
+ return (int) channel.position();
+ }
+
+ /**
+ * Gets the name of the underlying log file.
+ *
+ * @return the name of this <code>BatchLog</code>'s log file.
+ */
+ String getFilename()
+ {
+ return logFile.getName();
+ }
+
+ /**
+ * Writes one record at the current position of this <code>BatchLog</code>.
+ *
+ * @param record a buffer with the record to be written
+ * @param isEndRecord true if the record is an end record and false otherwise
+ * @throws IOException
+ */
+ void write(ByteBuffer record, boolean isEndRecord)
+ throws IOException
+ {
+ channel.write(record);
+ if (isEndRecord)
+ {
+ numEndRecords++;
+
+ synchronized (this)
+ {
+ if (markedForRestart == true &&
+ numLoggedTransactions ==
+ numLocalTransactionsCompleted + numEndRecords)
+ {
+ writer.restartBatchLog(this);
+ }
+ }
+ }
+ else
+ {
+ channel.force(false);
+ numLoggedTransactions++;
+ }
+ }
+
+ /**
+ * Writes a sequence of records to this <code>BatchLog</code>.
+ * The sequence of records is taken from the given array of buffers,
+ * starting at the specified offset, and it is written at the current
+ * position of this <code>BatchLog</code>.
+ *
+ * @param records an array of buffers containing records that should be
+ * written to the log
+ * @param offset the index of the first record of the <code>records</code>
+ * array that should be written to the log
+ * @param length the number of records that should be written to the log
+ * @param numTransactionRecords specifies how many of the <code>lenght</code>
+ * records are records for new transactions (commit, multi-TM
+ * commit, prepare and JCA prepare records). The remaining
+ * <code>length - numTransactionRecords</code> records are
+ * end-of-transaction records.
+ * @throws IOException
+ */
+ void write(ByteBuffer[] records,
+ int offset,
+ int length,
+ int numTransactionRecords)
+ throws IOException
+ {
+ channel.write(records, offset, length);
+
+ if (numTransactionRecords > 0)
+ {
+ channel.force(false);
+ numLoggedTransactions += numTransactionRecords;
+ }
+
+ if (numTransactionRecords < length)
+ {
+ numEndRecords += (length - numTransactionRecords);
+
+ synchronized (this)
+ {
+ if (markedForRestart == true &&
+ numLoggedTransactions ==
+ numLocalTransactionsCompleted + numEndRecords)
+ {
+ writer.restartBatchLog(this);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Signals the end of the two-phase commit protocol for a committed
+ * transaction that does not need an end record to be logged. This method
+ * should be invoked when the second phase of the two-phase commit protocol
+ * completes successfully.
+ *
+ * @param localTransactionId the local id of the completed transaction.
+ */
+ public void handleTxCompletion(long localTransactionId)
+ {
+ synchronized (this)
+ {
+ numLocalTransactionsCompleted++;
+ if (markedForRestart == true &&
+ numLoggedTransactions ==
+ numLocalTransactionsCompleted + numEndRecords)
+ {
+ writer.restartBatchLog(this);
+ }
+ }
+ }
+
+ /**
+ * Marks this <code>BatchLog</code> for restart. The restart will occur
+ * only when there are no more outstanding transactions, i.e, when
+ * <code>numLocalTransactionsCompleted + numEndRecords</code> reaches
+ * <code>numLoggedTransactions</code>.
+ */
+ void markForRestart()
+ {
+ synchronized (this)
+ {
+ markedForRestart = true;
+ if (numLoggedTransactions ==
+ numLocalTransactionsCompleted + numEndRecords)
+ {
+ writer.restartBatchLog(this);
+ }
+ }
+ }
+
+ /**
+ * Restarts this <code>BatchLog</code>. Overwrites with null bytes all
+ * log records in the log file, then returns the <code>BatchLog</code>
+ * to its <code>BatchWriter</code>s pool of clean <code>BatchLog</code>
+ * instances.
+ * <p>
+ * Only the <code>LogRestarter</code> calls this method.
+ *
+ * @throws IOException
+ */
+ void restart()
+ throws IOException
+ {
+ // TODO may have to rewrite header when we start recording XAResource
+ // handles in it as a new one may be deployed/undeployed
+ channel.position(topFp);
+ cleanUpLogFile();
+ writer.getNextLogs().add(this);
+ }
+
+ /**
+ * Overwrites with null bytes all log records in the log file, without
+ * changing the size of the file.
+ */
+ void cleanUpLogFile()
+ throws IOException
+ {
+ numLoggedTransactions = 0;
+ numLocalTransactionsCompleted = 0;
+ numEndRecords = 0;
+ markedForRestart = false;
+
+ // Overwites the remainder of the log file with null bytes.
+ cleanBuffer.limit(cleanBuffer.capacity());
+ while (channel.position() <= channel.size() - cleanBuffer.limit())
+ {
+ cleanBuffer.rewind();
+ channel.write(cleanBuffer);
+ }
+ cleanBuffer.limit((int) (channel.size() - channel.position()));
+ cleanBuffer.rewind();
+ channel.write(cleanBuffer);
+ channel.force(false);
+
+ channel.position(topFp);
+ }
+
+ /**
+ * Closes this <code>BatchLog</code>. If there are no outstanding
+ * transactions then all log records in the log file are erased.
+ */
+ void close()
+ {
+ errorLog.info("Closing transaction log " + getFilename() +
+ ", numLoggedTransactions=" + numLoggedTransactions +
+ ", numLocalTransactionsCompleted=" +
+ numLocalTransactionsCompleted +
+ ", numEndRecords=" + numEndRecords);
+ try
+ {
+ if (numLoggedTransactions ==
+ numLocalTransactionsCompleted + numEndRecords)
+ {
+ channel.position(topFp);
+ channel.truncate(topFp);
+ }
+ os.close();
+ }
+ catch (IOException e)
+ {
+ errorLog.error("Error closing transaction log " + getFilename(), e);
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,454 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * Simple implementation of <code>RecoveryLogReader</code> used at recovery
+ * time. The <code>BatchRecoveryLogger</code>'s implementation of method
+ * <code>getRecoveryLogs()</code> instantiates
+ * <code>BatchRecoveryLogReader</code>s for the existing recovery log files.
+ * It returns an array containing those readers, which the recovery manager
+ * uses to get the information in the log files.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class BatchRecoveryLogReader
+ implements RecoveryLogReader
+
+{
+ /** Class <code>Logger</code>, for trace messages. */
+ private static Logger errorLog =
+ Logger.getLogger(BatchRecoveryLogReader.class);
+
+ /** The log file read by this reader. */
+ private File logFile;
+
+ /** Xid factory for converting local transaction ids into global ids. */
+ private XidFactoryBase xidFactory;
+
+ /**
+ * Constructs a <code>BatchRecoveryLogReader</code>.
+ *
+ * @param logFile the log file that will be read by the reader
+ * @param xidFactory Xid factory that the reader will use to convert local
+ * transaction ids into global ids.
+ */
+ BatchRecoveryLogReader(File logFile, XidFactoryBase xidFactory)
+ {
+ this.logFile = logFile;
+ this.xidFactory = xidFactory;
+ }
+
+ /**
+ * Reads the header object written out to the beginning of the log file via
+ * Java serialization.
+ *
+ * @param dis a <code>DataInputStream</code> view of the log file
+ * @return the header object read from the log file.
+ * @throws IOException if there is an error reading the header object
+ * @throws ClassNotFoundException if the class of the header object is not
+ * available.
+ */
+ private Object readHeaderObject(DataInputStream dis)
+ throws IOException,
+ ClassNotFoundException
+ {
+ int num = dis.readInt();
+ byte[] bytes = new byte[num];
+ dis.read(bytes);
+ ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bais);
+ return ois.readObject();
+ }
+
+ // RecoveryLogReader implementation ------------------------------
+
+ /**
+ * Gets the name of the underlying log file.
+ *
+ * @return the name of the log file.
+ */
+ public String getLogFileName()
+ {
+ return logFile.toString();
+ }
+
+ /**
+ * Gets the branch qualifier string stored in the log file header.
+ *
+ * @return the branch qualifier read from the log file header.
+ */
+ public String getBranchQualifier()
+ {
+ FileInputStream fis;
+ DataInputStream dis;
+
+ try
+ {
+ fis = new FileInputStream(logFile);
+ dis = new DataInputStream(fis);
+ try
+ {
+ return (String) readHeaderObject(dis);
+ }
+ finally
+ {
+ fis.close();
+ }
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Recovers transaction information from the log file.
+ *
+ * @param committedSingleTmTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * committed single-TM transaction logged to the log file
+ * @param committedMultiTmTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * committed multi-TM transaction that has not yet completed the
+ * second phase of the 2PC protocol when the server crashed
+ * @param inDoubtTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * foreign transaction that arrived at the server via DTM/OTS
+ * context propagation and was in the in-doubt state (i.e.,
+ * replied to prepare with a commit vote but has not yet received
+ * information on the transaction outcome) when the server crashed
+ * @param inDoubtJcaTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * foreign transaction that arrived at the server via JCA
+ * transaction inflow and was in the in-doubt state (i.e., replied
+ * to prepare with a commit vote and was waiting for information
+ * on the transaction outcome) when the server crashed.
+ */
+ public void recover(List committedSingleTmTransactions,
+ List committedMultiTmTransactions,
+ List inDoubtTransactions,
+ List inDoubtJcaTransactions)
+ {
+ Map activeMultiTmTransactions = new HashMap();
+ FileInputStream fis;
+ DataInputStream dis;
+ CorruptedLogRecordException corruptedLogRecordException = null;
+
+ try
+ {
+ fis = new FileInputStream(logFile);
+ dis = new DataInputStream(fis);
+
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ try
+ {
+ readHeaderObject(dis); // branch qualifier
+
+ if (fis.available() < LogRecord.FULL_HEADER_LEN)
+ return;
+
+ FileChannel channel = fis.getChannel();
+ ByteBuffer buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+ channel.read(buf);
+
+ LogRecord.Data data;
+ int len = LogRecord.getNextRecordLength(buf, 0);
+
+ while (len > 0)
+ {
+ buf = ByteBuffer.allocate(len + LogRecord.FULL_HEADER_LEN);
+ if (channel.read(buf) < len)
+ {
+ errorLog.info("Unexpected end of file in transaction log file " +
+ logFile.getName());
+ break;
+ }
+ buf.flip();
+ data = new LogRecord.Data();
+ try
+ {
+ LogRecord.getData(buf, len, data);
+ }
+ catch (CorruptedLogRecordException e)
+ {
+ // Save the exception only if no previous exception is saved.
+ if (corruptedLogRecordException == null)
+ corruptedLogRecordException = e;
+ long corruptedRecordPos =
+ channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
+ long nextPos = scanForward(corruptedRecordPos + 1);
+ if (nextPos == 0)
+ {
+ errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " +
+ logFile.getName());
+ break;
+ }
+ else
+ {
+ errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " +
+ logFile.getName() + ". Skipping " +
+ (nextPos - corruptedRecordPos) + " bytes" +
+ ". Disabling presumed rollback.");
+ channel.position(nextPos);
+ buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+ channel.read(buf);
+ len = LogRecord.getNextRecordLength(buf, 0);
+ corruptedLogRecordException.disablePresumedRollback = true;
+ continue;
+ }
+ }
+ switch (data.recordType)
+ {
+ case LogRecord.TX_COMMITTED:
+ data.globalTransactionId =
+ xidFactory.localIdToGlobalId(data.localTransactionId);
+ committedSingleTmTransactions.add(data);
+ break;
+ case LogRecord.MULTI_TM_TX_COMMITTED:
+ data.globalTransactionId =
+ xidFactory.localIdToGlobalId(data.localTransactionId);
+ // fall through
+ case LogRecord.TX_PREPARED:
+ case LogRecord.JCA_TX_PREPARED:
+ activeMultiTmTransactions.put(new Long(data.localTransactionId),
+ data);
+ break;
+ case LogRecord.TX_END:
+ activeMultiTmTransactions.remove(
+ new Long(data.localTransactionId));
+ break;
+ default:
+ errorLog.warn("INVALID TYPE IN LOG RECORD.");
+ break;
+ }
+ try
+ {
+ len = LogRecord.getNextRecordLength(buf, len);
+ }
+ catch (CorruptedLogRecordException e)
+ {
+ // Save the exception only if no previous exception is saved.
+ if (corruptedLogRecordException == null)
+ corruptedLogRecordException = e;
+ long corruptedRecordPos =
+ channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
+ long nextPos = scanForward(corruptedRecordPos + 1);
+ if (nextPos == 0)
+ {
+ errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " +
+ logFile.getName());
+ len = 0;
+ }
+ else
+ {
+ errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " +
+ logFile.getName() + ". Skipping " +
+ (nextPos - corruptedRecordPos) + " bytes" +
+ ". Disabling presumed rollback.");
+ channel.position(nextPos);
+ buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+ channel.read(buf);
+ len = LogRecord.getNextRecordLength(buf, 0);
+ corruptedLogRecordException.disablePresumedRollback = true;
+ }
+ }
+ }
+
+ Iterator iter = activeMultiTmTransactions.values().iterator();
+ while (iter.hasNext())
+ {
+ data = (LogRecord.Data) iter.next();
+
+ switch (data.recordType)
+ {
+ case LogRecord.MULTI_TM_TX_COMMITTED:
+ committedMultiTmTransactions.add(data);
+ break;
+ case LogRecord.TX_PREPARED:
+ inDoubtTransactions.add(data);
+ break;
+ case LogRecord.JCA_TX_PREPARED:
+ inDoubtJcaTransactions.add(data);
+ break;
+ default:
+ errorLog.warn("INCONSISTENT STATE.");
+ break;
+ }
+ }
+
+ if (corruptedLogRecordException != null)
+ throw corruptedLogRecordException;
+ }
+ catch (IOException e)
+ {
+ errorLog.warn("Unexpected exception in recover:", e);
+ }
+ catch (ClassNotFoundException e)
+ {
+ errorLog.warn("Unexpected exception in recover:", e);
+ }
+ try
+ {
+ fis.close();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Removes the log file.
+ */
+ public void finishRecovery()
+ {
+ logFile.delete();
+ }
+
+ /**
+ * Scans the log file for a valid log record. This is a helper method for
+ * log file corruption handling.
+ *
+ * @param pos the file position where the forward scan should start.
+ * @return the file position in which a valid log record was found, or
+ * 0 if end of file was reached and no valid log record was found.
+ */
+ private long scanForward(long pos)
+ {
+ errorLog.trace("entering scanForward");
+ RandomAccessFile file = null;
+
+ try
+ {
+ file = new RandomAccessFile(logFile, "r");
+
+ while (pos + LogRecord.FULL_HEADER_LEN < logFile.length())
+ {
+ if (match(file, pos, LogRecord.HEADER))
+ {
+ errorLog.trace("scanForward: match at pos=" + pos);
+ file.seek(pos + LogRecord.HEADER_LEN);
+ short recLen = file.readShort();
+ errorLog.trace("scanForward: recLen=" + recLen);
+ if (pos + LogRecord.FULL_HEADER_LEN + recLen < logFile.length())
+ {
+ byte[] buf = new byte[recLen];
+ file.seek(pos + LogRecord.FULL_HEADER_LEN);
+ file.read(buf, 0, recLen);
+ if (LogRecord.hasValidChecksum(buf))
+ {
+ errorLog.trace("scanForward: returning " + pos);
+ return pos;
+ }
+ else
+ {
+ errorLog.trace("scanForward: " +
+ "bad checksum in record at pos=" + pos);
+ pos += LogRecord.HEADER_LEN;
+ }
+ }
+ else
+ pos =+ LogRecord.HEADER_LEN;
+ }
+ else
+ pos++;
+ }
+ errorLog.trace("scanForward: returning 0");
+ return 0;
+ }
+ catch (FileNotFoundException e)
+ {
+ errorLog.warn("Unexpected exception in scanForward:", e);
+ return 0;
+ }
+ catch (IOException e)
+ {
+ errorLog.warn("Unexpected exception in scanForward:", e);
+ return 0;
+ }
+ finally
+ {
+ if (file != null)
+ {
+ try
+ {
+ file.close();
+ }
+ catch (IOException e)
+ {
+ errorLog.warn("Unexpected exception in scanForward:", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the byte sequence that starts at given position of a
+ * file matches a given byte array.
+ *
+ * @param file a random access file open for reading
+ * @param pos the file position of the byte sequence to be compared against
+ * the <code>pattern</code> byte array
+ * @param pattern the byte pattern to match.
+ * @return true if the pattern appears at the specified position of the
+ * file, and false otherwise/
+ * @throws IOException if there is a problem reading the file.
+ */
+ private static boolean match(RandomAccessFile file, long pos, byte[] pattern)
+ throws IOException
+ {
+ for (int i = 0; i < pattern.length; i++)
+ {
+ file.seek(pos + i);
+ if (file.readByte() != pattern[i])
+ return false;
+ }
+ return true;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,403 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * This <code>RecoveryLogger</code> implementation
+ * uses <code>BatchWriter</code>s that batch log write requests
+ * in order to minimize disk forcing activity. A
+ * <code>BatchRecoveryLogger</code> instance is the "main object" of the
+ * the recovery logger service, which is simply an MBean wrapper for that
+ * instance.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class BatchRecoveryLogger
+ implements RecoveryLogger
+{
+ /** Array of names of directories that contain recovery log files. */
+ private String[] stringDirectoryList;
+
+ /** Array of directories that contain recovery log files. */
+ private File[] directoryList;
+
+ /** The constant size of a recovery log file. */
+ private int logFileSize;
+
+ /** Array of recovery log files found at recovery time. */
+ private File[] existingRecoveryLogFiles;
+
+ /** Array of log writers (one per directory that contains log files). */
+ private BatchWriter[] writers;
+
+ /** Number of log writers (number of directories that contain log files). */
+ private int numWriters;
+
+ /** Sequential number used to choose a log writer */
+ private volatile int seqNo = 0;
+
+ /** Name of the directory that contains heuristic status log files. */
+ private String heuristicStatusLogDirectoryName;
+
+ /** Directory that contain heuristic status log files. */
+ private File heuristicStatusLogDirectory;
+
+ /** Array of heuristic status log files found at recovery time. */
+ private File[] existingHeuristicStatusLogFiles;
+
+ /** Heuristic status log writer. */
+ private HeuristicStatusLog heuristicStatusLogger;
+
+ /** The server's Xid factory. */
+ private XidFactoryBase xidFactory;
+
+ /** For asynchronously restarting <code>BatchLog</code> instances. */
+ private LogRestarter logCleaner;
+
+ // Package protected methods -------------------------------------
+ // (The BatchRecoveryLoggerService calls these methods.)
+
+ /*
+ * Gets the names of directories that contain recovery log files.
+ *
+ * @return an array of names of directories that contain recovery log
+ * files.
+ *
+ */
+ String[] getDirectoryList()
+ {
+ return stringDirectoryList;
+ }
+
+ /**
+ * Sets the names of directories that contain recovery log files.
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ *
+ * @param directoryList array of names of directories that contain recovery
+ * log files.
+ *
+ */
+ public void setDirectoryList(String[] directoryList)
+ {
+ this.stringDirectoryList = directoryList;
+ File[] list = new File[directoryList.length];
+ for (int i = 0; i < directoryList.length; i++)
+ {
+ list[i] = new File(directoryList[i]);
+ list[i] = list[i].getAbsoluteFile();
+ if (!list[i].exists())
+ {
+ if (!list[i].mkdirs())
+ {
+ throw new RuntimeException("Unable to create recovery directory: "
+ + directoryList[i]);
+ }
+ }
+ }
+ this.directoryList = list;
+ }
+
+ /**
+ * Gets the constant size of a log file.
+ *
+ * @return the constans size of a log file, in bytes.
+ */
+ int getLogFileSize()
+ {
+ return logFileSize;
+ }
+
+ /**
+ * Sets the constant size of a log file.
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ *
+ * @param logFileSize the constant size of a log file, in bytes.
+ */
+ public void setLogFileSize(int logFileSize)
+ {
+ this.logFileSize = logFileSize;
+ }
+
+ /**
+ * Gets the name of the directory that contains heuristic status log files.
+ *
+ * @return the name of the directory that contains heuristic status log
+ * files.
+ */
+ String getHeuristicStatusLogDirectory()
+ {
+ return heuristicStatusLogDirectoryName;
+ }
+
+ /**
+ * Sets the name of the directory that contains heuristic status log files.
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ *
+ * @param heuristicStatusLogDirectoryName the name of the directory that
+ * contains heuristic status log files.
+ */
+ public void setHeuristicStatusLogDirectory(String heuristicStatusLogDirectoryName)
+ {
+ this.heuristicStatusLogDirectoryName = heuristicStatusLogDirectoryName;
+ heuristicStatusLogDirectory = new File(heuristicStatusLogDirectoryName);
+ heuristicStatusLogDirectory =
+ heuristicStatusLogDirectory.getAbsoluteFile();
+ if (!heuristicStatusLogDirectory.exists())
+ {
+ if (!heuristicStatusLogDirectory.mkdirs())
+ {
+ throw new RuntimeException("Unable to create heuristic status " +
+ "log directory: "
+ + heuristicStatusLogDirectoryName);
+ }
+ }
+ }
+
+ /**
+ * Gets the Xid factory.
+ *
+ * @return the Xid factory.
+ */
+ XidFactoryBase getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * Sets the Xid factory
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ *
+ * @param xidFactory the Xid factory.
+ */
+ public void setXidFactory(XidFactoryBase xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ /**
+ * Starts this <code>BatchRecoveryLoggger</code>.
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ */
+ public void start() throws Exception
+ {
+ ArrayList list = new ArrayList();
+ for (int i = 0; i < directoryList.length; i++)
+ {
+ File dir = directoryList[i];
+ File[] files = dir.listFiles();
+ for (int j = 0; j < files.length; j++)
+ {
+ list.add(files[j]);
+ }
+ }
+ existingRecoveryLogFiles = (File[]) list.toArray(new File[list.size()]);
+
+ logCleaner = new LogRestarter();
+ new Thread(logCleaner, "Log file cleaner").start();
+
+ writers = new BatchWriter[directoryList.length];
+ String branchQualifier = xidFactory.getBranchQualifier();
+ for (int i = 0; i < directoryList.length; i++)
+ {
+ writers[i] = new BatchWriter(branchQualifier, logFileSize / 128,
+ directoryList[i], logFileSize, logCleaner);
+ new Thread(writers[i], "Batch Recovery Log " + i).start();
+ }
+ numWriters = writers.length;
+
+ existingHeuristicStatusLogFiles = heuristicStatusLogDirectory.listFiles();
+ heuristicStatusLogger =
+ new HeuristicStatusLog(heuristicStatusLogDirectory);
+ }
+
+ /**
+ * Stops this <code>BatchRecoveryLoggger</code>.
+ * <p />
+ * This method used to be package protected. It was changed to public just
+ * for testing purposes.
+ */
+ public void stop() throws Exception
+ {
+ for (int i = 0; i < writers.length; i++)
+ {
+ writers[i].stop();
+ }
+ logCleaner.stop();
+ heuristicStatusLogger.close();
+ }
+
+ // RecoveryLogger implementation ---------------------------------
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#saveCommitDecision(
+ * long, java.lang.String[])
+ */
+ public TxCompletionHandler saveCommitDecision(long localTransactionId,
+ String[] resources)
+ {
+ ByteBuffer buffer;
+
+ if (resources == null || resources.length == 0)
+ {
+ buffer = LogRecord.createTxCommittedRecord(localTransactionId);
+ return writers[++seqNo % numWriters].addBatch(buffer, false);
+ }
+ else
+ {
+ buffer = LogRecord.createTxCommittedRecord(localTransactionId,
+ resources);
+ return writers[++seqNo % numWriters].addBatch(buffer, true);
+ }
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#savePrepareDecision(
+ * long, int, byte[], java.lang.String, java.lang.String[])
+ */
+ public TxCompletionHandler savePrepareDecision(long localTransactionId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ String recoveryCoordinator,
+ String[] resources)
+ {
+ ByteBuffer buffer = LogRecord.createTxPreparedRecord(localTransactionId,
+ inboundFormatId,
+ globalTransactionId,
+ recoveryCoordinator,
+ resources);
+ return writers[++seqNo % numWriters].addBatch(buffer, true);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#savePrepareDecision(
+ * long, javax.transaction.xa.Xid, java.lang.String[])
+ */
+ public TxCompletionHandler savePrepareDecision(long localTransactionId,
+ Xid inboundXid,
+ String[] resources)
+ {
+ ByteBuffer buffer =
+ LogRecord.createJcaTxPreparedRecord(localTransactionId,
+ inboundXid,
+ resources);
+ return writers[++seqNo % numWriters].addBatch(buffer, true);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#saveHeuristicStatus(
+ * long, boolean, int, byte[], byte[], int, int, boolean int[],
+ * org.jboss.tm.recovery.HeuristicStatus[])
+ */
+ public void saveHeuristicStatus(long localTransactionId,
+ boolean foreignTx,
+ int formatId,
+ byte[] globalTransactionId,
+ byte[] inboundBranchQualifier,
+ int transactionStatus,
+ int heurStatusCode,
+ boolean locallyDetectedHeuristicHazard,
+ int[] xaResourceHeuristics,
+ HeuristicStatus[] remoteResourceHeuristics)
+ {
+ ByteBuffer buffer =
+ LogRecord.createHeurStatusRecord(localTransactionId,
+ foreignTx,
+ formatId,
+ globalTransactionId,
+ inboundBranchQualifier,
+ transactionStatus,
+ heurStatusCode,
+ locallyDetectedHeuristicHazard,
+ xaResourceHeuristics,
+ remoteResourceHeuristics);
+ heuristicStatusLogger.write(buffer);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#clearHeuristicStatus(long)
+ */
+ public void clearHeuristicStatus(long localTransactionId)
+ {
+ ByteBuffer buffer =
+ LogRecord.createHeurForgottenRecord(localTransactionId);
+ heuristicStatusLogger.write(buffer);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#getRecoveryLogs()
+ */
+ public RecoveryLogReader[] getRecoveryLogs()
+ {
+ if (existingRecoveryLogFiles == null
+ || existingRecoveryLogFiles.length == 0)
+ return null;
+
+ RecoveryLogReader[] readers =
+ new RecoveryLogReader[existingRecoveryLogFiles.length];
+ for (int i = 0; i < readers.length; i++)
+ {
+ readers[i] =
+ new BatchRecoveryLogReader(existingRecoveryLogFiles[i], xidFactory);
+ }
+ return readers;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLogger#getHeuristicStatusLogs()
+ */
+ public HeuristicStatusLogReader[] getHeuristicStatusLogs()
+ {
+ if (existingHeuristicStatusLogFiles == null
+ || existingHeuristicStatusLogFiles.length == 0)
+ return null;
+
+ HeuristicStatusLogReader[] readers =
+ new HeuristicStatusLogReader[existingHeuristicStatusLogFiles.length];
+ for (int i = 0; i < readers.length; i++)
+ {
+ readers[i] = new SimpleHeuristicStatusLogReader(
+ existingHeuristicStatusLogFiles[i]);
+ }
+ return readers;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,167 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Service MBean that provides recovery logger functionality. The
+ * <code>BatchRecoveryLoggerService</code> is simply an MBean wrapper for a
+ * <code>BatchRecoveryLogger</code> instance, which does the real work.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class BatchRecoveryLoggerService
+ extends ServiceMBeanSupport
+ implements BatchRecoveryLoggerServiceMBean
+{
+ /** The actual recovery logger. */
+ protected BatchRecoveryLogger logger;
+
+ /** The recovery logger's Xid factory. */
+ private ObjectName xidFactory;
+
+ /**
+ * Constructs a <code>BatchRecoveryLoggerService</code>.
+ */
+ public BatchRecoveryLoggerService()
+ {
+ logger = createLogger();
+ }
+
+ /**
+ * To be overrided by subclasses that create a logger derived from
+ * <code>BatchRecoveryLogger</code> for testing purposes.
+ */
+ protected BatchRecoveryLogger createLogger()
+ {
+ return new BatchRecoveryLogger();
+ }
+
+ // ServiceMBeanSupport overrides ---------------------------------
+
+ /**
+ * @see org.jboss.system.ServiceMBeanSupport#startService()
+ */
+ public void startService() throws Exception
+ {
+ super.startService();
+ XidFactoryMBean xidFactoryObj =
+ (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+ logger.setXidFactory(xidFactoryObj);
+ logger.start();
+ }
+
+ /**
+ * @see org.jboss.system.ServiceMBeanSupport#stopService()
+ */
+ public void stopService() throws Exception
+ {
+ super.stopService();
+ logger.stop();
+ }
+
+ // RecoveryLoggerInstance implementation -------------------------
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryLoggerInstance#getInstance()
+ */
+ public RecoveryLogger getInstance()
+ {
+ return logger;
+ }
+
+ // BatchRecoveryLoggerServiceMBean implementation ----------------
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getDirectoryList()
+ */
+ public String[] getDirectoryList()
+ {
+ return logger.getDirectoryList();
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setDirectoryList(
+ * java.lang.String[])
+ */
+ public void setDirectoryList(String[] directoryList)
+ {
+ logger.setDirectoryList(directoryList);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getLogFileSize()
+ */
+ public int getLogFileSize()
+ {
+ return logger.getLogFileSize();
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setLogFileSize(
+ * int)
+ */
+ public void setLogFileSize(int logFileSize)
+ {
+ logger.setLogFileSize(logFileSize);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getHeuristicStatusLogDirectory()
+ */
+ public String getHeuristicStatusLogDirectory()
+ {
+ return logger.getHeuristicStatusLogDirectory();
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setHeuristicStatusLogDirectory(
+ * java.lang.String)
+ */
+ public void setHeuristicStatusLogDirectory(String directory)
+ {
+ logger.setHeuristicStatusLogDirectory(directory);
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getXidFactory()
+ */
+ public ObjectName getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setXidFactory(
+ * javax.management.ObjectName)
+ */
+ public void setXidFactory(ObjectName xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,100 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of the batch recovery logger service.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface BatchRecoveryLoggerServiceMBean
+ extends RecoveryLoggerInstance,
+ ServiceMBean
+{
+ /*
+ * Gets the names of directories that contain recovery log files.
+ *
+ * @return an array of names of directories that contain recovery log
+ * files.
+ *
+ */
+ String[] getDirectoryList();
+
+ /**
+ * Sets the names of directories that contain recovery log files.
+ *
+ * @param directoryList array of names of directories that contain recovery
+ * log files.
+ *
+ */
+ void setDirectoryList(String[] directoryList);
+
+ /**
+ * Gets the constant size of a log file.
+ *
+ * @return the constans size of a log file, in bytes.
+ */
+ int getLogFileSize();
+
+ /**
+ * Sets the constant size of a log file.
+ *
+ * @param logFileSize the constant size of a log file, in bytes.
+ */
+ void setLogFileSize(int logFileSize);
+
+ /**
+ * Gets the name of the directory that contains heuristic status log files.
+ *
+ * @return the name of the directory that contains heuristic status log
+ * files.
+ */
+ String getHeuristicStatusLogDirectory();
+
+ /**
+ * Sets the name of the directory that contains heuristic status log files.
+ *
+ * @param heuristicStatusLogDirectoryName the name of the directory that
+ * contains heuristic status log files.
+ */
+ void setHeuristicStatusLogDirectory(String directory);
+
+ /**
+ * Gets the Xid factory.
+ *
+ * @return the Xid factory.
+ */
+ ObjectName getXidFactory();
+
+ /**
+ * Sets the Xid factory
+ *
+ * @param xidFactory the Xid factory.
+ */
+ void setXidFactory(ObjectName xidFactory);
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,469 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+
+/**
+ * This class batches log write requests in order to minimize disk forcing
+ * activity. A <code>BatchWriter</code> has a current <code>BatchLog</code>
+ * instance, to which it writes log records, plus a pool of clean
+ * <code>BatchLog</code> instances, to be used when the current
+ * <code>BatchLog</code> gets full. It maintains a queue of write requests
+ * and implements a writer thread that takes write requests from the queue
+ * and batches them together in order to minimize disk forces.
+ * <p>
+ * The <code>BatchLog</code> instances owned by a <code>BatchWriter</code>
+ * know their owner <code>BatchWriter</code> and call the methods
+ * <code>restartBatchLog</code> and <code>getNextLogs</code> on their owner.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class BatchWriter implements Runnable
+{
+
+ /** Class <code>Logger</code> for trace messages. */
+ private static Logger errorLog = Logger.getLogger(BatchWriter.class);
+
+ /** True if trace messages should be logged. */
+ private static boolean traceEnabled = errorLog.isTraceEnabled();
+
+ /** The directory in which log files will be created. */
+ private final File dir;
+
+ /** The initial capacity of the queue of write requests. */
+ private final int initialCapacity;
+
+ /** The current <code>BatchLog</code> instance. */
+ private BatchLog log;
+
+ /** The constant size of a recovery log file. */
+ private final int fileSize;
+
+ /** The header object written to the beginning of a log file. */
+ private Object header;
+
+ /** Exception thrown when switching the current log file. */
+ private Exception abort;
+
+ /** This flag can be set to false to stop the writer thread. */
+ private boolean running = true;
+
+ /** Pool of clean <code>BatchLog</code> instances. */
+ private final List nextLogs = Collections.synchronizedList(new ArrayList());
+
+ /** Synchronizes accesses to the <code>currentQueue</code> of requests. */
+ private Object batchLock = new Object();
+
+ /** The current queue of write requests */
+ private ArrayList currentQueue;
+
+ /** Latch released when a batch of write requests are performed. */
+ private Latch currentLatch;
+
+ /** For asynchronously restarting <code>BatchLog</code> instances. */
+ private LogRestarter logRestarter;
+
+ /**
+ * Constructs a <code>BatchWriter</code>.
+ *
+ * @param header the header object to be placed at the beginning of the
+ * <code>BatchLog</code>s owned by this
+ * <code>BatchWriter</code>
+ * @param initialCapacity the initial capacity (in bytes) of the queue of
+ * write requests
+ * @param dir the directory in which log files will be created
+ * @param fileSize the constant size (in bytes) of a log file
+ * @param logRestarter <code>LogRestarter</code> instance that the
+ * <code>BatchWriter</code> will use for asynchronously
+ * restarting <code>BatchLog</code> instances.
+ * @throws IOException if a log file could not be created or initialized.
+ */
+ BatchWriter(Object header, int initialCapacity,
+ File dir, int fileSize, LogRestarter logRestarter)
+ throws IOException
+ {
+ this.header = header;
+ this.fileSize = fileSize;
+ this.initialCapacity = initialCapacity;
+ this.currentQueue = new ArrayList(initialCapacity);
+ this.currentLatch = new Latch();
+ this.dir = dir;
+ this.logRestarter = logRestarter;
+ log = new BatchLog(this, header, dir, fileSize);
+ nextLogs.add(new BatchLog(this, header, dir, fileSize));
+ }
+
+ /**
+ * Asynchronously restarts a given <code>BatchLog</code>. The
+ * <code>BatchLog</code> instances owned by this <code>BatchWriter</code>
+ * call this method to asynchronously restart themselves.
+ *
+ * @param logToRestart the <code>BatchLog</code> to be asynchronously
+ * restarted.
+ */
+ void restartBatchLog(BatchLog logToRestart)
+ {
+ logRestarter.add(logToRestart);
+ }
+
+ /**
+ * Gets the <code>List</code> that implements the pool of clean
+ * <code>BatchLog</code> instances of this <code>BatchWriter</code>.
+ * The <code>BatchLog</code> instances owned by this
+ * <code>BatchWriter</code> call this method after clean up, to add
+ * themselves to pool of clean <code>BatchLog</code> instances.
+ *
+ * @return the <code>List</code> of clean <code>BatchLog</code> instances
+ * kept by this <code>BatchWriter</code>.
+ */
+ List getNextLogs()
+ {
+ return nextLogs;
+ }
+
+ // FIXME: nobody calls this method
+ void clearAbort()
+ {
+ abort = null;
+ }
+
+ /**
+ * Stops the writer thread.
+ */
+ void stop()
+ {
+ synchronized (batchLock)
+ {
+ running = false;
+ batchLock.notifyAll();
+ }
+ }
+
+ /**
+ * Force-writes a "transaction committed" or "transaction prepared" log
+ * record to the current <code>BatchLog</code>.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing a log record to
+ * be written to the current <code>BatchLog</code>
+ * @param expectEndRecord true if the log record requires another log record
+ * (a <code>TX_END</code> record) to be eventually written
+ * to the same <code>BatchLog</code>.
+ * @return a <code>TxCompletionHandler</code> that should be invoked at the
+ * end of the second phase of the two-phase commit protocol
+ * for the transaction that generated the log record.
+ */
+ TxCompletionHandler addBatch(ByteBuffer buffer, boolean expectEndRecord)
+ {
+ PendingWriteRequest request = null;
+
+ if (traceEnabled)
+ {
+ errorLog.trace("Transaction log record:" +
+ HexDump.fromBuffer(buffer.array()));
+ errorLog.trace(LogRecord.toString(buffer));
+ }
+
+ synchronized (batchLock)
+ {
+ request = new PendingWriteRequest(buffer,
+ currentLatch,
+ expectEndRecord);
+ currentQueue.add(request);
+ batchLock.notify();
+ }
+
+ TxCompletionHandler completionHandler = request.waitTilDone();
+
+ if (!expectEndRecord)
+ return completionHandler;
+ else
+ return new TransactionCompletionLogger((BatchLog) completionHandler);
+ }
+
+ /**
+ * Writes a "transaction completed" (<code>TX_END</code>) record to the
+ * specified <code>BatchLog</code>. Each <code>TX_END</code> record is
+ * paired with a "transaction committed" or with a "transaction prepared"
+ * record and should be written to the same <code>BatchLog</code> as the
+ * record it is paired with. This method does not necessarily force the
+ * <code>TX_END</code> record to disk, but it might do so if that record
+ * is batched together with other records, which may require a forced
+ * write.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing a <code>TX_END</code>
+ * record to be written to a given <code>BatchLog</code>
+ * @param destinationLog the <code>BatchLog</code> to which the
+ * <code>TX_END</code> record will be written.
+ */
+ void addBatch(ByteBuffer buffer, BatchLog destinationLog)
+ {
+ PendingWriteRequest receipt = null;
+
+ if (traceEnabled)
+ {
+ errorLog.trace("Transaction Log record:" +
+ HexDump.fromBuffer(buffer.array()));
+ errorLog.trace(LogRecord.toString(buffer));
+ }
+
+ synchronized (batchLock)
+ {
+ receipt = new PendingWriteRequest(buffer,
+ currentLatch,
+ destinationLog);
+ currentQueue.add(receipt);
+ batchLock.notify();
+ }
+ }
+
+ /**
+ * The writer thread body.
+ */
+ public void run()
+ {
+ ArrayList work = null;
+ Latch workLatch = null;
+
+ while (running)
+ {
+ synchronized (batchLock)
+ {
+ if (currentQueue.size() > 0)
+ {
+ if (work == null)
+ work = new ArrayList(initialCapacity);
+
+ // Switch currentQueue and work. All write requests that
+ // were in currentQueue will be handled as a batch of work.
+
+ ArrayList tmp = work;
+ work = currentQueue;
+ currentQueue = tmp;
+ workLatch = currentLatch;
+ currentLatch = new Latch();
+ }
+ else
+ {
+ // Wait for a write request.
+ try
+ {
+ batchLock.wait();
+ }
+ catch (InterruptedException ignored)
+ {
+ if (!running) break;
+ Thread.interrupted(); // clear interrupted
+ }
+ continue;
+ }
+ }
+
+ try
+ {
+ // Perform the batch of work.
+ doWork(work);
+ }
+ catch (Exception e)
+ {
+ break;
+ }
+ finally
+ {
+ // Let
+ workLatch.release();
+ work.clear();
+ }
+ }
+ cleanup();
+ }
+
+ /**
+ * Performs a batch of write requests.
+ *
+ * @param work an <code>ArrayList</code> of
+ * <code>PendingWriteRequest</code>s.
+ */
+ private void doWork(ArrayList work)
+ {
+ if (abort != null)
+ {
+ for (int i = 0; i < work.size(); i++)
+ {
+ PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+ request.setFailure(abort);
+ }
+ return;
+ }
+
+ ByteBuffer[] records = new ByteBuffer[work.size()];
+ int offset = 0;
+ try
+ {
+ int length = records.length;
+ int usedSize = log.getPosition();
+ int numTransactions;
+ boolean done = false;
+
+ while (!done)
+ {
+ int j;
+ numTransactions = 0;
+
+ for (int i = j = offset; i < length; i++)
+ {
+ PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+ int type = request.getType();
+ records[j] = request.getBuffer();
+ if (type != PendingWriteRequest.TYPE_END)
+ {
+ usedSize += records[j].remaining();
+ if (type == PendingWriteRequest.TYPE_TX_MULTI_TM)
+ usedSize += LogRecord.TX_END_LEN;
+
+ if (usedSize > fileSize)
+ {
+ // will leave the for loop with work to do after restart
+ length = i;
+ }
+ else
+ {
+ numTransactions++;
+ j++;
+ }
+ }
+ else
+ {
+ // TX_END record: which log should we use?
+ BatchLog requestedLog = request.getLogger();
+
+ if (requestedLog != log)
+ {
+ // Use requestedLog instead of the current log
+ requestedLog.write(records[j], true);
+ // (Do not increment j in this case!)
+ }
+ else
+ {
+ // Let the record go to the current log
+ j++;
+ }
+ }
+
+ }
+ done = (length == records.length);
+ length = length - offset;
+ log.write(records, offset, j - offset, numTransactions);
+ setCompletionHandler(offset, length, work);
+ if (!done)
+ {
+ restart();
+ offset = offset + length;
+ length = records.length;
+ usedSize = log.getPosition();
+ }
+ }
+ }
+ catch (IOException failure)
+ {
+ for (int i = offset; i < records.length - offset; i++)
+ {
+ PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+ request.setFailure(failure);
+ }
+ if (abort == null)
+ restart();
+ }
+ }
+
+ /**
+ * Sets to the current <code>BatchLog</code> the completion handler of a
+ * range of requests in an <code>ArrayList</code> of
+ * <code>PendingWriteRequest</code>s.
+ *
+ * @param offset index of the first request in the range
+ * @param length number of requests in the range
+ * @param work an <code>ArrayList</code> of
+ * <code>PendingWriteRequest</code>s
+ */
+ private void setCompletionHandler(int offset, int length, ArrayList work)
+ {
+ for (int i = offset; i < length; i++)
+ {
+ PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+ if (request.getType() != PendingWriteRequest.TYPE_END)
+ request.setCompletionHandler(log);
+ }
+ }
+
+ /**
+ * Closes all <code>BatchLog</code>s owned by this <code>BatchWriter</code>.
+ */
+ private void cleanup()
+ {
+ synchronized (nextLogs)
+ {
+
+ for (int i = 0; i < nextLogs.size(); i++)
+ {
+ BatchLog nextLog = (BatchLog) nextLogs.get(i);
+ nextLog.close();
+ }
+ }
+ log.close();
+ }
+
+ /**
+ * Switch the current <code>BatchLog</code>.
+ */
+ private void restart()
+ {
+ log.markForRestart();
+
+ if (nextLogs.size() > 0)
+ log = (BatchLog) nextLogs.remove(0);
+ else
+ {
+ try
+ {
+ log = new BatchLog(this, header, dir, fileSize);
+ }
+ catch (IOException e)
+ {
+ abort = new Exception("FAILED TO RESTART RECOVERY LOG " +
+ "AFTER BEING FULL", e);
+ }
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ *
+ * A CorruptedLogRecordException.
+ *
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class CorruptedLogRecordException
+ extends RuntimeException
+{
+ /** The serialVersionUID */
+ private static final long serialVersionUID = -1711360962508810879L;
+
+ boolean disablePresumedRollback = false;
+
+ CorruptedLogRecordException(String message)
+ {
+ super(message);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,61 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ * "Struct class" that groups together the status code for a heuristic
+ * decision and a stringfied reference to the remote <code>Resource</code>
+ * instance that reported the heuristic decision.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HeuristicStatus
+{
+ /**
+ * The status code for a heuristic decision. This field uses the same
+ * encoding as the <code>errorCode</code> field of a
+ * <code>javax.transaction.xa.XAException</code> instance.
+ */
+ public int code;
+
+ /**
+ * A stringfied reference to the <code>Resource</code> that reported
+ * the heuristic decision.
+ */
+ public String resourceRef;
+
+ /**
+ * Constructs a <code>HeuristicStatus</code>.
+ *
+ * @param code the status code for a heuristic decision, encoded in the
+ * same way as the <code>errorCode</code> field of a
+ * <code>javax.transaction.xa.XAException</code> instance
+ * @param resourceRef a stringfied reference to the <code>Resource</code>
+ * that reported the heuristic decision.
+ */
+ public HeuristicStatus(int code, String resourceRef)
+ {
+ this.code = code;
+ this.resourceRef = resourceRef;
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,133 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Simple class that appends <code>ByteBuffer</code>s (each of which contains
+ * a heuristic status log record) to a log file. Information on heuristically
+ * completed transactions does not go to the recovery logs -- it goes to a
+ * separate log file. Since heuristic decisions are very rare, there is no need
+ * to batch heuristic log writes together.
+ *
+ * TODO: In this implementation the heuristic status log file grows whenever
+ * a record is written to it. This is bad for robustness and should be replaced
+ * by an implementation in which the log file has constant lenght (such as the
+ * recovery log implementation).
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HeuristicStatusLog
+{
+ /**
+ * Class <code>Logger</code>, for trace messages.
+ */
+ private static Logger errorLog =
+ Logger.getLogger(HeuristicStatusLog.class);
+
+ /**
+ * True if trace messages should be logged.
+ */
+ private static boolean traceEnabled = errorLog.isTraceEnabled();
+
+ /**
+ * The log file.
+ */
+ private File logFile;
+
+ /**
+ * A <code>RandomAccessFile</code> view of the log file.
+ */
+ RandomAccessFile os;
+
+ /**
+ * The <code>RandomAccessFile</code>'s underlying <code>FileChannel</code>.
+ */
+ private FileChannel channel;
+
+ /**
+ * Constructs a new <code>HeuristicStatusLog</code>.
+ *
+ * @param dir the directory in which the log file will be created.
+ * @throws IOException
+ */
+ HeuristicStatusLog(File dir)
+ throws IOException
+ {
+ logFile = File.createTempFile("HEURISTIC_STATUS_LOG", ".log", dir);
+ os = new RandomAccessFile(logFile, "rw");
+ channel = os.getChannel();
+ channel.force(true);
+ }
+
+ /**
+ * Writes one record at the current position of this
+ * <code>HeuristicStatusLog</code>.
+ *
+ * @param record a buffer with the record to be written
+ */
+ void write(ByteBuffer record)
+ {
+ if (traceEnabled)
+ {
+ errorLog.trace("Heuristic status log record:" +
+ HexDump.fromBuffer(record.array()));
+ errorLog.trace(LogRecord.toString(record));
+ }
+
+ try
+ {
+ channel.write(record);
+ channel.force(true);
+ }
+ catch (IOException e)
+ {
+ errorLog.error("Error writing heuristic status log " +
+ logFile.getName(), e);
+ }
+ }
+
+ /**
+ * Closes this <code>HeuristicStatusLog</code>.
+ */
+ void close()
+ {
+ try
+ {
+ os.close();
+ }
+ catch (IOException e)
+ {
+ errorLog.error("Error closing heuristic status log " +
+ logFile.getName(), e);
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.util.Map;
+
+/**
+ * Interface that gives access to the information in a heuristic status
+ * log file.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface HeuristicStatusLogReader
+{
+ /**
+ * Gets the name of the underlying heuristic status log file.
+ *
+ * @return the name of the heuristic status log file.
+ */
+ String getLogFileName();
+
+ /**
+ * Recovers information on heuristically completed transactions from
+ * the heuristic status log file.
+ *
+ * @param heuristicallyCompletedTransactions a <code>Map</code> to which
+ * this method will one entry per heuristically completed
+ * transaction. The map keys are <code>Long</code> values
+ * containing local transaction ids. The map values are
+ * <code>LogRecord.HeurData</code> objects with information
+ * on heuristically completed transactions.
+ */
+ void recover(Map heuristicallyCompletedTransactions);
+
+ /**
+ * Removes the underlying heuristic status log file.
+ */
+ void finishRecovery();
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,133 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.*;
+
+/**
+ * Utility class for converting a byte array into a hex dump string.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HexDump
+{
+ /**
+ * Converts a byte array into an hex dump string.
+ *
+ * @param buffer the byte array to be converted
+ * @return a string containing the hex dump of the <code>buffer</code>.
+ */
+ public static String fromBuffer(byte[] buffer)
+ {
+ int n;
+ final int N = 16;
+ StringBuffer sb = new StringBuffer("\n");
+
+ for (int i = 0; (n = Math.min(N, buffer.length - i)) != 0; i = i + n)
+ {
+ sb.append(toHexString(i) + ": ");
+ for (int j = 0; j < n ; j++)
+ sb.append(toHexString(buffer[i + j]) + " ");
+ for (int j = n; j < N; j++)
+ sb.append(" ");
+ sb.append(" ");
+ for (int j = 0; j < n ; j++)
+ sb.append(toDisplayableChar(buffer[i + j]));
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Converts a given byte into a string of two hex digits.
+ * @param b the byte to be converted
+ * @return the string representation of <code>b</code>,
+ * as a pair of hex digits.
+ */
+ private static String toHexString(byte b)
+ {
+ int i = b; // the byte 0x80 is sign extended to 0xffffff80
+ if (i < 0)
+ i = i - (int) 0xffffff00; // undo sign extension
+ String hexStr = Integer.toHexString(i);
+ return (hexStr.length() < 2) ? "0" + hexStr : hexStr;
+ }
+
+ /**
+ * Converts a given integer into a string of up to 8 hex digits left-padded
+ * with spaces.
+ *
+ * @param n the <code>int</code> to be converted
+ * @return a string containing the hex digits of <code>n</code> left-padded
+ * with spaces so that the total length of the string is 8.
+ */
+ private static String toHexString(int n)
+ {
+ String hexStr = Integer.toHexString(n);
+ while (hexStr.length() < 8)
+ hexStr = " " + hexStr;
+ return hexStr;
+ }
+
+ /**
+ * Converts a given byte into a displayable character.
+ * @param b the byte to convert
+ * @return a char whose value is <code>b</code> (if <code>b</code> is in the
+ * displayable range), or the char '.' (otherwise).
+ */
+ private static char toDisplayableChar(byte b)
+ {
+ char c = (char) b;
+ if (c >= 0x20 && c < 0x7f)
+ return c;
+ else
+ return '.';
+ }
+
+ /**
+ * For testing.
+ */
+ public static void main(String[] args) throws IOException
+ {
+ InputStream in;
+ byte[] ibuf = new byte[4096];
+
+ if (args.length > 1)
+ {
+ System.err.println("HexDump usage: java HexDump [file]");
+ System.exit(1);
+ }
+ in = (args.length == 0) ? System.in : new FileInputStream(args[0]);
+ int n = in.read(ibuf);
+ while (n > 0)
+ {
+ byte[] buf = new byte[n];
+ System.arraycopy(ibuf, 0, buf, 0, n);
+ System.out.print(fromBuffer(buf));
+ n = in.read(ibuf);
+ }
+ if (args.length != 0)
+ in.close();
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1389 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.tm.TxUtils;
+
+/**
+ * Utility class with static methods to create transaction log records and to
+ * extract information from transaction log records. It has static methods to
+ * create the following kinds of log records:
+ * <ul>
+ * <li><code>TX_COMMITTED</code> records, which are used for locally-started
+ * transactions that do not involve other transaction managers;</li>
+ * <li><code>MULTI_TM_TX_COMMITTED</code> records, which are used for
+ * locally-started transactions that involve other transaction
+ * managers;</li>
+ * <li><code>TX_PREPARED</code> records, which are used for foreign
+ * transactions that entered this virtual machine in transaction contexts
+ * propagated along with remote method invocations;</li>
+ * <li><code>JCA_TX_PREPARED</code> records, which are used for foreign
+ * transactions that entered this virtual machine through JCA transaction
+ * inflow;</li>
+ * <li><code>TX_END</code> records, which are used for distributed
+ * transactions and mark the end of the second phase of the 2PC subtree
+ * coordinated by this transaction manager. They are paired with
+ * <code>MULTI_TM_TX_COMMITTED</code>, <code>TX_PREPARED</code>, and
+ * <code>JCA_TX_PREPARED</code> records. No <code>TX_END</code> record
+ * is written out in the case of a locally-started transaction that
+ * involves no external transaction managers. In other words,
+ * <code>TX_END</code> records are not paired with
+ * <code>TX_COMMITTED</code> records;</li>
+ *<li><code>HEUR_STATUS</code> records, which are used to log the
+ * heuristic status of a distributed transaction;</li>
+ *<li><code>HEUR_FORGOTTEN</code> records, which are used to clear the
+ * heuristic status of a distributed transaction.</li>
+ * </ul>
+ * Layout of <code>MULTI_TM_TX_COMMITTED</code> records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long)
+ * - countOfDirEntries (N, a short)
+ * - varField 0 \
+ * ... | <------------ variable-sized fields
+ * - varField N-1 /
+ * - offset of varField N-1 \ directory of varFields: N dir entries
+ * - length of varField N-1 | (each entry contains the length of
+ * ... | <-- a varField and the offset of its
+ * - offset of varField 0 | first byte relative to the start
+ * - length of varField 0 / of the record)
+ * - checksum (int)
+ * </pre>
+ * The varFields of a <code>MULTI_TM_TX_COMMITTED</code> record contain
+ * stringfied references for the remote <code>Resource</code>s enlisted in the
+ * transaction. Note that the dir entries are stored backwards at the end of
+ * the record, that is, the one that appears last (just before the checksum)
+ * refers to the first varField, and the one that appears first refers to the
+ * last varField.
+ * <p>
+ * Layout of <code>TX_PREPARED</code> and <code>JCA_TX_PREPARED</code>
+ * records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long)
+ * - countOfDirEntries (N, a short)
+ * - inboundFormatId (int)
+ * - gidLength (short)
+ * - globalTransactionId (an array of gidLength bytes)
+ * - varField 0 \
+ * ... | <------------ variable-sized fields
+ * - varField N-1 /
+ * - offset of varField N-1 \ directory of varFields: N dir entries
+ * - length of varField N-1 | (each entry contains the length of
+ * ... | <-- a varField and the offset of its
+ * - offset of varField 0 | first byte relative to the start
+ * - length of varField 0 / of the record)
+ * - checksum (int)
+ * </pre>
+ * The inboundFormatId is the formatId of the foreign <code>Xid</code>.
+ * In a <code>TX_PREPARED</code> record, the first varField (the one referred
+ * to by the dir entry that immediately precedes the checksum) contains a
+ * stringfied reference for the <code>RecoveryCoordinator</code> of the
+ * transaction.
+ * In a <code>JCA_TX_PREPARED</code> record, the first varField contains the
+ * inbound branch qualifier, which is the branch qualifier part of the foreign
+ * <code>Xid</code> passed to <code>XATerminator.prepare()</code>.
+ * The remaining varFields of a <code>TX_PREPARED</code> or
+ * <code>JCA_TX_PREPARED</code> records contain stringfied references for the
+ * remote <code>Resource</code>s enlisted in the transaction.
+ * </p>
+ * Layout of <code>TX_COMMITTED</code> records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long),
+ * - checksum (int)
+ * </pre>
+ * <p>
+ * Layout of <code>TX_END</code> records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long),
+ * - checksum (int)
+ * </pre>
+ * <p>
+ * Layout of <code>HEUR_STATUS</code> records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long)
+ * - countOfDirEntries (N, a short)
+ * - transactionStatus (byte)
+ * - heuristicStatusCode (byte)
+ * - locallyDetectedHeuristicHazardFlag (byte)
+ * - isForeignTx (byte)
+ * - formatId (int)
+ * - gidLength (short)
+ * - globalTransactionId (an array of gidLength bytes)
+ * - varField 0 \
+ * ... | <------------ variable-sized fields
+ * - varField N-1 /
+ * - offset of varField N-1 \ directory of varFields: N dir entries
+ * - length of varField N-1 | (each entry contains the length of
+ * ... | <-- a varField and the offset of its
+ * - offset of varField 0 | first byte relative to the start
+ * - length of varField 0 / of the record)
+ * - checksum (int)
+ * </pre>
+ *
+ * In a <code>HEUR_STATUS</code> record, the first varField (the one referred
+ * to by the dir entry that immediately precedes the checksum) contains the
+ * the inbound branch qualifier of a transaction that entered the server via
+ * JCA inflow. This varField is empty (i.e., it has zero length) in the case of
+ * a transaction that did not enter the server via JCA inflow. The second
+ * varField contains a byte array with the heuristic status codes of the XA
+ * resources that reported heuristic decisions. This varField is empty if no
+ * XA resource reported heuristic decisions. The remaining varFields are
+ * associated with remote resources that reported heuristic decisions. Each
+ * such varField contains a byte with the heuristic status code reported by the
+ * remote resource, followed by a stringfied reference for the remote
+ * <code>Resource</code> instance that reported a heuristic decision.
+ * <p>
+ * Layout of <code>HEUR_FORGOTTEN</code> records:
+ * <pre>
+ * - magicHeader (an array of HEADER_LEN bytes)
+ * - recordLength (short)
+ * - recordLengthCheck (short)
+ * - recordType (byte)
+ * - localTransactionId (long),
+ * - checksum (int)
+ * </pre>
+ * <p>
+ * Note that <code>TX_COMMITTED</code>, <code>TX_END</code>, and
+ * <code>HEUR_FORGOTTEN</code> records have fixed size. The other types of
+ * records are variable-sized.
+ * <p>
+ * In all record types:
+ * <ul>
+ * <li>The recordLength is the number of bytes of the part of the record that
+ * follows the recordLengthCheck field. It counts the bytes from the
+ * recordType field up to (and including) the checksum field.</li>
+ * <li>The recordLengthCheck is the arithmetic negation of the recordLength
+ * value (i.e,, -recordLength).</li>
+ * <li>The checksum is the Adler-32 checksum of the part of the record that
+ * starts at the recordType field(which is included in the checksum) and
+ * ends at the checksum field (which is not included).</li>
+ * </ul>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class LogRecord
+{
+ /** Magic header placed in all log records.*/
+ public static final byte[] HEADER = "Log".getBytes();
+
+ /** Length of the magic header. */
+ public static final int HEADER_LEN = HEADER.length;
+
+ /** Null header that will be read when there are no more log records */
+ private static final byte[] NULL_HEADER = {0, 0, 0};
+
+ /** Size of a byte, in bytes. */
+ private static final int SIZEOF_BYTE = 1;
+
+ /** Size of a short, in bytes. */
+ static final int SIZEOF_SHORT = 2;
+
+ /** Size of a long, in bytes. */
+ private static final int SIZEOF_LONG = 8;
+
+ /** Length of the inbound format id. */
+ private static final int FORMAT_ID_LEN = 4;
+
+ /** Length of the checksum. */
+ private static final int CHKSUM_LEN = 4;
+
+ /** Total length of the magic header plus record length fields. */
+ static final int FULL_HEADER_LEN = HEADER_LEN + 2 * SIZEOF_SHORT;
+
+ /** Value of recordType field in a single-TM tx committed record */
+ static final byte TX_COMMITTED = (byte) 'C';
+
+ /** Value of recordType field in a multi-TM tx committed record */
+ static final byte MULTI_TM_TX_COMMITTED = (byte) 'M';
+
+ /** Value of recordType field in a tx prepared record */
+ static final byte TX_PREPARED = (byte) 'P';
+
+ /** Value of recordType field in a JCA tx prepared record */
+ static final byte JCA_TX_PREPARED = (byte) 'R';
+
+ /** Value of recordType field in a tx end record */
+ static final byte TX_END = (byte) 'E';
+
+ /** Value of recordType field in a heuristic status record */
+ static final byte HEUR_STATUS = (byte) 'H';
+
+ /** Value of recordType field in a heuristic status record */
+ static final byte HEUR_FORGOTTEN = (byte) 'F';
+
+ /** Size of varField directory entry */
+ private static final int SIZEOF_DIR_ENTRY =
+ SIZEOF_SHORT /* offset */
+ + SIZEOF_SHORT; /* length */
+
+ /**
+ * Minimum length of a multi-TM tx committed
+ * (<code>MULTI_TM_TX_COMMITTED</code>) record.
+ */
+ private static final int MIN_MULTI_TM_TX_COMMITTED_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type */
+ + SIZEOF_LONG /* local transaction id */
+ + SIZEOF_SHORT /* count of dir entries */
+ + CHKSUM_LEN; /* checksum */
+
+ /**
+ * Minimum length of a tx prepared (<code>TX_PREPARED</code>)
+ * or JCA tx prepared (<code>JCA_TX_PREPARED</code>) record.
+ */
+ private static final int MIN_TX_PREPARED_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type */
+ + SIZEOF_LONG /* local transaction id */
+ + SIZEOF_SHORT /* count of dir entries */
+ + FORMAT_ID_LEN /* inbound format id */
+ + SIZEOF_SHORT /* length of global transaction id */
+ + CHKSUM_LEN; /* checksum */
+
+ /**
+ * Fixed length of a single-TM tx committed (<code>TX_COMMITTED</code>)
+ * record.
+ */
+ private static final int TX_COMMITED_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type (TX_COMMITTED) */
+ + SIZEOF_LONG /* local transaction id */
+ + CHKSUM_LEN; /* checksum */
+
+ /**
+ * Fixed length of a tx end (<code>TX_END</code>) record.
+ */
+ static final int TX_END_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type (TX_END) */
+ + SIZEOF_LONG /* local transaction id */
+ + CHKSUM_LEN; /* checksum */
+
+ /**
+ * Minimum length of a heuristic status (<code>HEUR_STATUS</code>) record.
+ */
+ private static final int MIN_HEUR_STATUS_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type (HEUR_STATUS) */
+ + SIZEOF_LONG /* local transaction id */
+ + SIZEOF_SHORT /* count of dir entries */
+ + SIZEOF_BYTE /* transaction status */
+ + SIZEOF_BYTE /* heuristic status code */
+ + SIZEOF_BYTE /* locally-detected heuristic hazard flag */
+ + SIZEOF_BYTE /* foreign tx flag */
+ + FORMAT_ID_LEN /* format id */
+ + SIZEOF_SHORT /* length of global transaction id */
+ + SIZEOF_DIR_ENTRY /* varField with inbound branch qualifier */
+ + SIZEOF_DIR_ENTRY /* varField with heuristic codes for XA resources */
+ + CHKSUM_LEN; /* checksum */
+
+ /**
+ * Fixed length of a heuristic forgotten (HEUR_FORGOTTEN) record.
+ */
+ static final int HEUR_FORGOTTEN_LEN =
+ HEADER_LEN /* magic header */
+ + SIZEOF_SHORT /* record length */
+ + SIZEOF_SHORT /* record length check */
+ + SIZEOF_BYTE /* record type (HEUR_FORGOTTEN) */
+ + SIZEOF_LONG /* local transaction id */
+ + CHKSUM_LEN; /* checksum */
+
+
+
+ /**
+ * Structure filled out by the method <code>LogRecord.getData()</code>.
+ */
+ public static class Data
+ {
+ public byte recordType;
+ public long localTransactionId;
+ public byte[] globalTransactionId;
+ public int inboundFormatId;
+ public String recoveryCoordinator;
+ public byte[] inboundBranchQualifier;
+ public String[] resources;
+ }
+
+ /**
+ * Structure filled out by the method <code>LogRecord.getHeurData()</code>.
+ */
+ public static class HeurData
+ {
+ public byte recordType;
+ public long localTransactionId;
+ public boolean foreignTx;
+ public int formatId;
+ public byte[] globalTransactionId;
+ public byte[] inboundBranchQualifier;
+ public int transactionStatus;
+ public int heuristicStatusCode;
+ public boolean locallyDetectedHeuristicHazard;
+ public int[] xaResourceHeuristics;
+ public HeuristicStatus[] remoteResourceHeuristics;
+ }
+
+ /**
+ * Private constructor to enforce non-instantiability.
+ */
+ private LogRecord()
+ {
+ }
+
+ /**
+ * Creates a tx committed record for a locally-started transaction that
+ * does not involve other transaction managers.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @return a <code>ByteBuffer</code> containing the tx committed record.
+ * The buffer position is set to zero and the buffer limit is set
+ * to the number of bytes in the commit record.
+ */
+ static ByteBuffer createTxCommittedRecord(long localTransactionId)
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(TX_COMMITED_LEN);
+
+ buffer.put(HEADER)
+ .putShort((short) (TX_COMMITED_LEN - FULL_HEADER_LEN))
+ .putShort((short) -(TX_COMMITED_LEN - FULL_HEADER_LEN))
+ .put(TX_COMMITTED)
+ .putLong(localTransactionId);
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ SIZEOF_BYTE + SIZEOF_LONG);
+ buffer.putInt(TX_COMMITED_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Creates a multi-TM tx committed record for a distributed transaction.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param resources an array of stringfied references for the remote
+ * resources (<code>org.jboss.tm.remoting.Resource</code>
+ * instances) enlisted in the transaction.
+ * @return a <code>ByteBuffer</code> containing the tx committed record.
+ * The buffer position is set to zero and the buffer limit is set
+ * to the number of bytes in the record.
+ */
+ static ByteBuffer createTxCommittedRecord(long localTransactionId,
+ String[] resources)
+ {
+ int recordLen = MIN_MULTI_TM_TX_COMMITTED_LEN;
+ int resourceCount = 0;
+
+ if (resources != null && (resourceCount = resources.length) > 0)
+ {
+ for (int i = 0; i < resourceCount; i++)
+ {
+ recordLen += SIZEOF_DIR_ENTRY /* offset and length */
+ + resources[i].length(); /* data */
+ }
+ }
+ else
+ throw new RuntimeException("No remote resources were specified");
+
+ ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+
+ buffer.put(HEADER)
+ .putShort((short) (recordLen - FULL_HEADER_LEN))
+ .putShort((short) -(recordLen - FULL_HEADER_LEN))
+ .put(MULTI_TM_TX_COMMITTED)
+ .putLong(localTransactionId)
+ .putShort((short) resourceCount);
+
+ for (int i= 0; i < resourceCount; i++)
+ {
+ int offset = buffer.position();
+ int length = resources[i].length();
+ byte[] resourceBytes = new byte[length];
+
+ resources[i].getBytes(0, length, resourceBytes, 0);
+ buffer.put(resourceBytes)
+ .putShort(recordLen
+ - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * i,
+ (short) length)
+ .putShort(recordLen
+ - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * i,
+ (short) offset);
+ }
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+ buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Creates a tx prepared record or a JCA tx prepared record.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param inboundFormatId the format id of the foreign <code>Xid</code>
+ * @param globalTransactionId the global id of the transaction
+ * @param jcaInboundTransaction true if this method should create a JCA tx
+ * prepared record, false if this method should
+ * create a tx prepared record
+ * @param recoveryCoordOrInboundBranchQual an stringfied recovery
+ * coordinator converted to a byte array (if
+ * jcaInboundTransaction is false) or the inbound
+ * branch qualifier of a JCA inbound transaction (if
+ * jcaInboundTransaction is true)
+ * @param resources an array of stringfied references for the remote
+ * resources (<code>org.jboss.tm.remoting.Resource</code>
+ * instances) enlisted in the transaction.
+ * @return a <code>ByteBuffer</code> containing the tx prepared record or
+ * JCA tx prepared record. The buffer position is set to zero and
+ * the buffer limit is set to the number of bytes in the record.
+ */
+ private static ByteBuffer createTxPreparedRecord(
+ long localTransactionId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ boolean jcaInboundTransaction,
+ byte[] recoveryCoordOrInboundBranchQual,
+ String[] resources)
+ {
+ int recordLen = MIN_TX_PREPARED_LEN;
+ int globalTxIdLen = 0;
+ int resourceCount = 0;
+
+ if (globalTransactionId != null &&
+ (globalTxIdLen = globalTransactionId.length) > 0)
+ {
+ recordLen += globalTxIdLen;
+ }
+ else
+ throw new RuntimeException("The global transaction id " +
+ "was not specified");
+
+ if (resources != null && (resourceCount = resources.length) > 0)
+ {
+ for (int i = 0; i < resourceCount; i++)
+ {
+ recordLen += SIZEOF_DIR_ENTRY /* dir entry */
+ + resources[i].length(); /* data */
+ }
+ }
+
+ recordLen += SIZEOF_DIR_ENTRY /* dir entry */
+ + recoveryCoordOrInboundBranchQual.length; /* data */
+
+ ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+
+ buffer.put(HEADER)
+ .putShort((short) (recordLen - FULL_HEADER_LEN))
+ .putShort((short) -(recordLen - FULL_HEADER_LEN))
+ .put(jcaInboundTransaction ? JCA_TX_PREPARED : TX_PREPARED)
+ .putLong(localTransactionId)
+ .putShort((short) (resourceCount + 1))
+ .putInt(inboundFormatId)
+ .putShort((short) globalTxIdLen)
+ .put(globalTransactionId);
+
+ int offset = buffer.position();
+ int length = recoveryCoordOrInboundBranchQual.length;
+
+ buffer.put(recoveryCoordOrInboundBranchQual)
+ .putShort(recordLen - CHKSUM_LEN - SIZEOF_SHORT, (short) length)
+ .putShort(recordLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY,
+ (short) offset);
+
+ for (int i = 0; i < resourceCount; )
+ {
+ offset = buffer.position();
+ length = resources[i].length();
+ byte[] byteArray = new byte[length];
+
+ resources[i].getBytes(0, length, byteArray, 0);
+ i++; // increment i *before* storing the entry (because entry 0 is
+ // the recovery coordinator or inbound branch qualifier)
+ buffer.put(byteArray)
+ .putShort(recordLen
+ - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * i,
+ (short) length)
+ .putShort(recordLen
+ - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * i,
+ (short) offset);
+ }
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+ buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Creates a tx prepared record for a distributed transaction.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param inboundFormatId the format id of the foreign <code>Xid</code>
+ * @param globalTransactionId the global id of the transaction
+ * @param recoveryCoordinator a stringfied reference for the remote
+ * coordinator, an
+ * <code>org.jboss.tm.remoting.RecoveryCoordinator</code>
+ * instance
+ * @param resources an array of stringfied references for the remote
+ * resources (<code>org.jboss.tm.remoting.Resource</code>
+ * instances) enlisted in the transaction.
+ * @return a <code>ByteBuffer</code> containing the tx prepared record.
+ * The buffer position is set to zero and the buffer limit is set
+ * to the number of bytes in the record.
+ */
+ static ByteBuffer createTxPreparedRecord(long localTransactionId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ String recoveryCoordinator,
+ String[] resources)
+ {
+ int len = recoveryCoordinator.length();
+ byte[] coordinatorByteArray = new byte[len];
+
+ recoveryCoordinator.getBytes(0, len, coordinatorByteArray, 0);
+
+ return createTxPreparedRecord(localTransactionId,
+ inboundFormatId,
+ globalTransactionId,
+ false /* not JCA inbound */,
+ coordinatorByteArray,
+ resources);
+ }
+
+ /**
+ * Creates a tx prepared record for a JCA inbound transaction.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param inboundXid a foreign <code>Xid</code> instance
+ * @param resources an array of stringfied references for the remote
+ * resources (<code>org.jboss.tm.remoting.Resource</code>
+ * instances) enlisted in the transaction.
+ * @return a <code>ByteBuffer</code> containing the JCA tx prepared record.
+ * The buffer position is set to zero and the buffer limit is set
+ * to the number of bytes in the record.
+ */
+ static ByteBuffer createJcaTxPreparedRecord(long localTransactionId,
+ Xid inboundXid,
+ String[] resources)
+ {
+ return createTxPreparedRecord(localTransactionId,
+ inboundXid.getFormatId(),
+ inboundXid.getGlobalTransactionId(),
+ true /* JCA inbound */,
+ inboundXid.getBranchQualifier(),
+ resources);
+ }
+
+ /**
+ * Creates a tx end record for a distributed transaction.
+ *
+ * @param localTransactionId the local id of the transaction.
+ * @return a <code>ByteBuffer</code> containing the end record. The
+ * buffer position is set to zero and the buffer limit is set to
+ * the number of bytes in the end record.
+ */
+ static ByteBuffer createTxEndRecord(long localTransactionId)
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(TX_END_LEN);
+
+ buffer.put(HEADER)
+ .putShort((short) (TX_END_LEN - FULL_HEADER_LEN))
+ .putShort((short) -(TX_END_LEN - FULL_HEADER_LEN))
+ .put(TX_END)
+ .putLong(localTransactionId);
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ SIZEOF_BYTE + SIZEOF_LONG);
+ buffer.putInt(TX_END_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Creates a heuristic status record for a transaction.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param foreignTx true if the transaction is a foreign one, false otherwise
+ * @param formatId the format id field of the transaction's <code>Xid</code>
+ * @param globalTransactionId the global id field of the transaction's
+ * <code>Xid</code>
+ * @param inboundBranchQualifier the inbound branch qualifier, in the case
+ * of a foreign transaction that has been imported via JCA
+ * transaction inflow, or null otherwise
+ * @param transactionStatus the transaction status
+ * (<code>javax.transaction.Status.STATUS_COMMITTING</code>, or
+ * <code>javax.transaction.Status.STATUS_COMMITTED</code>, or
+ * <code>javax.transaction.Status.STATUS_ROLLING_BACK</code>, or
+ * <code>javax.transaction.Status.STATUS_ROLLEDBACK</code>)
+ * @param heurStatusCode the heuristic status code, which takes the same
+ * values as the <code>errorCode</code> field of
+ * <code>javax.transaction.xa.XAException</code>
+ * @param locallyDetectedHeuristicHazard true if a heuristic hazard was
+ * detected locally and is still outstanding
+ * @param xaResourceHeuristics array with the heuristic status codes of
+ * the XA resources that reported heuristic decisions,
+ * or null if no XA resources reported heuristic decisions
+ * @param remoteResourceHeuristics array with the heuristic status of
+ * the remote resources that reported heuristic decisions,
+ * or null if no remote resources reported heuristic
+ * decisions
+ * @return a <code>ByteBuffer</code> containing the heuristic status record.
+ * The buffer position is set to zero and the buffer limit is set
+ * to the number of bytes in the record.
+ */
+ static ByteBuffer createHeurStatusRecord(
+ long localTransactionId,
+ boolean foreignTx,
+ int formatId,
+ byte[] globalTransactionId,
+ byte[] inboundBranchQualifier,
+ int transactionStatus,
+ int heurStatusCode,
+ boolean locallyDetectedHeuristicHazard,
+ int[] xaResourceHeuristics,
+ HeuristicStatus[] remoteResourceHeuristics)
+ {
+ int recordLen = MIN_HEUR_STATUS_LEN;
+ int globalTxIdLen = 0;
+ int remoteResourceHeuristicsCount = 0;
+
+ if (globalTransactionId != null)
+ recordLen += (globalTxIdLen = globalTransactionId.length);
+ if (inboundBranchQualifier != null)
+ recordLen += inboundBranchQualifier.length;
+ if (xaResourceHeuristics != null)
+ recordLen += xaResourceHeuristics.length;
+ if (remoteResourceHeuristics != null)
+ {
+ remoteResourceHeuristicsCount = remoteResourceHeuristics.length;
+ for (int i = 0; i < remoteResourceHeuristicsCount; i++)
+ {
+ recordLen += SIZEOF_DIR_ENTRY /* offset and length */
+ + SIZEOF_BYTE /* data */
+ + remoteResourceHeuristics[i].resourceRef.length();
+ }
+ }
+
+ ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+
+ buffer.put(HEADER)
+ .putShort((short) (recordLen - FULL_HEADER_LEN))
+ .putShort((short) -(recordLen - FULL_HEADER_LEN))
+ .put(HEUR_STATUS)
+ .putLong(localTransactionId)
+ .putShort((short) (remoteResourceHeuristicsCount + 2))
+ .put((byte) transactionStatus)
+ .put((byte) heurStatusCode)
+ .put((locallyDetectedHeuristicHazard) ? (byte) 1 : (byte) 0)
+ .put((foreignTx) ? (byte) 1 : (byte) 0)
+ .putInt(formatId)
+ .putShort((short) globalTxIdLen)
+ .put(globalTransactionId);
+
+
+ int offset, length;
+
+ // varField 0: inboundBranchQualifier
+ offset = buffer.position();
+ length = (inboundBranchQualifier == null) ? 0
+ : inboundBranchQualifier.length;
+ if (length > 0)
+ buffer.put(inboundBranchQualifier);
+ buffer.putShort(recordLen - CHKSUM_LEN - SIZEOF_SHORT, (short) length)
+ .putShort(recordLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY, (short) offset);
+
+ // varField 1: xaResourceHeuristics
+ offset = buffer.position();
+ length = (xaResourceHeuristics == null) ? 0
+ : xaResourceHeuristics.length;
+ if (length > 0)
+ {
+ byte[] xaResHeurCodes = new byte[length];
+ for (int i = 0; i < length; i++)
+ xaResHeurCodes[i] = (byte) xaResourceHeuristics[i];
+ buffer.put(xaResHeurCodes);
+ }
+ buffer.putShort(recordLen - CHKSUM_LEN
+ - SIZEOF_SHORT - SIZEOF_DIR_ENTRY,
+ (short) length)
+ .putShort(recordLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY,
+ (short) offset);
+
+ // varField 2, ... : remoteResourceHeuristics
+ for (int i = 0 ; i < remoteResourceHeuristicsCount; i++)
+ {
+ String resourceRef = remoteResourceHeuristics[i].resourceRef;
+ offset = buffer.position();
+ length = resourceRef.length() + 1;
+ byte[] heurStatus = new byte[length];
+ heurStatus[0] = (byte) remoteResourceHeuristics[i].code;
+ resourceRef.getBytes(0, resourceRef.length(), heurStatus, 1);
+ buffer.put(heurStatus)
+ .putShort(recordLen - CHKSUM_LEN
+ - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * (i + 2),
+ (short) length)
+ .putShort(recordLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * (i + 2),
+ (short) offset);
+ }
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+ buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Creates a heuristic forgotten record for a transaction.
+ *
+ * @param localTransactionId the local id of the transaction.
+ * @return a <code>ByteBuffer</code> containing the heuristic forgotten
+ * record. The buffer position is set to zero and the buffer limit
+ * is set to the number of bytes in the heuristic forgotten record.
+ */
+ static ByteBuffer createHeurForgottenRecord(long localTransactionId)
+ {
+ ByteBuffer buffer = ByteBuffer.allocate(HEUR_FORGOTTEN_LEN);
+
+ buffer.put(HEADER)
+ .putShort((short) (HEUR_FORGOTTEN_LEN - FULL_HEADER_LEN))
+ .putShort((short) -(HEUR_FORGOTTEN_LEN - FULL_HEADER_LEN))
+ .put(HEUR_FORGOTTEN)
+ .putLong(localTransactionId);
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(),
+ FULL_HEADER_LEN,
+ SIZEOF_BYTE + SIZEOF_LONG);
+ buffer.putInt(TX_END_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+ return (ByteBuffer) buffer.position(0);
+ }
+
+ /**
+ * Fills out a <code>Data</code> structure with the information taken from
+ * the log record in a given <code>ByteBuffer</code>. The log record starts
+ * at the beginning of the buffer. It cannot be a log record with heuristic
+ * information (i.e., its type cannot be <code>HEUR_STATUS</code> or
+ * <code>HEUR_FORGOTTEN</code>). Its length is known in advance and may
+ * be smaller than the number of bytes in the buffer. The magic header and
+ * record length fields are not included in the <code>ByteBuffer</code>,
+ * whose first byte is the record type of the log record.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing the part of a log
+ * record that starts at the record type field (which is the
+ * first byte of the <code>ByteBuffer</code> and goes until
+ * the checksum field (which is included in the
+ * <code>ByteBuffer</code>
+ * @param recLen the length of the log record in the buffer
+ * @param data a <code>Data</code> structure to be filled out with the
+ * information extracted from the log record.
+ */
+ static void getData(ByteBuffer buffer, int recLen, Data data)
+ {
+ short gidLength;
+ short countOfDirEntries;
+
+ if (recLen > buffer.limit())
+ return; // TODO: throw an exception in this case
+
+ int checksumField = buffer.getInt(recLen - CHKSUM_LEN);
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(), 0, recLen - CHKSUM_LEN);
+
+ if ((int) checksum.getValue() != checksumField)
+ {
+ throw new CorruptedLogRecordException("Wrong checksum.");
+ }
+
+ data.recordType = buffer.get();
+
+ switch (data.recordType)
+ {
+ case MULTI_TM_TX_COMMITTED:
+ data.localTransactionId = buffer.getLong();
+ countOfDirEntries = buffer.getShort();
+
+ // Get varField 0, ..., varField N (where N = countOfDirEntries - 1)
+ data.resources = new String[countOfDirEntries];
+ for (int i = 0; i < countOfDirEntries; i++)
+ {
+ short length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ short offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+ data.resources[i] = new String(buffer.array(), 0, offset, length);
+ }
+
+ // Nullify unused data fields
+ data.globalTransactionId = null;
+ data.inboundFormatId = -1;
+ data.recoveryCoordinator = null;
+ data.inboundBranchQualifier = null;
+ break;
+
+ case TX_PREPARED:
+ case JCA_TX_PREPARED:
+ data.localTransactionId = buffer.getLong();
+ countOfDirEntries = buffer.getShort();
+ data.inboundFormatId = buffer.getInt();
+ gidLength = buffer.getShort();
+ data.globalTransactionId = new byte[gidLength];
+ buffer.get(data.globalTransactionId);
+
+ // Get lentgh and offset of varField 0:
+ short length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+ short offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+ // (Alternatively we could have set offset to buffer.position(),
+ // as varField 0 cames immediately after the globalTransactionId.)
+
+ if (data.recordType == TX_PREPARED)
+ {
+ // varField 0 is the recovery coordinator
+ data.recoveryCoordinator =
+ new String(buffer.array(), 0, offset, length);
+ data.inboundBranchQualifier = null;
+ }
+ else
+ {
+ // varField 0 is the inbound branch qualifier
+ data.recoveryCoordinator = null;
+ data.inboundBranchQualifier = new byte[length];
+ // At this point (offset == buffer.position),
+ // so the line below is commented out:
+ // buffer.position(offset);
+ buffer.get(data.inboundBranchQualifier);
+ }
+
+ // Get varField 1, ... varField N (where N == countOfDirEntries - 1)
+ data.resources = new String[countOfDirEntries - 1];
+ for (int i = 1; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+ data.resources[i - 1] =
+ new String(buffer.array(), 0, offset, length);
+ }
+ break;
+
+ case TX_COMMITTED:
+ case TX_END:
+ data.localTransactionId = buffer.getLong();
+ // Nullify the remaining data fields
+ data.globalTransactionId = null;
+ data.inboundFormatId = -1;
+ data.recoveryCoordinator = null;
+ data.inboundBranchQualifier = null;
+ data.resources = null;
+ break;
+
+ case HEUR_STATUS:
+ case HEUR_FORGOTTEN:
+ throw new RuntimeException("Log record with unexpected type");
+
+ default:
+ throw new RuntimeException("Log record with invalid type");
+ }
+ }
+
+ /**
+ * Fills out a <code>HeurData</code> structure with the information taken
+ * from the <code>HEUR_STATUS</code> or <code>HEUR_FORGOTTEN</code> log
+ * record in a given <code>ByteBuffer</code>. The log record starts at the
+ * beginning of the buffer. Its length is known in advance and may be
+ * smaller than the number of bytes in the buffer. The magic header and
+ * record length fields are not included in the <code>ByteBuffer</code>,
+ * whose first byte is the record type of the log record.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing the part of a
+ * <code>HEUR_STATUS</code> or <code>HEUR_FORGOTTEN</code> log
+ * record that starts at the record type field (which is the
+ * first byte of the <code>ByteBuffer</code> and goes until
+ * the checksum field (which is included in the
+ * <code>ByteBuffer</code>
+ * @param recLen the length of the log record in the buffer
+ * @param data a <code>HeurData</code> structure to be filled out with the
+ * information extracted from the log record.
+ */
+ static void getHeurData(ByteBuffer buffer, int recLen, HeurData data)
+ {
+ if (recLen > buffer.limit())
+ return; // TODO: throw an exception in this case
+
+ int checksumField = buffer.getInt(recLen - CHKSUM_LEN);
+
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(), 0, recLen - CHKSUM_LEN);
+
+ if ((int) checksum.getValue() != checksumField)
+ {
+ throw new CorruptedLogRecordException("Wrong checksum.");
+ }
+
+ data.recordType = buffer.get();
+
+ switch (data.recordType)
+ {
+ case HEUR_STATUS:
+ data.localTransactionId = buffer.getLong();
+ short countOfDirEntries = buffer.getShort();
+ data.transactionStatus = buffer.get();
+ data.heuristicStatusCode = buffer.get();
+ data.locallyDetectedHeuristicHazard = (buffer.get() != 0);
+ data.foreignTx = (buffer.get() != 0);
+ data.formatId = buffer.getInt();
+ int gidLength = buffer.getShort();
+ data.globalTransactionId = new byte[gidLength];
+ buffer.get(data.globalTransactionId);
+
+ short length, offset;
+
+ // Get lentgh and offset of varField 0:
+ length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+ offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+ // (Alternatively we could have set offset to buffer.position(),
+ // as varField 0 cames immediately after the globalTransactionId.)
+
+ if (length == 0)
+ data.inboundBranchQualifier = null;
+ else
+ {
+ data.inboundBranchQualifier = new byte[length];
+ // At this point (offset == buffer.position),
+ // so the line below is commented out:
+ // buffer.position(offset);
+ buffer.get(data.inboundBranchQualifier);
+ }
+
+ // Get lentgh and offset of varField 1:
+ length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY);
+ offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+
+ if (length == 0)
+ data.xaResourceHeuristics = null;
+ else
+ {
+ byte[] xaResHeurCodes = new byte[length];
+ buffer.position(offset);
+ buffer.get(xaResHeurCodes);
+ data.xaResourceHeuristics = new int[length];
+ for (int i = 0; i < length; i++)
+ data.xaResourceHeuristics[i] = xaResHeurCodes[i];
+ }
+
+ if (countOfDirEntries > 2)
+ data.remoteResourceHeuristics =
+ new HeuristicStatus[countOfDirEntries - 2];
+ else
+ data.remoteResourceHeuristics = null;
+
+ // Get varField 2, ... varField N (where N == countOfDirEntries - 1)
+ for (int i = 2; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+
+ byte remoteResourceHeurCode = buffer.get(offset);
+ String remoteResourceRef =
+ new String(buffer.array(), 0, offset + 1, length - 1);
+ data.remoteResourceHeuristics[i - 2] =
+ new HeuristicStatus(remoteResourceHeurCode, remoteResourceRef);
+ }
+ break;
+
+ case HEUR_FORGOTTEN:
+ data.localTransactionId = buffer.getLong();
+ data.heuristicStatusCode = 0;
+ data.xaResourceHeuristics = null;
+ data.remoteResourceHeuristics = null;
+ break;
+
+ case MULTI_TM_TX_COMMITTED:
+ case TX_PREPARED:
+ case JCA_TX_PREPARED:
+ case TX_COMMITTED:
+ case TX_END:
+ throw new RuntimeException("Log record with unexpected type");
+
+ default:
+ throw new RuntimeException("Log record with invalid type");
+
+ }
+ }
+
+ /**
+ * Gets the lenght of the log record that follows the one at the beginning
+ * of a given buffer. This method assumes that the header, record lenght,
+ * and record length check of the next record follows the current record
+ * in the buffer.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing the header, record
+ * lenght, and record length check of the next log record
+ * @param currentRecordLen the buffer position at which the header starts.
+ * @return the next record length, a short value read from the absolute
+ * buffer position <code>currentRecordLength + HEADER_LEN</code>
+ *
+ */
+ static int getNextRecordLength(ByteBuffer buffer, int currentRecordLength)
+ {
+ buffer.position(currentRecordLength);
+ if (buffer.remaining() < FULL_HEADER_LEN)
+ return 0;
+ else
+ {
+ byte[] header = new byte[HEADER_LEN];
+ buffer.get(header);
+ if (!Arrays.equals(header, HEADER))
+ {
+ if (Arrays.equals(header, NULL_HEADER))
+ return 0;
+ else
+ throw new CorruptedLogRecordException("Invalid header.");
+ }
+ else
+ {
+ short recLen = buffer.getShort();
+ short recLenCheck = buffer.getShort();
+ if (recLenCheck != -recLen)
+ throw new CorruptedLogRecordException("Record lenght check failed.");
+ return recLen;
+
+ }
+ }
+ }
+
+ /**
+ * Receives a byte array containing the part of a log record that follows
+ * the header and verifies if the record has a valid checksum.
+ *
+ * @param buf a byte array containing the part of a log record that starts
+ * with the record type and ends with the checksum, which is
+ * included in the array.
+ * @return true if the checksum is valid, false otherwise.
+ */
+ static boolean hasValidChecksum(byte[] buf)
+ {
+ int bufLen = buf.length;
+ int checksumField = ByteBuffer.wrap(buf).getInt(bufLen - CHKSUM_LEN);
+ Checksum checksum = new Adler32();
+ checksum.update(buf, 0, bufLen - CHKSUM_LEN);
+ return ((int) checksum.getValue() == checksumField);
+ }
+
+ /**
+ * Returs the string representation of the log record in a buffer.
+ *
+ * @param buffer a <code>ByteBuffer</code> containing a log record,
+ * with the buffer position set to zero and the buffer limit
+ * set to the number of bytes in the commit record.
+ * @return a string that describes the log record.
+ */
+ static String toString(ByteBuffer buffer)
+ {
+ short countOfDirEntries;
+ short offset;
+ short length;
+ short gidLen;
+ byte[] gid;
+
+ int recLen = buffer.limit();
+ buffer.position(FULL_HEADER_LEN);
+
+ StringBuffer sb = new StringBuffer("Record Info:\n Type: ");
+ byte recordType = buffer.get();
+
+ switch (recordType)
+ {
+ case MULTI_TM_TX_COMMITTED:
+ sb.append("MULTI_TM_TX_COMMITTED\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ countOfDirEntries = buffer.getShort();
+ if (countOfDirEntries > 0)
+ {
+ sb.append(" Resources:\n");
+ // Get varField 0, ..., varField N (where N = countOfDirEntries - 1)
+ for (int i = 0; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ sb.append(" ");
+ sb.append(new String(buffer.array(), 0, offset, length));
+ sb.append("\n");
+ }
+ }
+ break;
+ case TX_PREPARED:
+ sb.append("TX_PREPARED\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ countOfDirEntries = buffer.getShort();
+ sb.append(" Inbound format id: ");
+ sb.append(buffer.getInt());
+ sb.append("\n");
+ gidLen = buffer.getShort();
+ sb.append(" Global transaction id: ");
+ gid = new byte[gidLen];
+ buffer.get(gid);
+ sb.append(new String(gid, 0));
+ sb.append("\n");
+ sb.append(" Recovery coordinator: ");
+ // Get varField 0
+ length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+ offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+ sb.append(new String(buffer.array(), 0, offset, length));
+ sb.append("\n");
+ if (countOfDirEntries > 1)
+ {
+ sb.append(" Resources:\n");
+ // Get varField 1, ..., varField N (where N = countOfDirEntries - 1)
+ for (int i = 1; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ sb.append(" ");
+ sb.append(new String(buffer.array(), 0, offset, length));
+ }
+ }
+ break;
+ case JCA_TX_PREPARED:
+ sb.append("JCA_TX_PREPARED\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ countOfDirEntries = buffer.getShort();
+ sb.append(" Inbound format id: ");
+ sb.append(buffer.getInt());
+ sb.append("\n");
+ gidLen = buffer.getShort();
+ sb.append(" Global transaction id: ");
+ gid = new byte[gidLen];
+ buffer.get(gid);
+ sb.append(new String(gid, 0));
+ sb.append("\n");
+ sb.append(" Inbound branch qualifier: ");
+ // Get varField 0
+ length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+ offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+ sb.append(new String(buffer.array(), 0, offset, length));
+ sb.append("\n");
+ if (countOfDirEntries > 1)
+ {
+ sb.append(" Resources:\n");
+ // Get varField 1, ..., varField N (where N = countOfDirEntries - 1)
+ for (int i = 1; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ sb.append(" ");
+ sb.append(new String(buffer.array(), 0, offset, length));
+ }
+ }
+ break;
+ case TX_COMMITTED:
+ sb.append("TX_COMMITTED\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ break;
+ case TX_END:
+ sb.append("TX_END\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ break;
+ case HEUR_STATUS:
+ sb.append("HEUR_STATUS\n");
+ sb.append(" Local transaction id: ");
+ sb.append(buffer.getLong());
+ sb.append("\n");
+ countOfDirEntries = buffer.getShort();
+ sb.append(" Transaction status: ");
+ byte status = buffer.get();
+ sb.append(TxUtils.getStatusAsString(status));
+ sb.append("\n");
+ sb.append(" Heuristic status: ");
+ byte heurCode = buffer.get();
+ if (heurCode != 0)
+ sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+ else
+ sb.append("NONE");
+ sb.append("\n");
+ sb.append(" Locally-detected heuristic hazard: ");
+ sb.append((buffer.get() != 0) ? "yes" : "no");
+ sb.append("\n");
+ sb.append(" Foreign transaction: ");
+ boolean foreignTransaction = (buffer.get() != 0);
+ sb.append((foreignTransaction) ? "yes" : "no");
+ sb.append("\n");
+ sb.append((foreignTransaction) ? " Inbound format id: "
+ : " Format id: ");
+ sb.append(buffer.getInt());
+ sb.append("\n");
+ gidLen = buffer.getShort();
+ sb.append(" Global transaction id: ");
+ gid = new byte[gidLen];
+ buffer.get(gid);
+ sb.append(new String(gid, 0));
+ sb.append("\n");
+ // Get varField 0
+ length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+ if (length > 0)
+ {
+ sb.append(" Inbound branch qualifier: ");
+ offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+ sb.append(new String(buffer.array(), 0, offset, length));
+ sb.append("\n");
+ }
+ // Get varField 1
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY);
+ if (length > 0)
+ {
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY);
+ sb.append(" XAResource heuristics:\n");
+ for (int i = 0; i < length; i++)
+ {
+ sb.append(" ");
+ heurCode = buffer.get(offset + i);
+ sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+ sb.append("\n");
+ }
+ }
+ // Get varField 2, ..., varField N (where N = countOfDirEntries - 1)
+ if (countOfDirEntries > 2)
+ {
+ sb.append(" Remote resource heuristics:\n");
+ for (int i = 2; i < countOfDirEntries; i++)
+ {
+ length = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_SHORT
+ - SIZEOF_DIR_ENTRY * i);
+ offset = buffer.getShort(recLen - CHKSUM_LEN
+ - SIZEOF_DIR_ENTRY
+ - SIZEOF_DIR_ENTRY * i);
+ heurCode = buffer.get(offset);
+ sb.append(" ");
+ sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+ sb.append(" - ");
+ sb.append(new String(buffer.array(), 0,
+ offset + 1, length - 1));
+ sb.append("\n");
+ }
+ }
+ break;
+ case HEUR_FORGOTTEN:
+ sb.append("HEUR_FORGOTTEN\n");
+ sb.append("Local transaction id: ");
+ sb.append(buffer.getLong(FULL_HEADER_LEN + SIZEOF_BYTE));
+ sb.append("\n");
+ break;
+ default:
+ sb.append("INVALID\n");
+ break;
+ }
+ buffer.position(0);
+ return sb.toString();
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,119 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+
+/**
+ * This class supports asynchronous restarting of log files. Restarting a log
+ * file means overwriting its entire content with null bytes and returning the
+ * file to the pool of clean log files maintained by a <code>BatchWriter</code>.
+ * A <code>LogRestarter</code> encapsulates a queue of log files to be
+ * restarted and implements a background thread that restarts the log files
+ * in the queue. Its sole purpose is to avoid the delay of cleaning up a log
+ * file.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class LogRestarter
+ implements Runnable
+{
+ /**
+ * Class <code>Logger</code>, for trace messages.
+ */
+ private static Logger errorLog = Logger.getLogger(LogRestarter.class);
+
+ /** This flag can be set to false to stop the log restarter thread. */
+ private boolean running = true;
+
+ /** Queue of <code>BatchLog</code> instances to be restarted. */
+ private List logsToRestart = new ArrayList();
+
+ /**
+ * Takes a log file to be asynchronously restarted.
+ *
+ * @param log a <code>BatchLog</code> instance to be restarted.
+ */
+ synchronized void add(BatchLog log)
+ {
+ logsToRestart.add(log);
+ notify();
+ }
+
+ /**
+ * Stops the log restarter thread.
+ */
+ synchronized void stop()
+ {
+ running = false;
+ notify();
+ }
+
+ /**
+ * The log restarter thread body.
+ */
+ public void run()
+ {
+ BatchLog log;
+
+ while (running)
+ {
+ synchronized (this)
+ {
+ if (logsToRestart.size() > 0)
+ {
+ log = (BatchLog) logsToRestart.remove(0);
+ }
+ else
+ {
+ try
+ {
+ wait();
+ }
+ catch (InterruptedException e)
+ {
+ if (!running)
+ break;
+ }
+ continue;
+ }
+ }
+
+ try
+ {
+ log.restart();
+ }
+ catch (IOException e)
+ {
+ errorLog.error("Error cleaning up transaction log "
+ + log.getFilename(), e);
+ }
+ }
+
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,216 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents one pending "write record" operation to be performed on
+ * a <code>BatchLog</code>.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class PendingWriteRequest
+{
+ /**
+ * Type of a tx committed or tx prepared record for a transaction that
+ * involves multiple TMs.
+ */
+ static final int TYPE_TX_MULTI_TM = 1;
+
+ /**
+ * Type of a tx committed record for a transaction that involves a single TM.
+ */
+ static final int TYPE_TX_SINGLE_TM = 0;
+
+ /**
+ * Type of an tx end record for a transaction that involves a multiple TMs.
+ * There is no tx end record for a transaction that involves a single TM.
+ * */
+ static final int TYPE_END = -1;
+
+ /**
+ * A buffer containing the record to be written to a <code>BatchLog</code>.
+ */
+ private ByteBuffer buffer;
+
+ /**
+ * The <code>BatchLog</code> to which the record should be written.
+ */
+ private BatchLog log;
+
+ /**
+ * A <code>Latch</code> that will be released after the pending write
+ * operation is performed. Whoever actually performs the write operation
+ * is responsible for releasing this latch.
+ */
+ private Latch latch;
+
+ /**
+ * The <code>TxCompletionHandler</code> associated with a commit record or
+ * with a prepare record to be written to a log. The two-phase commit
+ * implementation should invoke the completion handler at the end of the
+ * second phase of the protocol for the transaction that has the pending
+ * commit or prepare record.
+ */
+ private TxCompletionHandler completionHandler;
+
+ /**
+ * Either contains an exception thrown during an attempt to perform the
+ * pending write record operation, or null if no exception was thrown.
+ */
+ private Exception failure;
+
+ /**
+ * Indicates the type of the record to be written to a <code>BatchLog</code>.
+ * Possible values are <code>TYPE_TX_MULTI_TM</code>,
+ * <code>TYPE_TX_SINGLE_TM</code>, and <code>TX_END</code>.
+ *
+ */
+ private int type;
+
+ /**
+ * Creates a <code>PendingWriteRequest</code> for a commit or prepare
+ * record.
+ *
+ * @param buffer a buffer containing the commit or prepare record
+ * @param latch a latch that will be released after the pending write
+ * operation is performed
+ * @param multiTmTransaction true if the record refers to a transaction
+ * that involves multiple TMs (and requires a tx end record),
+ * false if the record refers to a transaction that involves
+ * a single TM (and requires no tx end record).
+ */
+ PendingWriteRequest(ByteBuffer buffer,
+ Latch latch,
+ boolean multiTmTransaction)
+ {
+ this.buffer = buffer;
+ this.latch = latch;
+ this.log = null;
+ type = (multiTmTransaction) ? TYPE_TX_MULTI_TM : TYPE_TX_SINGLE_TM;
+ }
+
+ /**
+ * Creates a <code>PendingWriteRequest</code> for a tx end record to be
+ * written to a given <code>BatchLog</code>. This method takes an argument
+ * that speficies the <code>BatchLog</code> because the tx end record for a
+ * transaction must be written to the same <code>BatchLog</code> as the
+ * transaction's tx committed or tx prepared record.
+ *
+ * @param buffer a buffer containing the tx end record
+ * @param latch a latch that will be released after the pending write
+ * operation is performed
+ * @param log the <code>BatchLog</code> to which the tx end record should
+ * be written.
+ */
+ PendingWriteRequest(ByteBuffer buffer, Latch latch, BatchLog log)
+ {
+ this.buffer = buffer;
+ this.latch = latch;
+ this.log = log;
+ type = TYPE_END;
+ }
+
+ /**
+ * Waits until this pending write record is performed. If an exception
+ * was thrown while attempting to perform the write record, this method
+ * throws a <code>RuntimeException</code> that contains the original
+ * exception.
+ *
+ * @return the completion handler associated with a tx committed or with
+ * a tx prepared record.
+ */
+ TxCompletionHandler waitTilDone()
+ {
+ try
+ {
+ latch.acquire();
+ if (failure != null)
+ throw failure;
+ return completionHandler;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the buffer containing the record to be written.
+ *
+ * @return a <code>ByteBuffer</code> instance containing the record.
+ */
+ ByteBuffer getBuffer()
+ {
+ return buffer;
+ }
+
+ /**
+ * Gets the type of the record to be written.
+ *
+ * @return <code>TYPE_TX_MULTI_TM</code>, or
+ * <code>TYPE_TX_SINGLE_TM</code>, or <code>TX_END</code>.
+ */
+ int getType()
+ {
+ return type;
+ }
+
+ /**
+ * Gets the <code>BatchLog</code> to which the record should be written.
+ *
+ * @return the <code>BatchLog</code> to which the record should be written.
+ */
+ BatchLog getLogger()
+ {
+ return log;
+ }
+
+ /**
+ * Associates a completion handler with the pending tx committed or
+ * tx prepared record.
+ *
+ * @param completionHandler the new completion handler.
+ */
+ void setCompletionHandler(TxCompletionHandler completionHandler)
+ {
+ this.completionHandler = completionHandler;
+ }
+
+ /**
+ * Stores an exception thrown during an attempt to perform the pending
+ * write operation. The method <code>waitTilDone</code> will throw a
+ * a <code>RuntimeException</code> that contains the stored exception.
+ *
+ * @param failure an exception to be stored.
+ */
+ void setFailure(Exception failure)
+ {
+ this.failure = failure;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * Interface that gives access to a XA resource at recovery time.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface Recoverable
+{
+ public String getId();
+
+ public XAResource getResource();
+
+ public Xid[] scan() throws XAException;
+
+ public void cleanupResource();
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,81 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.util.List;
+
+/**
+ * Interface that gives access to the information in a recovery log file.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLogReader
+{
+ /**
+ * Gets the name of the underlying log file.
+ *
+ * @return the name of the log file.
+ */
+ String getLogFileName();
+
+ /**
+ * Gets the branch qualifier string read from the log file.
+ *
+ * @return the branch qualifier read from the log file.
+ */
+ String getBranchQualifier();
+
+ /**
+ * Recovers transaction information from the log file.
+ *
+ * @param committedSingleTmTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * committed single-TM transaction logged to the log file
+ * @param committedMultiTmTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * committed multi-TM transaction that has not yet completed the
+ * second phase of the 2PC protocol when the server crashed
+ * @param inDoubtTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * foreign transaction that arrived at the server via DTM/OTS
+ * context propagation and was in the in-doubt state (i.e.,
+ * replied to prepare with a commit vote but has not yet received
+ * information on the transaction outcome) when the server crashed
+ * @param inDoubtJcaTransactions a <code>List</code> of
+ * <code>LogRecord.Data</code> instances with one element per
+ * foreign transaction that arrived at the server via JCA
+ * transaction inflow and was in the in-doubt state (i.e., replied
+ * to prepare with a commit vote and was waiting for information
+ * on the transaction outcome) when the server crashed.
+ */
+ void recover(List committedSingleTmTransactions,
+ List committedMultiTmTransactions,
+ List inDoubtTransactions,
+ List inDoubtJcaTransactions);
+
+ /**
+ * Removes the underlying log file.
+ */
+ void finishRecovery();
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,155 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * This is the main interface of the recovery logger service.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLogger
+{
+ /**
+ * Should be invoked at the end of the voting phase of the two-phase commit
+ * protocol for a transaction started at this virtual machine.
+ *
+ * @param localTransactionId the local id of the transaction
+ * @param resources an array of stringfied references to the remote
+ * <code>Resource</code> instances that voted commit.
+ * @return a handler that should be invoked at the end of the second phase
+ * of the two-phase commit protocol for the transaction (if the
+ * transaction is not heuristically completed) or after any
+ * heuristic decisions are cleared.
+ */
+ public TxCompletionHandler saveCommitDecision(long localTransactionId,
+ String[] resources);
+
+ /**
+ * Should be invoked at the end of the voting phase of the two-phase commit
+ * protocol for a foreign transaction that arrived at this virtual machine
+ * in a transaction context propagated along with a remote invocation.
+ *
+ * @param localTransactionId the local id assigned to the transaction
+ * @param inboundFormatId the format id field of the incoming
+ * transaction context
+ * @param globalTransactionId the global transaction id field of the
+ * incoming transaction context
+ * @param recoveryCoordinator a stringfied reference to the remote
+ * <code>RecoveryCoordinator</code> instance
+ * @param resources an array of stringfied references to the remote
+ * <code>Resource</code> instances that voted commit.
+ * @return a handler that should be invoked at the end of the second phase
+ * of the two-phase commit protocol for the transaction (if the
+ * transaction is not heuristically completed) or after any
+ * heuristic decisions are cleared.
+ */
+ public TxCompletionHandler savePrepareDecision(long localTransactionId,
+ int inboundFormatId,
+ byte[] globalTransactionId,
+ String recoveryCoordinator,
+ String[] resources);
+
+ /**
+ * Should be invoked at the end of the voting phase of the two-phase commit
+ * protocol for a foreign transaction that arrived at this virtual machine
+ * via JCA transaction inflow.
+ *
+ * @param localTransactionId the local id assigned to the transaction
+ * @param inboundXid the inbound <code>Xid</code>
+ * @param resources an array of stringfied references to the remote
+ * <code>Resource</code> instances that voted commit.
+ * @return a handler that should be invoked at the end of the second phase
+ * of the two-phase commit protocol for the transaction (if the
+ * transaction is not heuristically completed) or after any
+ * heuristic decisions are cleared.
+ */
+ public TxCompletionHandler savePrepareDecision(long localTransactionId,
+ Xid inboundXid,
+ String[] resources);
+
+ /**
+ * Should be invoked for a heuristically completed transaction, at the end
+ * of the second phase of the two-phase commit protocol.
+ *
+ * @param localTransactionId the local id assigned to the transaction
+ * @param foreignTx true for a foreign transaction, false otherwise
+ * @param formatId the format id field of the transaction's <code>Xid</code>
+ * @param globalTransactionId the global id field of the transaction's
+ * <code>Xid</code>
+ * @param inboundBranchQualifier the inbound branch qualifier, in the case
+ * of a foreign transaction that has been imported via JCA
+ * transaction inflow, or null otherwise
+ * @param transactionStatus the transaction status (either
+ * <code>javax.transaction.Status.STATUS_COMMITTED</code> or
+ * <code>javax.transaction.Status.STATUS_ROLLEDBACK</code>)
+ * @param heurStatusCode the heuristic status code, which takes the same
+ * values as the <code>errorCode</code> field of
+ * <code>javax.transaction.xa.XAException</code>
+ * @param locallyDetectedHeuristicHazard true if a heuristic hazard was
+ * detected locally and is still outstanding
+ * @param xaResourceHeuristics array of heuristic status codes for the
+ * <code>XAResource</code> that are in heuristic states
+ * @param remoteResourceHeuristics array of <code>HeuristicStatus</code>
+ * instances for the remote resources that are in heuristic
+ * states.
+ */
+ public void saveHeuristicStatus(long localTransactionId,
+ boolean foreignTx,
+ int formatId,
+ byte[] globalTransactionId,
+ byte[] inboundBranchQualifier,
+ int transactionStatus,
+ int heurStatusCode,
+ boolean locallyDetectedHeuristicHazard,
+ int[] xaResourceHeuristics,
+ HeuristicStatus[] remoteResourceHeuristics);
+
+ /**
+ * Clears the heuristic status of a heuristically completed transaction.
+ *
+ * @param localTransactionId the local id assigned to the transaction
+ */
+ public void clearHeuristicStatus(long localTransactionId);
+
+ /**
+ * Should be invoked at recovery time to obtain an array of reader objects
+ * that access the existing transaction log files.
+ *
+ * @return an array that contains one <code>RecoveryLogReader</code>
+ * instance per transaction log file.
+ */
+ public RecoveryLogReader[] getRecoveryLogs();
+
+ /**
+ * Should be invoked at recovery time to obtain an array of reader objects
+ * that access the existing heuristic status log files.
+ *
+ * @return an array that contains one <code>HeuristicStatusLogReader</code>
+ * instance per heuristic status log file.
+ */
+ public HeuristicStatusLogReader[] getHeuristicStatusLogs();
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,33 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface that gives access to a <code>RecoveryLogger</code> instance.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLoggerInstance
+{
+ RecoveryLogger getInstance();
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1053 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.transaction.Status;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.TxUtils;
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * A <code>RecoveryManager</code> object manages the crash recovery process.
+ * At recovery time, the <code>RecoveryManagerService</code> creates a
+ * <code>RecoveryManager</code> instance that interacts with the
+ * <code>RecoveryLogger</code> to read the transaction logs and identify
+ * the transactions that were active at the time of the crash. The
+ * <code>RecoveryManager</code> knows how to perform crash recovery for
+ * transactions involving <code>XAResource</code>s that correspond to
+ * <code>Recoverable</code> objects known in advance.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryManager
+{
+ /**
+ * Class <code>Logger</code> for trace messages.
+ */
+ private static Logger log = Logger.getLogger(RecoveryManager.class.getName());
+
+ /**
+ * A <code>XAResourceAccess</code> implementation that calls
+ * <code>cleanUpResource</code> on a <code>Recoverable</code> instance
+ * if the <code>Recoverable</code> will not be used anymore. The
+ * last <code>release</code> call on an <code>XAResourceAccess</code>
+ * instance closes the underlying <code>XAConnection</code>.
+ */
+ public static class XAResourceAccessImpl
+ implements XAResourceAccess
+ {
+ private Recoverable recoverable;
+ private int refCount;
+
+ // not public -- called only by the XAResXids constructor (see below)
+ XAResourceAccessImpl(Recoverable recoverable)
+ {
+ this.recoverable = recoverable;
+ refCount = 1;
+ }
+
+ // not public -- called only by the XAWork constructor
+ synchronized XAResourceAccess duplicate()
+ {
+ refCount++;
+ return this;
+ }
+
+ public synchronized void release()
+ {
+ if (refCount <= 0)
+ log.warn("release called, but refCount=" + refCount +
+ ", this=" + this, new Throwable("[Stack trace]"));
+
+ if (--refCount == 0)
+ recoverable.cleanupResource();
+ }
+
+ }
+
+ /**
+ * "Struct class" that groups together a <code>Recoverable</code> object,
+ * the <code>XAResource</code> represented by the <code>Recoverable</code>,
+ * and a set of <code>Xids</code> that still need to be processed.
+ */
+ private static class XAResourceXids
+ {
+ public Recoverable recoverable;
+ public XAResource resource;
+ public XAResourceAccessImpl resourceAccess;
+ public Set xids;
+
+ XAResourceXids(Recoverable recoverable)
+ {
+ this.recoverable = recoverable;
+ this.resource = recoverable.getResource();
+ // Note that the XAResourceAccess constructor is *not* called if a
+ // throwable occurs in recoverable.getResource().
+ this.resourceAccess = new XAResourceAccessImpl(recoverable);
+ this.xids = new HashSet();
+ }
+ }
+
+ /**
+ * This implementation of <code>TxCompletionHandler</code> handles
+ * transaction completion for transactions recreated by the recovery
+ * process.
+ */
+ private static class CompletionHandler
+ implements TxCompletionHandler
+ {
+ /**
+ * The log reader associated with the transaction whose completion
+ * is handled by this <code>CompletionHandler</code>.
+ */
+ private RecoveryLogReader reader;
+
+ /**
+ * Number of pending transactions associated with the log reader.
+ */
+ private int pendingTransactions;
+
+ /**
+ * Creates a new <code>CompletionHandler</code>.
+ *
+ * @param reader log reader associated with the transaction whose
+ * completion will be handled by the new
+ * <code>CompletionHandler</code>
+ * @param pendingTransactions number of pending transactions
+ * associated with the log reader above.
+ */
+ CompletionHandler(RecoveryLogReader reader, int pendingTransactions)
+ {
+ this.reader = reader;
+ this.pendingTransactions = pendingTransactions;
+ }
+
+ /**
+ * Signals the end of the two-phase commit protocol for a committed
+ * transaction. This method should be invoked when the second phase of the
+ * two-phase commit protocol completes successfully.
+ *
+ * @param localTransactionId the local id of the completed transaction.
+ */
+ public void handleTxCompletion(long localTransactionId)
+ {
+ if (--pendingTransactions == 0)
+ reader.finishRecovery();
+ }
+ }
+
+ /**
+ * The Xid factory used by this <code>RecoveryManager</code>.
+ */
+ private XidFactoryBase xidFactory;
+
+ /**
+ * The transaction manager used by this <code>RecoveryManager</code>.
+ */
+ private TxManager txManager;
+
+ /**
+ * The recovery logger used by this <code>RecoveryManager</code>.
+ */
+ private RecoveryLogger recoveryLogger;
+
+ /**
+ * Constructs a new <code>RecoveryManager</code>.
+ *
+ * @param xidFactory the Xid factory that will be used by the new
+ * <code>RecoveryManager</code>
+ * @param txManager the transaction manager that will be used by the new
+ * <code>RecoveryManager</code>
+ * @param recoveryLogger the recovery logger that will be used by the new
+ * <code>RecoveryManager</code>
+ */
+ public RecoveryManager(XidFactoryBase xidFactory,
+ TxManager txManager,
+ RecoveryLogger recoveryLogger)
+ {
+ this.xidFactory = xidFactory;
+ this.txManager = txManager;
+ this.recoveryLogger = recoveryLogger;
+ }
+
+ /**
+ * Performs crash recovery. This method reads the transaction logs,
+ * identifies the transactions that were active at the time of the crash,
+ * and performs crash recovery actions for each such transaction, which
+ * may involve any subset of the <code>XAResource</code>s associated with
+ * a given list of <code>Recoverable</code> objects
+ *
+ * @param rcoverables a list of <code>Recoverable</code> objects whose
+ * <code>XAResource</code>s may be involved in active
+ * transactions.
+ */
+ public void recover(ArrayList recoverables)
+ {
+ Map heuristicallyCompletedTransactions = new HashMap();
+
+ HeuristicStatusLogReader[] heurStatusLogReaders =
+ recoveryLogger.getHeuristicStatusLogs();
+
+ if (heurStatusLogReaders != null)
+ {
+ // Get the heuristically completed transactions
+ // from the existing heuristic status log files
+ for (int i = 0; i < heurStatusLogReaders.length; i++)
+ heurStatusLogReaders[i].recover(heuristicallyCompletedTransactions);
+
+ // Save the heuristic status of those transactions
+ // to the current heuristic status log file
+ Iterator heurIt =
+ heuristicallyCompletedTransactions.keySet().iterator();
+ while (heurIt.hasNext())
+ {
+ Long localId = (Long) heurIt.next();
+ LogRecord.HeurData heurData =
+ (LogRecord.HeurData) heuristicallyCompletedTransactions.get(
+ localId);
+ recoveryLogger.saveHeuristicStatus(
+ heurData.localTransactionId,
+ heurData.foreignTx,
+ heurData.formatId,
+ heurData.globalTransactionId,
+ heurData.inboundBranchQualifier,
+ heurData.transactionStatus,
+ heurData.heuristicStatusCode,
+ heurData.locallyDetectedHeuristicHazard,
+ heurData.xaResourceHeuristics,
+ heurData.remoteResourceHeuristics);
+ }
+
+ // Get rid of the existing heuristic status log files
+ for (int i = 0; i < heurStatusLogReaders.length; i++)
+ heurStatusLogReaders[i].finishRecovery();
+ }
+
+ RecoveryLogReader[] readers = recoveryLogger.getRecoveryLogs();
+ if (readers == null || readers.length == 0)
+ return;
+
+ // Obtain the set of branch qualifiers generated by this TM.
+ Set readerBranchQualifiers = new HashSet();
+ for (int i = 0; i < readers.length; i++)
+ {
+ String branchQualifier = null;
+ try
+ {
+ branchQualifier = readers[i].getBranchQualifier();
+ }
+ catch (Exception e)
+ {
+ log.error("logfile corrupted: "
+ + readers[i].getLogFileName(), e);
+ }
+ readerBranchQualifiers.add(branchQualifier);
+ log.info("will recover transactions with branch qualifier " +
+ branchQualifier +
+ " (logFile: " + readers[i].getLogFileName() + ")");
+ }
+
+ Map toRecoverMap = new HashMap();
+ try
+ {
+ // Populate a map whose keys are Recoverable ids and whose values are
+ // XAResourceXids objects, each of which contains the set of Xids of
+ // the active XA transaction branches that involve a given XAResource
+ // and that require recovery actions.
+ for (int i = 0; i < recoverables.size(); i++)
+ {
+ Recoverable rec = (Recoverable) recoverables.get(i);
+ XAResourceXids xaResXids;
+ try
+ {
+ xaResXids = new XAResourceXids(rec);
+ }
+ catch (Throwable t)
+ {
+ throw new RuntimeException("Unable to getResource: "
+ + rec.getId()
+ + " aborting recovery.", t);
+ }
+ toRecoverMap.put(rec.getId(), xaResXids);
+ try
+ {
+ xaResXids.xids.addAll(pruneXidList(rec.scan(),
+ readerBranchQualifiers,
+ rec.getId()));
+ }
+ catch (XAException e)
+ {
+ // TODO also, what to do if this fails?
+ // Should we go on and still try to recover other resources?
+ throw new RuntimeException("Unable to scan: " + rec.getId(), e);
+ }
+ }
+
+ // Perform the recovery actions.
+ recover(readers, heuristicallyCompletedTransactions, toRecoverMap);
+ }
+ finally
+ {
+ cleanupRecoverables(toRecoverMap.values().iterator());
+ }
+ }
+
+ /**
+ * Performs crash recovery using a given array of log readers and a map
+ * with information on the active XA transaction branches for which
+ * recovery actions must be taken.
+ *
+ * @param readers an array of transaction log readers
+ * @param heuristicallyCompletedTransactions
+ * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids
+ * and whose values are <code>XAResourceXids<code>
+ * objects, each of which contains the set of
+ * <code>Xids</code> for the active XA transaction
+ * branches that involve a given <code>XAResource<code>
+ * and that require recovery actions.
+ */
+ private void recover(RecoveryLogReader[] readers,
+ Map heuristicallyCompletedTransactions,
+ Map toRecoverMap)
+ {
+ boolean presumeRollback = true;
+ CorruptedLogRecordException corruptedLogRecordException = null;
+
+ // Recreate the pending committed and in-doubt transactions, including the
+ // ones that are pending just because they were heuristically completed.
+ for (int i = 0; i < readers.length; i++)
+ {
+ log.info("recovering log file " + readers[i].getLogFileName());
+ List committedSingleTmTransactions = new ArrayList();
+ List committedMultiTmTransactions = new ArrayList();
+ List inDoubtTransactions = new ArrayList();
+ List inDoubtJcaTransactions = new ArrayList();
+
+ try
+ {
+ readers[i].recover(committedSingleTmTransactions,
+ committedMultiTmTransactions,
+ inDoubtTransactions,
+ inDoubtJcaTransactions);
+ }
+ catch (CorruptedLogRecordException e)
+ {
+ log.trace("reader threw CorruptedLogRecordException with " +
+ "disablePresumedRollback=" + e.disablePresumedRollback);
+ corruptedLogRecordException = e;
+ if (corruptedLogRecordException.disablePresumedRollback)
+ presumeRollback = false;
+ }
+
+ int pendingTransactions = committedSingleTmTransactions.size() +
+ committedMultiTmTransactions.size() +
+ inDoubtTransactions.size() +
+ inDoubtJcaTransactions.size();
+
+ if (pendingTransactions == 0)
+ readers[i].finishRecovery();
+ else
+ {
+ CompletionHandler completionHandler =
+ new CompletionHandler(readers[i], pendingTransactions);
+
+ resumePendingTransactions(heuristicallyCompletedTransactions,
+ committedSingleTmTransactions,
+ committedMultiTmTransactions,
+ inDoubtTransactions,
+ inDoubtJcaTransactions,
+ toRecoverMap,
+ completionHandler);
+ }
+ }
+
+ // Recreate the remaining heuristically completed transactions
+ // (these transactions are in the rolledback state).
+ Iterator heurIt = heuristicallyCompletedTransactions.keySet().iterator();
+ while (heurIt.hasNext())
+ {
+ Long localId = (Long) heurIt.next();
+ LogRecord.HeurData heurData =
+ (LogRecord.HeurData) heuristicallyCompletedTransactions.get(localId);
+ heurIt.remove(); // heuristicallyCompletedTransactions.remove(localId)
+ byte[] globalId = heurData.globalTransactionId;
+ List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(heurData,
+ xaResourcesWithHeuristics,
+ null /* no completion handler */);
+ }
+
+ if (!presumeRollback)
+ {
+ log.info("PRESUMED ROLLBACK IS DISABLED DUE TO LOG FILE CORRUPTION.");
+ }
+
+ // Rollback the transactions that remained in the toRecoverMap.
+ // (This is presumed rollback.)
+ Iterator rit = toRecoverMap.values().iterator();
+ while (rit.hasNext())
+ {
+ XAResourceXids xaResXids = (XAResourceXids) rit.next();
+ Iterator it = xaResXids.xids.iterator();
+ while (it.hasNext())
+ {
+ Xid xid = (Xid) it.next();
+ if (!presumeRollback)
+ {
+ log.info("WOULD ROLLBACK " + xidFactory.toString(xid) +
+ " ON RECOVERABLE XAResource " +
+ xaResXids.recoverable.getId() +
+ ", BUT PRESUMED ROLLBACK IS DISABLED");
+ }
+ else
+ {
+ try
+ {
+ xaResXids.resource.rollback(xid);
+ log.info("rolledback " + xidFactory.toString(xid) +
+ " on recoverable XAResource " +
+ xaResXids.recoverable.getId());
+ }
+ catch (XAException e)
+ {
+ log.warn("XAException in recover (when rolling back " +
+ "res " + xaResXids.recoverable.getId() + ", xid=" +
+ xidFactory.toString(xid) + "): errorCode="
+ + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ // TODO: check the errorCode and retry the rollback if
+ // XAER_RMFAIL or XAER_RMFAIL.
+ }
+ }
+ }
+ }
+
+ if (corruptedLogRecordException != null)
+ throw corruptedLogRecordException;
+ }
+
+ /**
+ * Resumes the pending transactions specified by the <code>List</code>
+ * arguments.
+ *
+ * @param committedSingleTmTransactions a list of
+ * <code>LogRecord.Data</code> objects
+ * for the committed single-TM
+ * transactions
+ * @param committedMultiTmTransactions a list of <code>LogRecord.Data</code>
+ * objects for the committed multi-TM
+ * transactions
+ * @param inDoubtTransactions a list of <code>LogRecord.Data</code> objects
+ * for the in-doubt transactions that entered
+ * this virtual machine in transaction contexts
+ * propagated along with remote method invocations
+ * @param inDoubtJcaTransactions a list of <code>LogRecord.Data</code> objects
+ * for the in-doubt transactions that entered
+ * this virtual machine through JCA transaction
+ * inflow
+ * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids and
+ * whose values are <code>XAResourceXids<code> objects,
+ * each of which contains the set of <code>Xids</code>
+ * for all active XA transaction branches that involve
+ * a given <code>XAResource<code> and that require
+ * recovery actions
+ * @param completionHandler a <code>TxCompletionHandler</code> that handles
+ * the completion of all transactions specified by
+ * the preceding arguments.
+ */
+ private void resumePendingTransactions(Map heuristicallyCompletedTransactions,
+ List committedSingleTmTransactions,
+ List committedMultiTmTransactions,
+ List inDoubtTransactions,
+ List inDoubtJcaTransactions,
+ Map toRecoverMap,
+ TxCompletionHandler completionHandler)
+ {
+ Iterator it;
+ LogRecord.Data data;
+ LogRecord.HeurData heurData;
+
+ it = committedSingleTmTransactions.iterator();
+ while (it.hasNext())
+ {
+ data = (LogRecord.Data) it.next();
+ byte[] globalId = data.globalTransactionId;
+ Long localId = new Long(data.localTransactionId);
+ heurData =
+ (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+ if (heurData != null)
+ heuristicallyCompletedTransactions.remove(localId);
+
+ if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+ {
+ List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(heurData,
+ xaResourcesWithHeuristics,
+ completionHandler);
+ }
+ else
+ {
+ // Either heurData is null or it has a
+ // locally-detected heuristic hazard.
+
+ List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+ if (pendingXAWorkList.isEmpty())
+ {
+ if (heurData == null)
+ {
+ completionHandler.handleTxCompletion(data.localTransactionId);
+ }
+ else
+ {
+ // just the locally-detected heuristic hazard
+ txManager.recreateTransaction(heurData,
+ pendingXAWorkList,
+ completionHandler);
+ }
+ }
+ else
+ {
+ txManager.recreateTransaction(data.localTransactionId,
+ pendingXAWorkList,
+ completionHandler,
+ heurData);
+ }
+ }
+ }
+
+ it = committedMultiTmTransactions.iterator();
+ while (it.hasNext())
+ {
+ data = (LogRecord.Data) it.next();
+ byte[] globalId = data.globalTransactionId;
+ Long localId = new Long(data.localTransactionId);
+ heurData =
+ (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+ if (heurData != null)
+ heuristicallyCompletedTransactions.remove(localId);
+
+ if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+ {
+ List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(heurData,
+ xaResourcesWithHeuristics,
+ completionHandler);
+ }
+ else
+ {
+ // Either heurData is null or it has a
+ // locally-detected heuristic hazard.
+
+ List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ pendingXAWorkList,
+ data.resources,
+ completionHandler,
+ heurData);
+ }
+ }
+
+ it = inDoubtTransactions.iterator();
+ while (it.hasNext())
+ {
+ data = (LogRecord.Data) it.next();
+ byte[] globalId = data.globalTransactionId;
+ Long localId = new Long(data.localTransactionId);
+ heurData =
+ (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+ if (heurData != null)
+ heuristicallyCompletedTransactions.remove(localId);
+
+ if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+ {
+ heuristicallyCompletedTransactions.remove(localId);
+ List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(heurData,
+ xaResourcesWithHeuristics,
+ completionHandler);
+ }
+ else
+ {
+ // Either heurData is null or it has a
+ // locally-detected heuristic hazard.
+
+ if (heurData == null)
+ {
+ List preparedXAWorkList = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.recoveryCoordinator,
+ preparedXAWorkList,
+ data.resources,
+ completionHandler,
+ null);
+ }
+ else
+ {
+ // locally-detected heuristic hazard
+ if (heurData.transactionStatus == Status.STATUS_COMMITTING)
+ {
+ List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.recoveryCoordinator,
+ pendingXAWorkList,
+ data.resources,
+ completionHandler,
+ heurData);
+ }
+ else if (heurData.transactionStatus == Status.STATUS_ROLLING_BACK)
+ {
+ List pendingXAWorkList =
+ rollbackXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.recoveryCoordinator,
+ pendingXAWorkList,
+ data.resources,
+ completionHandler,
+ heurData);
+ }
+ else
+ {
+ log.warn("Cannot recover tx=" + toString() +
+ "\nInconsistent state",
+ new Throwable("[Stack trace]"));
+ }
+ }
+ }
+ }
+
+ it = inDoubtJcaTransactions.iterator();
+ while (it.hasNext())
+ {
+ data = (LogRecord.Data) it.next();
+ byte[] globalId = data.globalTransactionId;
+ Long localId = new Long(data.localTransactionId);
+ heurData =
+ (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+ if (heurData != null)
+ heuristicallyCompletedTransactions.remove(localId);
+
+ if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+ {
+ List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(heurData,
+ xaResourcesWithHeuristics,
+ completionHandler);
+ }
+ else
+ {
+ // Either heurData is null or it has a
+ // locally-detected heuristic hazard.
+
+ List preparedXAWorkList = getXAWork(globalId, toRecoverMap);
+ if (heurData == null)
+ {
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.inboundBranchQualifier,
+ preparedXAWorkList,
+ data.resources,
+ completionHandler,
+ null);
+ }
+ else
+ {
+ // locally-detected heuristic hazard
+ if (heurData.transactionStatus == Status.STATUS_COMMITTING)
+ {
+ List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.inboundBranchQualifier,
+ pendingXAWorkList,
+ data.resources,
+ completionHandler,
+ heurData);
+ }
+ else if (heurData.transactionStatus == Status.STATUS_ROLLING_BACK)
+ {
+ List pendingXAWorkList =
+ rollbackXAWork(globalId, toRecoverMap);
+ txManager.recreateTransaction(data.localTransactionId,
+ data.inboundFormatId,
+ data.globalTransactionId,
+ data.inboundBranchQualifier,
+ pendingXAWorkList,
+ data.resources,
+ completionHandler,
+ heurData);
+ }
+ else
+ {
+ log.warn("Cannot recover tx=" + toString() +
+ "\nInconsistent state",
+ new Throwable("[Stack trace]"));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Commits the XA work associated with a given global transaction id. This
+ * method receives a <code>toRecoverMap</code> whose values are
+ * <code>XAResourceXids<code> objects that contain the <code>Xids</code>
+ * of all active XA transaction branches that require recovery actions.
+ * It removes from the <code>toRecoverMap</code> all <code>Xids</code>
+ * that correspond to the XA work committed (those associated with the
+ * specified <code>globalId</code>).
+ *
+ * @param globalId the global transaction id associated with the work to
+ * commit
+ * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids and
+ * whose values are <code>XAResourceXids<code> objects,
+ * each of which contains the set of <code>Xids</code>
+ * for all active XA transaction branches that involve
+ * a given <code>XAResource<code> and that require
+ * recovery actions
+ * @return a "pending work" list containing <code>XAWork</code> instances
+ * describing the work that should have been committed, but could
+ * not be committed due to transient problems. The caller should
+ * try to commit the pending work again, at a later time.
+ */
+ private List commitXAWork(byte[] globalId, Map toRecoverMap)
+ {
+ log.info("*** trying to complete XA work with globalId " +
+ new String(globalId).trim());
+ globalId = pad(globalId);
+ List pendingXAWorkList = new ArrayList();
+ Iterator rit = toRecoverMap.values().iterator();
+ while (rit.hasNext())
+ {
+ XAResourceXids toRecover = (XAResourceXids) rit.next();
+ log.info(" looking at resource " + toRecover.recoverable.getId());
+
+ Iterator resXidIt = toRecover.xids.iterator();
+ while (resXidIt.hasNext())
+ {
+ Xid resXid = (Xid) resXidIt.next();
+ byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+ if (!Arrays.equals(globalId, resGlobalId))
+ continue;
+ try
+ {
+ toRecover.resource.commit(resXid, false);
+ log.info(" committed: " + resXid);
+ }
+ catch (XAException e)
+ {
+ switch (e.errorCode)
+ {
+ case XAException.XA_HEURCOM:
+ // Ignore this exception, as the the heuristic outcome
+ // is the one we wanted anyway.
+ log.trace("commitXAWork ignored XAException.XA_HEURCOM", e);
+ try
+ {
+ toRecover.resource.forget(resXid);
+ }
+ catch (XAException xae)
+ {
+ log.warn("XAException in commitXAWork (when forgetting " +
+ "XA_HEURCOM): errorCode=" +
+ TxUtils.getXAErrorCodeAsString(xae.errorCode),
+ xae);
+ }
+ break;
+ case XAException.XA_HEURRB:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ log.warn("Heuristic XAException in commitXAWork: errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode) +
+ "\nWill deal with the heuristic later", e);
+ XAWork postponedWork = new XAWork(toRecover.resource,
+ resXid,
+ toRecover.resourceAccess);
+ pendingXAWorkList.add(postponedWork);
+ break;
+ case XAException.XAER_RMERR:
+ log.warn("Unexpected XAException in commitXAWork: errorCode="
+ + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ log.warn("XAException in commitXAWork: errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode) +
+ "\nWill attempt to commit the XAResource later", e);
+ XAWork pendingXAWork = new XAWork(toRecover.resource,
+ resXid,
+ toRecover.resourceAccess);
+ pendingXAWorkList.add(pendingXAWork);
+ break;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // This should never happen!
+ log.warn("Could not recover from unexpected XAException: " +
+ " errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ }
+ }
+ finally
+ {
+ resXidIt.remove(); // remove resXid from toRecover.xids
+ }
+ }
+ if (toRecover.xids.isEmpty())
+ rit.remove(); // remove toRecover from toRecoverMap
+ }
+ return pendingXAWorkList;
+ }
+
+ private List rollbackXAWork(byte[] globalId, Map toRecoverMap)
+ {
+ log.info("*** trying to rollback XA work with globalId " +
+ new String(globalId).trim());
+ globalId = pad(globalId);
+ List pendingXAWorkList = new ArrayList();
+ Iterator rit = toRecoverMap.values().iterator();
+ while (rit.hasNext())
+ {
+ XAResourceXids toRecover = (XAResourceXids) rit.next();
+ log.info(" looking at resource " + toRecover.recoverable.getId());
+
+ Iterator resXidIt = toRecover.xids.iterator();
+ while (resXidIt.hasNext())
+ {
+ Xid resXid = (Xid) resXidIt.next();
+ byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+ if (!Arrays.equals(globalId, resGlobalId))
+ continue;
+ try
+ {
+ toRecover.resource.rollback(resXid);
+ log.info(" rolledback: " + resXid);
+ }
+ catch (XAException e)
+ {
+ switch (e.errorCode)
+ {
+ case XAException.XA_HEURRB:
+ // Ignore this exception, as the the heuristic outcome
+ // is the one we wanted anyway.
+ log.trace("rollbackXAWork ignored XAException.XA_HEURRB", e);
+ try
+ {
+ toRecover.resource.forget(resXid);
+ }
+ catch (XAException xae)
+ {
+ log.warn("XAException in rollbackXAWork (when forgetting "
+ + "XA_HEURRB): errorCode=" +
+ TxUtils.getXAErrorCodeAsString(xae.errorCode),
+ xae);
+ }
+ break;
+ case XAException.XA_HEURCOM:
+ case XAException.XA_HEURMIX:
+ case XAException.XA_HEURHAZ:
+ log.warn("Heuristic XAException in rollbackXAWork: errorCode="
+ + TxUtils.getXAErrorCodeAsString(e.errorCode) +
+ "\nWill deal with the heuristic later", e);
+ XAWork postponedWork = new XAWork(toRecover.resource,
+ resXid,
+ toRecover.resourceAccess);
+ pendingXAWorkList.add(postponedWork);
+ break;
+ case XAException.XAER_RMERR:
+ log.warn("Unexpected XAException in rollbackXAWork: " +
+ "errorCode="
+ + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ case XAException.XAER_RMFAIL:
+ case XAException.XA_RETRY:
+ log.warn("XAException in rollbackXAWork: errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode) +
+ "\nWill attempt to rollback the XAResource later", e);
+ XAWork pendingXAWork = new XAWork(toRecover.resource,
+ resXid,
+ toRecover.resourceAccess);
+ pendingXAWorkList.add(pendingXAWork);
+ break;
+ case XAException.XAER_NOTA:
+ case XAException.XAER_INVAL:
+ case XAException.XAER_PROTO:
+ default:
+ // This should never happen!
+ log.warn("Could not recover from unexpected XAException: " +
+ " errorCode=" +
+ TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+ break;
+ }
+ }
+ finally
+ {
+ resXidIt.remove(); // remove resXid from toRecover.xids
+ }
+ }
+ if (toRecover.xids.isEmpty())
+ rit.remove(); // remove toRecover from toRecoverMap
+ }
+ return pendingXAWorkList;
+ }
+
+ /**
+ * Extracts from a <code>toRecoverMap</code> all the XA work that is
+ * associated with a given global transaction id. This method scans
+ * the <code>toRecoverMap</code> and builds a list of <code>XAWork</code>
+ * instances whose <code>Xids</code> contain the specified global id.
+ * It removes all those <code>Xids</code> from the the
+ * <code>toRecoverMap</code>.
+ *
+ * @param globalId the global transaction id
+ * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids
+ * and whose values are <code>XAResourceXids<code>
+ * objects, each of which contains the set of
+ * <code>Xids</code> for the active XA transaction
+ * branches that involve a given <code>XAResource<code>
+ * and that require recovery actions.
+ * @return a <code>List</code> of <code>XAWork</code> instances with
+ * <code>Xid</code> fields that were taken from the
+ * <code>toRecoverMap</code> and that contain the specified global
+ * transaction id.
+ */
+ private List getXAWork(byte[] globalId, Map toRecoverMap)
+ {
+ log.info("*** getting XA work with globalId " +
+ new String(globalId).trim());
+ globalId = pad(globalId);
+ List xaWorkList = new ArrayList();
+ Iterator rit = toRecoverMap.values().iterator();
+ while (rit.hasNext())
+ {
+ XAResourceXids toRecover = (XAResourceXids) rit.next();
+ log.info(" looking at resource " + toRecover.recoverable.getId());
+
+ Iterator resXidIt = toRecover.xids.iterator();
+ while (resXidIt.hasNext())
+ {
+ Xid resXid = (Xid) resXidIt.next();
+ byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+ if (!Arrays.equals(globalId, resGlobalId))
+ continue;
+
+ XAWork preparedXAWork = new XAWork(toRecover.resource,
+ resXid,
+ toRecover.resourceAccess);
+ xaWorkList.add(preparedXAWork);
+ resXidIt.remove(); // remove resXid from toRecover.xids
+ }
+ }
+ return xaWorkList;
+ }
+
+ /**
+ * Takes an iterator for a collection of <code>XAResourceXids</code>
+ * instances and cleans up every <code>Recoverable</code> in the
+ * <code>recoverable</code> field of an element of the collection.
+ */
+ private void cleanupRecoverables(Iterator it)
+ {
+ while (it.hasNext())
+ {
+ XAResourceXids xaResXids = (XAResourceXids) it.next();
+ try
+ {
+ xaResXids.resourceAccess.release();
+ }
+ catch (Exception ignored)
+ {
+ }
+ }
+ }
+
+ /**
+ * Filters out every xid whose branch qualifier field was not generated by
+ * transaction manager. This is to avoid rolling back transaction branches
+ * that are not ours.
+ */
+ private List pruneXidList(Xid[] xids,
+ Set branchQualifiers,
+ String resourceName)
+ {
+ ArrayList list = new ArrayList();
+ for (int i = 0; i < xids.length; i++)
+ {
+ byte[] branchQual = xids[i].getBranchQualifier();
+ String baseBranchQual = xidFactory.getBaseBranchQualifier(branchQual);
+ if (branchQualifiers.contains(baseBranchQual))
+ {
+ list.add(xids[i]);
+ log.info("Adding xid " + xidFactory.toString(xids[i]) +
+ " to pruned Xid list for " + resourceName);
+
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Pads a byte array with null bytes so that the length of the padded
+ * array is <code>Xid.MAXGTRIDSIZE</code>. Called before comparing
+ * global transaction ids.
+ *
+ */
+ private byte[] pad(byte[] globalId)
+ {
+ if (globalId.length < Xid.MAXGTRIDSIZE)
+ {
+ byte[] bytes = new byte[Xid.MAXGTRIDSIZE];
+ System.arraycopy(globalId, 0, bytes, 0, globalId.length);
+ globalId = bytes;
+ }
+ return globalId;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,183 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.util.ArrayList;
+
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.system.server.Server;
+import org.jboss.system.server.ServerImplMBean;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Service MBean that manages crash recovery.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryManagerService
+ extends ServiceMBeanSupport
+ implements NotificationListener,
+ RecoveryManagerServiceMBean
+{
+ private ObjectName xidFactory;
+ private ObjectName txManager;
+ private RecoveryLogger recoveryLogger;
+ private XidFactoryMBean xidFactoryObj;
+ private TxManager txManagerObj;
+ private ArrayList xaResourceManagers = new ArrayList();
+
+ // TODO make pluggable
+ private RecoveryManager recoveryManager;
+
+ // ServiceMBeanSupport overrides ---------------------------------
+
+ /**
+ * @see org.jboss.system.ServiceMBeanSupport#startService()
+ */
+ protected void startService() throws Exception
+ {
+ super.startService();
+ xidFactoryObj =
+ (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+ txManagerObj =
+ (TxManager) getServer().getAttribute(txManager, "TransactionManager");
+ txManagerObj.setRecoveryLogger(recoveryLogger);
+
+ NotificationFilter filter = new NotificationFilter()
+ {
+ private static final long serialVersionUID = 1L;
+
+ public boolean isNotificationEnabled(Notification n)
+ {
+ return n.getType().equals(Server.START_NOTIFICATION_TYPE);
+ }
+ };
+
+ this.getServer().addNotificationListener(ServerImplMBean.OBJECT_NAME,
+ this,
+ filter,
+ null);
+ }
+
+ /**
+ * @see org.jboss.system.ServiceMBeanSupport#stopService()
+ */
+ protected void stopService() throws Exception
+ {
+ super.stopService();
+ }
+
+ // NotificationListener implementation ---------------------------
+
+ /**
+ * @see javax.management.NotificationListener#handleNotification(
+ * javax.management.Notification, java.lang.Object)
+ */
+ public void handleNotification(Notification notification, Object handback)
+ {
+ log.info("RECEIVED STARTUP NOTIFICATION");
+ if (/* resources.size() > 0 && */ recoveryLogger != null)
+ {
+ recover();
+ }
+ txManagerObj.clearRecoveryPending();
+ }
+
+ // RecoveryManagerServiceMBean implementation --------------------
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#getXidFactory()
+ */
+ public ObjectName getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setXidFactory(
+ * javax.management.ObjectName)
+ */
+ public void setXidFactory(ObjectName xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#getTransactionManager()
+ */
+ public ObjectName getTransactionManager()
+ {
+ return txManager;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setTransactionManager(
+ * javax.management.ObjectName)
+ */
+ public void setTransactionManager(ObjectName txManager)
+ {
+ this.txManager = txManager;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setRecoveryLogger(
+ * org.jboss.tm.recovery.RecoveryLoggerInstance)
+ */
+ public void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger)
+ {
+ this.recoveryLogger = recoveryLogger.getInstance();
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#recover()
+ */
+ public void recover()
+ {
+ try
+ {
+ recoveryManager =
+ new RecoveryManager(xidFactoryObj, txManagerObj, recoveryLogger);
+ recoveryManager.recover(xaResourceManagers);
+ }
+ catch (Exception e)
+ {
+ log.error("Unable to recover", e);
+ }
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#registerRecoverable(
+ * org.jboss.tm.recovery.Recoverable)
+ */
+ public void registerRecoverable(Recoverable recoverable)
+ {
+ xaResourceManagers.add(recoverable);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,84 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of the recovery manager service.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryManagerServiceMBean
+ extends ServiceMBean
+{
+ /**
+ * Gets the Xid factory's object name.
+ *
+ * @return the Xid factory's object name.
+ */
+ ObjectName getXidFactory();
+
+ /**
+ * Sets the Xid factory's object name.
+ *
+ * @param xidFactory the Xid factory's object name.
+ */
+ void setXidFactory(ObjectName xidFactory);
+
+ /**
+ * Gets the transaction manager's object name.
+ *
+ * @return the transaction manager's object name.
+ */
+ ObjectName getTransactionManager();
+
+ /**
+ * Sets the transaction manager's object name.
+ *
+ * @param txManager the transaction manager's object name.
+ */
+ void setTransactionManager(ObjectName txManager);
+
+ /**
+ * Sets the recovery logger.
+ *
+ * @param recoveryLogger a <code>RecoveryLoggerInstance</code>.
+ */
+ void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger);
+
+ /**
+ * Registers a <code>Recoverable</code> instance with the recovery manager
+ * service.
+ *
+ * @param recoverable the <code>Recoverable</code> instance to be registered.
+ */
+ void registerRecoverable(Recoverable recoverable);
+
+ /**
+ * Performs crash recovery.
+ */
+ void recover();
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ * This exception is used by the recovery testing framework to force the TM to abort at
+ * certain points so that recovery logging can be tested.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryTestingException extends RuntimeException
+{
+ private static final long serialVersionUID = 1L;
+
+ public RecoveryTestingException()
+ {
+ }
+
+ public RecoveryTestingException(String message)
+ {
+ super(message);
+ }
+
+ public RecoveryTestingException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+
+ public RecoveryTestingException(Throwable cause)
+ {
+ super(cause);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,155 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Map;
+
+/**
+ * Simple implementation of <code>HeuristicStatusLogReader</code> used at
+ * recovery time. The <code>BatchRecoveryLogger</code>'s implementation of
+ * method <code>getHeuristicStatusLogs()</code> instantiates
+ * <code>SimpleHeuristicStatusLogReader</code>s for the existing heuristic
+ * status log files. It returns an array containing those readers, which the
+ * recovery manager uses to get information on heuristically completed
+ * transactions.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class SimpleHeuristicStatusLogReader
+ implements HeuristicStatusLogReader
+{
+ /** The underlying heuristic status log file. */
+ private File logFile;
+
+ /**
+ * Constructs a <code>SimpleHeuristicStatusLogReader</code>.
+ *
+ * @param logFile the heuristic status log file to read.
+ */
+ public SimpleHeuristicStatusLogReader(File logFile)
+ {
+ this.logFile = logFile;
+ }
+
+ /**
+ * Gets the name of the heuristic status log file.
+ *
+ * @return the name of the heuristic status log file.
+ */
+ public String getLogFileName()
+ {
+ return logFile.toString();
+ }
+
+ /**
+ * Recovers information on heuristically completed transactions from
+ * the heuristic status log file.
+ *
+ * @param heuristicallyCompletedTransactions a <code>Map</code> to which
+ * this method will one entry per heuristically completed
+ * transaction. The map keys are <code>Long</code> values
+ * containing local transaction ids. The map values are
+ * <code>LogRecord.HeurData</code> objects with information
+ * on heuristically completed transactions.
+ */
+ public void recover(Map heuristicallyCompletedTransactions)
+ {
+ FileInputStream fis;
+
+ try
+ {
+ fis = new FileInputStream(logFile);
+
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ try
+ {
+ if (fis.available() < LogRecord.FULL_HEADER_LEN)
+ return; // TODO: perhaps thrown an exception in this case?
+
+ FileChannel channel = fis.getChannel();
+ ByteBuffer buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+ channel.read(buf);
+
+ int len = LogRecord.getNextRecordLength(buf, 0);
+ LogRecord.HeurData data = new LogRecord.HeurData();
+
+ while (len > 0)
+ {
+ buf = ByteBuffer.allocate(len + LogRecord.FULL_HEADER_LEN);
+ if (channel.read(buf) < len)
+ break; // TODO: throw an exception or log something
+ buf.flip();
+ LogRecord.getHeurData(buf, len, data);
+ switch (data.recordType)
+ {
+ case LogRecord.HEUR_STATUS:
+ heuristicallyCompletedTransactions.put(
+ new Long(data.localTransactionId),
+ data);
+ break;
+
+ case LogRecord.HEUR_FORGOTTEN:
+ heuristicallyCompletedTransactions.remove(
+ new Long(data.localTransactionId));
+ break;
+
+ default:
+ // TODO: log something
+ break;
+ }
+ len = LogRecord.getNextRecordLength(buf, len);
+ }
+ }
+ catch (IOException ignore)
+ {
+ }
+ try
+ {
+ fis.close();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ /**
+ * Removes the heuristic status log file.
+ */
+ public void finishRecovery()
+ {
+ logFile.delete();
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,73 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This <code>TxCompletionHandler</code> implementation writes
+ * <code>TX_END</code> records to a <code>BatchLog</code>. At transaction
+ * completion, a <code>TX_END</code> record is written out to the
+ * <code>BatchLog</code>.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class TransactionCompletionLogger implements TxCompletionHandler
+{
+ /** The underlying <code>BatchLog</code> instance. */
+ private BatchLog log;
+
+ /**
+ * Constructs a <code>TransactionCompletionLogger</code> that writes
+ * <code>TX_END</code> records to a given <code>BatchLog</code>.
+ *
+ * @param log the <code>BatchLog</code> to which <code>TX_END</code>
+ * records will be written out.
+ */
+ TransactionCompletionLogger(BatchLog log)
+ {
+ this.log = log;
+ }
+
+ /**
+ * Signals the end of the two-phase commit protocol for a committed
+ * transaction. This method should be invoked when the second phase of the
+ * two-phase commit protocol completes successfully and no heuristic
+ * decisions were made. In the case of a heuristically committed transaction,
+ * this method should not be invoked at the end of the second phase of the
+ * two-phase commit protocol. Instead, the <code>handleTxCompletion</code>
+ * call should be postponed until the heuristic outcome is forgotten.
+ *
+ * This implementation writes to the <code>BatchLog</code> a
+ * <code>TX_END</code> record for the transaction.
+ *
+ * @param localTransactionId the local id of the completed transaction.
+ */
+ public void handleTxCompletion(long localTransactionId)
+ {
+ ByteBuffer buffer = LogRecord.createTxEndRecord(localTransactionId);
+ BatchWriter writer = log.getBatchWriter();
+ writer.addBatch(buffer, log);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,46 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface of an object that should be invoked at transaction completion.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface TxCompletionHandler
+{
+
+ /**
+ * Signals the end of the two-phase commit protocol for a committed
+ * transaction. This method should invoked when the second phase of the
+ * two-phase commit protocol completes successfully and no heuristic
+ * decisions were made. In the case of a heuristically committed transaction,
+ * this method should not be invoked at the end of the second phase of the
+ * two-phase commit protocol. Instead, the <code>handleTxCompletion</code>
+ * call should be postponed until the heuristic outcome is forgotten.
+ *
+ * @param localTransactionId the local id of the completed transaction.
+ */
+ void handleTxCompletion(long localTransactionId);
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,40 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface with a single method for releasing the access to an
+ * <code>XAResource</code>. The holder of an <code>XAResourceAccess<code>
+ * instance has access to some <code>XAResource</code> through a connection
+ * to an XA datasource. When it finishes using the <code>XAResource</code>,
+ * it must call <code>release</code> on the <code>XAResourceAccess<code>
+ * instance. If no other object has access to the <code>XAResource</code>,
+ * then the <code>relase</code> call closes the underlying
+ * <code>XAConnection</code>.
+ *
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface XAResourceAccess
+{
+ void release();
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,64 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * "Struct class" that represents the work to be performed by a given XA
+ * resource for a given transaction branch.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class XAWork
+{
+ /** The XAResource. */
+ public XAResource res;
+
+ /** The transaction branch identifier. */
+ public Xid xid;
+
+ /** The XAResourceAccess to be released when the work is completed. */
+ public XAResourceAccess xaResourceAccess;
+
+ /**
+ * Constructs an <code>XAWork</code> instance given an
+ * <code>XAResource</code>, a transaction branch identifier, and
+ * an <code>XAResourceAccess</code> instance.
+ *
+ * @param res the <code>XAResource</code>
+ * @param xid the transaction branch identifier.
+ * @param xaResourceAccess the <code>XAResourceAccess</code> instance
+ * to be released when the work is completed.
+ */
+ XAWork(XAResource res,
+ Xid xid,
+ RecoveryManager.XAResourceAccessImpl xaResourceAccess)
+ {
+ this.res = res;
+ this.xid = xid;
+ this.xaResourceAccess = xaResourceAccess.duplicate();
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,166 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.LocalId;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * MBean service that distinguishes "transaction generations", which correspond
+ * to different executions of a JBoss server, by stuffing a "transaction
+ * generation number" into the high part of all the local transaction ids
+ * created by an execution of the server. This service keeps the next
+ * transaction generation number (the value that the next server run will stuff
+ * into the high part of its local transaction ids) in a file, which it reads
+ * and updates at server startup time.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class XidFactoryInitializationService
+ extends ServiceMBeanSupport
+ implements XidFactoryInitializationServiceMBean
+{
+ private ObjectName xidFactory;
+ private String filename;
+ private int nextTxGenerationNumber = 0;
+
+ // ServiceMBeanSupport override ----------------------------------
+
+ /**
+ * @see org.jboss.system.ServiceMBeanSupport#startService()
+ */
+ protected void startService()
+ throws Exception
+ {
+ super.startService();
+ XidFactoryMBean xidFactoryObj =
+ (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+
+ File nextTxGenerationFile = new File(filename);
+ nextTxGenerationFile = nextTxGenerationFile.getAbsoluteFile();
+ if (!nextTxGenerationFile.createNewFile())
+ {
+ // Read existing file.
+ InputStream in = new FileInputStream(nextTxGenerationFile);
+ DataInputStream dataIn = new DataInputStream(in);
+ int txGenNumberFromFile = dataIn.readInt();
+ int checksumFromFile = dataIn.readInt();
+ dataIn.close();
+
+ // Verify the checksum.
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.putInt(txGenNumberFromFile);
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(), 0, 4);
+ if ((int) checksum.getValue() != checksumFromFile)
+ throw new RuntimeException("Incorrect checksum in file " +
+ nextTxGenerationFile + ". Could not " +
+ "obtain the next transaction " +
+ "generation number.");
+
+ // Rename existing file
+ File backupFile = new File(filename + ".bak");
+ backupFile.delete();
+ nextTxGenerationFile.renameTo(backupFile);
+ nextTxGenerationNumber = txGenNumberFromFile;
+ }
+
+ // Set the transaction generation in the Xid factory.
+ xidFactoryObj.setGlobalIdNumber(LocalId.assemble(nextTxGenerationNumber,
+ 0 /* tx number */));
+
+ // Increment and save the next transaction generation number.
+ nextTxGenerationNumber++;
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.putInt(nextTxGenerationNumber);
+ Checksum checksum = new Adler32();
+ checksum.update(buffer.array(), 0, 4);
+
+ FileOutputStream out= new FileOutputStream(nextTxGenerationFile);
+ DataOutputStream dataOut = new DataOutputStream(out);
+ dataOut.writeInt(nextTxGenerationNumber);
+ dataOut.writeInt((int) checksum.getValue());
+ dataOut.flush();
+ out.getFD().sync();
+ dataOut.close();
+ }
+
+ // XidFactoryInitializationServiceMBean implementation -----------
+
+ /**
+ * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getXidFactory()
+ */
+ public ObjectName getXidFactory()
+ {
+ return xidFactory;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#setXidFactory(
+ * javax.management.ObjectName)
+ */
+ public void setXidFactory(ObjectName xidFactory)
+ {
+ this.xidFactory = xidFactory;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getNextTxGenerationFile()
+ */
+ public String getNextTxGenerationFile()
+ {
+ return filename;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#setNextTxGenerationFile(
+ * java.lang.String)
+ */
+ public void setNextTxGenerationFile(String filename)
+ {
+ this.filename = filename;
+ }
+
+ /**
+ * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getNextTxGenerationNumber()
+ */
+ public int getNextTxGenerationNumber()
+ {
+ return nextTxGenerationNumber;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,53 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of a service that distinguishes "transaction generations",
+ * which correspond to different executions of a JBoss server, by stuffing
+ * a "transaction generation number" into the high part of all the local
+ * transaction ids created by an execution of the server. This service keeps
+ * the next transaction generation number (the value that the next server run
+ * will stuff into the high part of its local transaction ids) in a file, which
+ * it reads and updates at server startup time.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface XidFactoryInitializationServiceMBean
+ extends ServiceMBean
+{
+ ObjectName getXidFactory();
+
+ void setXidFactory(ObjectName xidFactory);
+
+ String getNextTxGenerationFile();
+
+ void setNextTxGenerationFile(String filename);
+
+ int getNextTxGenerationNumber();
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,299 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery.test;
+
+import org.jboss.tm.recovery.BatchRecoveryLogger;
+import org.jboss.tm.recovery.BatchWriter;
+import org.jboss.tm.recovery.RecoveryLogTerminator;
+import org.jboss.tm.XidFactory;
+
+import java.io.RandomAccessFile;
+import java.io.IOException;
+import java.io.File;
+import java.nio.channels.FileChannel;
+import java.nio.ByteBuffer;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * benches file per thread vs. batch queue
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class TestForceTime
+{
+ private static final int RECORD_SIZE = Xid.MAXGTRIDSIZE * 2;
+ private static int NUM_DIRS = 2;
+ private static final int ITERATIONS = 100;
+ private static int numThreads = 500;
+ private static long average;
+ private static int count;
+ private static long startTime;
+ private static XidFactory factory = new XidFactory();
+ private static int tx_per_sec;
+
+
+ private static class BatchThread implements Runnable
+ {
+ private BatchRecoveryLogger logger;
+ private int id;
+
+ public BatchThread(int id, BatchRecoveryLogger logger)
+ {
+ this.logger = logger;
+ this.id = id;
+ }
+
+ public void run()
+ {
+ synchronized (startLock)
+ {
+ try
+ {
+ startLock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ try
+ {
+ //System.out.println("Starting " + id);
+
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < ITERATIONS; i++)
+ {
+ Xid xid = factory.newXid();
+ RecoveryLogTerminator term = logger.committing(xid);
+ term.committed(xid);
+ }
+ long time = System.currentTimeMillis() - start;
+ //System.out.println(ITERATIONS + " batches took : " + time + " of id " + id);
+ stats("batch: ", time);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ private static void stats(String tag, long time)
+ {
+ synchronized (startLock)
+ {
+ count++;
+ if (count == numThreads)
+ {
+ long end = System.currentTimeMillis() - startTime;
+ System.out.println(tag + " TOTAL TIME TOOK: " + end);
+ tx_per_sec = (int) (((double) (numThreads * ITERATIONS) / (double) end) * 1000);
+ System.out.println("tx per sec = " + tx_per_sec);
+ double avg = (double) average / (double) numThreads;
+ //System.out.println(tag + "average time took: " + avg);
+ }
+ average += time;
+ }
+ }
+
+ public static void main(String[] args) throws Exception
+ {
+
+
+ for (int i = 1; i <= 1024; i = i * 2)
+ {
+ System.out.println("*** threads: " + i);
+ NUM_DIRS = 1;
+ numThreads = i;
+ average = 0;
+ count = 0;
+ runLogger();
+ }
+
+
+ /*
+ int i = 1024;
+ int last_tx = 0;
+ while (true)
+ {
+ System.out.println("*** threads: " + i);
+ NUM_DIRS = 1;
+ numThreads = i;
+ average = 0;
+ count = 0;
+ runLogger();
+ if (last_tx > tx_per_sec) break;
+ last_tx = tx_per_sec;
+ i+= 1;
+ }
+ */
+ }
+
+ private static void runLogger()
+ throws IOException, InterruptedException
+ {
+ average = 0;
+ File dir = new File("/tmp/batchRecovery");
+ dir.mkdirs();
+ String[] dirs = new String[NUM_DIRS];
+ for (int i = 0; i < dirs.length; i++)
+ {
+ dirs[i] = "/tmp/batchRecovery/dir" + i;
+ }
+ BatchRecoveryLogger logger = new BatchRecoveryLogger();
+ logger.setDirectoryList(dirs);
+ logger.setMaxLogSize(ITERATIONS * numThreads);
+ try
+ {
+ logger.start();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+
+ Thread[] workers = new Thread[numThreads];
+
+ for (int i = 0; i < numThreads; i++)
+ {
+ workers[i] = new Thread(new BatchThread(i, logger));
+ workers[i].start();
+ }
+
+ Thread.sleep(1000);
+
+ //System.out.println("waking up threads");
+ count = 0;
+ startTime = System.currentTimeMillis();
+
+ synchronized (startLock)
+ {
+ startLock.notifyAll();
+ }
+
+ for (int i = 0; i < numThreads; i++)
+ {
+ workers[i].join();
+ }
+
+ //System.out.println("logger.stop");
+ try
+ {
+ logger.stop();
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static void simple()
+ throws Exception
+ {
+ average = 0;
+ Thread[] workers = new Thread[numThreads];
+
+ for (int i = 0; i < numThreads; i++)
+ {
+ workers[i] = new Thread(new SimpleThread(i));
+ workers[i].start();
+ }
+
+ Thread.sleep(1000);
+
+ //System.out.println("waking up threads");
+
+ startTime = System.currentTimeMillis();
+ count = 0;
+
+ synchronized (startLock)
+ {
+ startLock.notifyAll();
+ }
+
+ for (int i = 0; i < numThreads; i++)
+ {
+ workers[i].join();
+ }
+ }
+
+ private static Object startLock = new Object();
+
+ private static class SimpleThread implements Runnable
+ {
+ private FileChannel channel;
+ private RandomAccessFile raf;
+ private int id;
+ private BatchWriter logger;
+
+ public SimpleThread(int id) throws IOException
+ {
+
+ File dir = new File("/tmp/SimpleRecovery/dir" + id);
+ dir.mkdirs();
+ logger = new BatchWriter("hello", 100, dir, RECORD_SIZE, ITERATIONS * numThreads);
+ }
+
+ public void run()
+ {
+ synchronized (startLock)
+ {
+ try
+ {
+ startLock.wait();
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ //System.out.println("Starting...");
+
+ long start = System.currentTimeMillis();
+ boolean failed = false;
+ for (int i = 0; i < ITERATIONS; i++)
+ {
+ byte[] bytes = new byte[RECORD_SIZE];
+ ByteBuffer buf = ByteBuffer.wrap(bytes);
+ try
+ {
+ logger.committing(buf);
+ }
+ catch (Exception e)
+ {
+ failed = true;
+ }
+ }
+ long time = (System.currentTimeMillis() - start);
+ //System.out.println(ITERATIONS + " forces took: " + time);
+ stats("SIMPLE: " + failed + " ", time);
+ logger.cleanup();
+ }
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,480 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.recovery.test;
+
+import java.sql.*;
+import javax.sql.*;
+
+import oracle.jdbc.*;
+import oracle.jdbc.pool.*;
+import oracle.jdbc.xa.OracleXid;
+import oracle.jdbc.xa.OracleXAException;
+import oracle.jdbc.xa.client.*;
+
+import javax.transaction.xa.*;
+
+import org.jboss.tm.XidFactory;
+import org.jboss.tm.XidImpl;
+
+
+/**
+ * Comment
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class TestOracleXA
+{
+
+ static String URL1 = "jdbc:oracle:thin:@192.168.1.102:1521:joracle";
+ static String URL2 = "jdbc:oracle:thin:@192.168.1.102:1521:joracle";
+
+
+ public static void main(String args [])
+ {
+ try
+ {
+ boolean fail = false;
+
+
+ if (factory == null)
+ {
+ factory = new XidFactory();
+ factory.setPad(true);
+ base = (XidImpl) factory.newXid();
+ }
+
+ if (args.length > 0)
+ {
+
+ if (args[0].equals("recover"))
+ {
+ recover2();
+ return;
+ }
+ else if (args[0].equals("print"))
+ {
+ DriverManager.registerDriver(new OracleDriver());
+ printResults();
+ return;
+ }
+ else if (args[0].equals("fail"))
+ {
+ fail = true;
+ }
+ else if (args[0].equals("nonxa"))
+ {
+ DriverManager.registerDriver(new OracleDriver());
+ nonXA();
+ return;
+ }
+ }
+
+
+ DriverManager.registerDriver(new OracleDriver());
+
+ // You can put a database name after the @ sign in the connection URL.
+ Connection conna =
+ DriverManager.getConnection(URL1, "HR", "H19");
+
+ Connection connb =
+ DriverManager.getConnection(URL2, "HR", "H19");
+
+ // Prepare a statement to create the table
+ Statement stmta = conna.createStatement();
+
+ // Prepare a statement to create the table
+ Statement stmtb = connb.createStatement();
+
+ try
+ {
+ // Drop the test table
+ stmta.execute("drop table my_table");
+ }
+ catch (SQLException e)
+ {
+ System.out.println("error");
+ }
+
+ try
+ {
+ // Create a test table
+ stmta.execute("create table my_table (col1 int)");
+ }
+ catch (SQLException e)
+ {
+ System.out.println("error");
+ }
+
+ try
+ {
+ // Drop the test table
+ stmtb.execute("drop table my_tab");
+ }
+ catch (SQLException e)
+ {
+ System.out.println("error");
+ }
+
+ try
+ {
+ // Create a test table
+ stmtb.execute("create table my_tab (col1 char(30))");
+ }
+ catch (SQLException e)
+ {
+ System.out.println("error");
+ }
+
+ stmta.close();
+ stmta = null;
+ stmtb.close();
+ stmtb = null;
+
+ conna.commit();
+ conna.close();
+ conna = null;
+ connb.commit();
+ connb.close();
+ connb = null;
+
+
+
+ // Create XADataSource instances and set properties.
+ OracleXADataSource oxds1 = new OracleXADataSource();
+ oxds1.setURL(URL1);
+ oxds1.setUser("HR");
+ oxds1.setPassword("H19");
+
+ OracleXADataSource oxds2 = new OracleXADataSource();
+
+ oxds2.setURL(URL2);
+ oxds2.setUser("HR");
+ oxds2.setPassword("H19");
+
+ // Get XA connections to the underlying data sources
+ XAConnection pc1 = oxds1.getXAConnection();
+ XAConnection pc2 = oxds2.getXAConnection();
+
+ // Get the physical connections
+ Connection conn1 = pc1.getConnection();
+ Connection conn2 = pc2.getConnection();
+
+ // Get the XA resources
+ XAResource oxar1 = pc1.getXAResource();
+ XAResource oxar2 = pc2.getXAResource();
+
+ // Create the Xids With the Same Global Ids
+ Xid xid1 = createXid(1);
+ Xid xid2 = createXid(2);
+
+ // Start the Resources
+ oxar1.start(xid1, XAResource.TMNOFLAGS);
+ oxar2.start(xid2, XAResource.TMNOFLAGS);
+
+ // Execute SQL operations with conn1 and conn2
+ doSomeWork1(conn1);
+ doSomeWork2(conn2);
+
+ // END both the branches -- IMPORTANT
+ oxar1.end(xid1, XAResource.TMSUCCESS);
+ oxar2.end(xid2, XAResource.TMSUCCESS);
+
+ // Prepare the RMs
+ int prp1 = oxar1.prepare(xid1);
+ int prp2 = oxar2.prepare(xid2);
+
+ System.out.println("Return value of prepare 1 is " + prp1);
+ System.out.println("Return value of prepare 2 is " + prp2);
+
+ boolean do_commit = true;
+
+ if (!((prp1 == XAResource.XA_OK) || (prp1 == XAResource.XA_RDONLY)))
+ do_commit = false;
+
+ if (!((prp2 == XAResource.XA_OK) || (prp2 == XAResource.XA_RDONLY)))
+ do_commit = false;
+
+ System.out.println("do_commit is " + do_commit);
+ System.out.println("Is oxar1 same as oxar2 ? " + oxar1.isSameRM(oxar2));
+
+
+ if (fail)
+ {
+ System.exit(1);
+ // Close connections
+ conn1.close();
+ conn1 = null;
+ conn2.close();
+ conn2 = null;
+
+ pc1.close();
+ pc1 = null;
+ pc2.close();
+ pc2 = null;
+
+ recover2();
+ }
+
+ else
+ {
+
+ if (prp1 == XAResource.XA_OK)
+ if (do_commit)
+ oxar1.commit(xid1, false);
+ else
+ oxar1.rollback(xid1);
+
+ if (prp2 == XAResource.XA_OK)
+ if (do_commit)
+ oxar2.commit(xid2, false);
+ else
+ oxar2.rollback(xid2);
+
+ // Close connections
+ conn1.close();
+ conn1 = null;
+ conn2.close();
+ conn2 = null;
+
+ pc1.close();
+ pc1 = null;
+ pc2.close();
+ pc2 = null;
+ }
+
+
+ printResults();
+
+ }
+ catch (SQLException sqe)
+ {
+ sqe.printStackTrace();
+ }
+ catch (XAException xae)
+ {
+ if (xae instanceof OracleXAException)
+ {
+ System.out.println("XA Error is " +
+ ((OracleXAException) xae).getXAError());
+ System.out.println("SQL Error is " +
+ ((OracleXAException) xae).getOracleError());
+ }
+ else
+ {
+ xae.printStackTrace();
+ System.out.println("error code: " + xae.errorCode);
+ }
+ }
+ catch (Throwable t)
+ {
+ System.out.println("****SHIT!!!");
+ t.printStackTrace();
+ }
+ System.out.println("DONE!!!");
+ }
+
+ private static void recover2()
+ throws SQLException, XAException
+ {
+ // Create XADataSource instances and set properties.
+ OracleXADataSource oxds1 = new OracleXADataSource();
+ oxds1.setURL(URL1);
+ oxds1.setUser("HR");
+ oxds1.setPassword("H19");
+
+ OracleXADataSource oxds2 = new OracleXADataSource();
+
+ oxds2.setURL(URL2);
+ oxds2.setUser("HR");
+ oxds2.setPassword("H19");
+
+ XAConnection pc1;
+ XAConnection pc2;
+ Connection conn1;
+ Connection conn2;
+ XAResource oxar1;
+ XAResource oxar2;
+ // TRY RECOVERING
+
+ // Get XA connections to the underlying data sources
+ pc1 = oxds1.getXAConnection();
+ pc2 = oxds2.getXAConnection();
+
+ // Get the physical connections
+ conn1 = pc1.getConnection();
+ conn2 = pc2.getConnection();
+
+ // Get the XA resources
+ oxar1 = pc1.getXAResource();
+ oxar2 = pc2.getXAResource();
+
+ System.out.println("*** TRYING TO RECOVER");
+
+ Xid[] recover1 = oxar1.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
+ if (recover1 != null)
+ {
+ System.out.println("RECOVERING 1");
+ for (int i = 0; i < recover1.length; i++)
+ {
+ Xid xid = factory.fromXid(recover1[i]);
+ System.out.println("recovering XID: " + xid);
+ oxar1.commit(recover1[i], false);
+ }
+ }
+ else
+ {
+ System.out.println("RECOVER1 returned null");
+ }
+
+ Xid[] recover2 = oxar2.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
+ if (recover2 != null)
+ {
+ System.out.println("RECOVERING 2");
+ for (int i = 0; i < recover2.length; i++)
+ {
+ Xid xid = factory.fromXid(recover2[i]);
+ System.out.println("recovering XID: " + xid);
+ oxar2.commit(recover2[i], false);
+ }
+ }
+ else
+ {
+ System.out.println("RECOVER1 returned null");
+ }
+
+ System.out.println("HERE!!!!!");
+
+ // Close connections
+ conn1.close();
+ conn1 = null;
+ conn2.close();
+ conn2 = null;
+
+ pc1.close();
+ pc1 = null;
+ pc2.close();
+ pc2 = null;
+ }
+
+ private static void printResults()
+ throws SQLException
+ {
+ Connection conna;
+ Connection connb;
+ Statement stmta;
+ Statement stmtb;
+ conna =
+ DriverManager.getConnection(URL1, "HR", "H19");
+
+ connb =
+ DriverManager.getConnection(URL2, "HR", "H19");
+
+ // Prepare a statement to create the table
+ stmta = conna.createStatement();
+
+ // Prepare a statement to create the table
+ stmtb = connb.createStatement();
+
+ ResultSet rset = stmta.executeQuery("select col1 from my_table");
+ while (rset.next())
+ System.out.println("Col1 is " + rset.getInt(1));
+
+ rset.close();
+ rset = null;
+
+ rset = stmtb.executeQuery("select col1 from my_tab");
+ while (rset.next())
+ System.out.println("Col1 is " + rset.getString(1));
+
+ rset.close();
+ rset = null;
+
+ stmta.close();
+ stmta = null;
+ stmtb.close();
+ stmtb = null;
+
+ conna.commit();
+ conna.close();
+ conna = null;
+ connb.commit();
+ connb.close();
+ connb = null;
+ }
+
+ private static void nonXA()
+ throws SQLException
+ {
+ Connection conna;
+ Connection connb;
+ Statement stmta;
+ Statement stmtb;
+ conna =
+ DriverManager.getConnection(URL1, "HR", "H19");
+ conna.setAutoCommit(false);
+
+ connb =
+ DriverManager.getConnection(URL2, "HR", "H19");
+
+ connb.setAutoCommit(false);
+
+ doSomeWork1(conna);
+ doSomeWork2(connb);
+
+ System.exit(1);
+
+ conna.commit();
+ conna.close();
+ conna = null;
+ connb.commit();
+ connb.close();
+ connb = null;
+ }
+
+ static XidFactory factory;
+ static XidImpl base;
+
+ static Xid createXid(int bids)
+ throws XAException
+ {
+ return factory.newBranch(base, bids);
+ }
+
+ private static void doSomeWork1(Connection conn)
+ throws SQLException
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate("insert into my_table values(1)");
+ st.close();
+ }
+
+ private static void doSomeWork2(Connection conn)
+ throws SQLException
+ {
+ Statement st = conn.createStatement();
+ st.executeUpdate("insert into my_tab values('world')");
+ st.close();
+ }
+
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,372 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.remoting.CannotConnectException;
+import org.jboss.remoting.Client;
+import org.jboss.remoting.InvalidConfigurationException;
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.server.DistributedTransactionManager;
+
+/**
+ * Client-side DTM stubs are dynamic proxies that use this
+ * <code>InvocationHandler</code> implementation.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class ClientInvocationHandler
+ implements InvocationHandler, Externalizable
+{
+ static final long serialVersionUID = 2253923354553253502L;
+
+ // Constants (DTM interface codes) -------------------------------
+
+ public static final char TRANSACTION_FACTORY = 'F';
+ public static final char COORDINATOR = 'C';
+ public static final char TERMINATOR = 'T';
+ public static final char RESOURCE = 'R';
+ public static final char RECOVERY_COORDINATOR = 'V';
+ public static final char SYNCHRONIZATION = 'S';
+
+ // Fields --------------------------------------------------------
+
+ private char interfaceCode;
+ private long targetObjectId;
+ private InvokerLocator[] locators;
+ private Client client; // lazily initialized by the invoke method
+ private String stringRepresentation;
+
+ // Constructors --------------------------------------------------
+
+ /** No-arg constructor for externalization. */
+ public ClientInvocationHandler()
+ {
+ }
+
+ /**
+ * Constructs a <code>ClientInvocationHandler</code> for a target object
+ * whose id is 0, given the remote interface of the target and its
+ * <code>InvokerLocator</code>s.
+ */
+ public ClientInvocationHandler(Class interf, InvokerLocator[] locators)
+ throws Exception
+ {
+ this(interf, 0, locators);
+ }
+
+ /**
+ * Constructs a <code>ClientInvocationHandler</code> for a target object
+ * given the remote interface of the target, its object id, and its
+ * <code>InvokerLocator</code>s.
+ */
+ public ClientInvocationHandler(Class interf,
+ long targetObjectId,
+ InvokerLocator[] locators)
+ throws Exception
+ {
+ this(getInterfaceCode(interf), targetObjectId, locators);
+ }
+
+ /**
+ * Constructs a <code>ClientInvocationHandler</code> for a target object
+ * whose id is 0, given the remote interface code of the target and its
+ * <code>InvokerLocator</code>s.
+ */
+ public ClientInvocationHandler(char interfaceCode, InvokerLocator[] locators)
+ throws Exception
+ {
+ this(interfaceCode, 0, locators);
+ }
+
+ /**
+ * Constructs a <code>ClientInvocationHandler</code> for a target object
+ * given the remote interface code of the target, its object id, and its
+ * <code>InvokerLocator</code>s.
+ */
+ public ClientInvocationHandler(char interfaceCode,
+ long targetObjectId,
+ InvokerLocator[] locators)
+ throws Exception
+ {
+ if (interfaceCode != TRANSACTION_FACTORY
+ && interfaceCode != COORDINATOR
+ && interfaceCode != TERMINATOR
+ && interfaceCode != RESOURCE
+ && interfaceCode != RECOVERY_COORDINATOR
+ && interfaceCode != SYNCHRONIZATION)
+ throw new IllegalArgumentException();
+
+ if (locators.length == 0)
+ throw new IllegalArgumentException();
+
+ this.interfaceCode = interfaceCode;
+ this.targetObjectId = targetObjectId;
+ this.locators = locators;
+ }
+
+ // Utility methods -----------------------------------------------
+
+ /**
+ * Returns a <code>Class</code> instance representing the remote interface
+ * implemented by the dynamic proxies that use this invocation handler.
+ */
+ Class getClientInterface() // called by RemoteInterface.toString
+ {
+ switch (interfaceCode)
+ {
+ case TRANSACTION_FACTORY:
+ return TransactionFactory.class;
+ case COORDINATOR:
+ return Coordinator.class;
+ case TERMINATOR:
+ return Terminator.class;
+ case RESOURCE:
+ return Resource.class;
+ case RECOVERY_COORDINATOR:
+ return RecoveryCoordinator.class;
+ case SYNCHRONIZATION:
+ return Synchronization.class;
+ }
+ throw new RuntimeException("Illegal value in field interfaceCode");
+ }
+
+ /**
+ * Takes a <code>Class</code> instance representing a DTM interface
+ * and converts is into an interface code.
+ */
+ private static char getInterfaceCode(Class interf)
+ {
+ if (interf == TransactionFactory.class)
+ return TRANSACTION_FACTORY;
+ else if (interf == Coordinator.class)
+ return COORDINATOR;
+ else if (interf == Terminator.class)
+ return TERMINATOR;
+ else if (interf == Resource.class)
+ return RESOURCE;
+ else if (interf == RecoveryCoordinator.class)
+ return RECOVERY_COORDINATOR;
+ else if (interf == Synchronization.class)
+ return SYNCHRONIZATION;
+ else
+ throw new IllegalArgumentException("argument is not a DTM interface");
+ }
+
+ // InvocationHandler method --------------------------------------
+
+ /**
+ * Uses the <code>InvokerLocator</code> associated with this handler to
+ * send out an <code>Invocation</code> containing this handler's target
+ * object id, the given method, and the given arguments.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ Exception savedException = null;
+
+ if (method.getDeclaringClass() == Object.class)
+ {
+ String methodName = method.getName();
+
+ if (methodName.equals("toString"))
+ return this.toString();
+ else if (methodName.equals("hashCode"))
+ return new Integer(this.toString().hashCode());
+ else if (methodName.equals("equals"))
+ return new Boolean(this.toString().equals(args[0].toString()));
+ }
+
+ if (client != null)
+ {
+ // Non-null client: just use it!
+ try
+ {
+ return client.invoke(new Invocation(targetObjectId, method, args));
+ }
+ catch (CannotConnectException e)
+ {
+ client = null;
+ }
+
+ }
+
+ // Either client has not been initialized yet or it became null after
+ // a failed invocation attempt. Try each locator in sequence. If all
+ // of them fail, propagate the last exception up to the caller, wrapped
+ // into a RemoteException.
+ Invocation invocation = new Invocation(targetObjectId, method, args);
+ for (int i = 0; i < locators.length; i++)
+ {
+ try
+ {
+ client = new Client(locators[i],
+ DistributedTransactionManager.SUBSYSTEM);
+ return client.invoke(invocation);
+ }
+ catch (CannotConnectException e)
+ {
+ client = null;
+ savedException = e;
+ }
+ catch (InvalidConfigurationException e)
+ {
+ client = null;
+ savedException = e;
+ }
+ }
+ throw new RemoteException(savedException.getClass().getName(),
+ savedException);
+ }
+
+ // Externalizable methods ----------------------------------------
+
+ /**
+ * Reads a <code>ClientInvocationHandler</code> in externalized form:
+ * interface code, target object id, and locator URI.
+ */
+ public void readExternal(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ this.interfaceCode = in.readChar();
+ this.targetObjectId = in.readLong();
+ short len = in.readShort();
+ if (len < 1)
+ throw new IOException("ObjectInput does not contain a valid " +
+ "ClientInvocationHandler");
+ this.locators = new InvokerLocator[len];
+ for (int i = 0; i < len; i++)
+ {
+ this.locators[i] = new InvokerLocator(in.readUTF());
+ }
+ this.client = null;
+ }
+
+ /**
+ * Writes a <code>ClientInvocationHandler</code> in externalized form:
+ * interface code, target object id, and locator URI.
+ */
+ public void writeExternal(ObjectOutput out) throws IOException
+ {
+ out.writeChar(interfaceCode);
+ out.writeLong(targetObjectId);
+ out.writeShort(locators.length);
+ for (int i = 0; i < locators.length; i++)
+ {
+ out.writeUTF(locators[i].getLocatorURI());
+ }
+ }
+
+ // Conversion to/from string representation
+
+ /**
+ * Converts a <code>ClientInvocationHandler</code> to string. These are
+ * examples of stringfied handlers:
+ * <p>
+ * <code>T3d00000004c75,socket://server4.acme.com:3873/</code><br>
+ * <code>C1b6,socket://zee.acme.com:3873/|rmi://zee.acme.com:5678/</code>
+ * <p>
+ * The handler comprises an interface code (the first character),
+ * immediately followed by the hexadecimal representation of the target
+ * object id (a long value), a comma (','), and a list of locator URIs
+ * separated by vertical bars ('|'s).
+ */
+ public String toString()
+ {
+ if (stringRepresentation == null)
+ stringRepresentation = interfaceCode +
+ Long.toHexString(targetObjectId) +
+ ',' + locators[0].getLocatorURI();
+ for (int i = 1; i < locators.length; i++)
+ {
+ stringRepresentation += '|' + locators[i].getLocatorURI();
+ }
+ return stringRepresentation;
+ }
+
+ /**
+ * Converts a stringfied handler back into a
+ * <code>ClientInvocationHandler</code> instance.
+ */
+ public static ClientInvocationHandler fromString(String s)
+ throws Exception
+ {
+ String locatorURI;
+ InvokerLocator locator;
+
+ int oidEndIndex = s.indexOf(',');
+ if (oidEndIndex == -1)
+ throw new IllegalArgumentException();
+
+ String oidString = s.substring(1, oidEndIndex);
+ int uriStartIndex = oidEndIndex + 1;
+ int uriEndIndex = s.indexOf('|', uriStartIndex);
+
+ if (uriEndIndex == -1)
+ {
+ // single URI
+ locatorURI = s.substring(uriStartIndex);
+ locator = new InvokerLocator(locatorURI);
+ return new ClientInvocationHandler(s.charAt(0),
+ Long.parseLong(oidString, 16),
+ new InvokerLocator[] { locator });
+ }
+ else
+ {
+ // multiple URIs separated by '|'s
+ List locatorList = new ArrayList();
+ while (uriEndIndex != -1)
+ {
+ locatorURI = s.substring(uriStartIndex, uriEndIndex);
+ locator = new InvokerLocator(locatorURI);
+ locatorList.add(locator);
+ uriStartIndex = uriEndIndex + 1;
+ uriEndIndex = s.indexOf('|', uriStartIndex);
+ }
+ locatorURI = s.substring(uriStartIndex);
+ locator = new InvokerLocator(locatorURI);
+ locatorList.add(locator);
+ InvokerLocator[] locators =
+ (InvokerLocator[]) locatorList.toArray(new InvokerLocator[0]);
+ return new ClientInvocationHandler(s.charAt(0),
+ Long.parseLong(oidString, 16),
+ locators);
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,671 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.tm.GlobalId;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.Vote;
+
+
+/**
+ * An instance of this class represents a method invocation on any of the
+ * remote interfaces of the transaction service, which are the interfaces
+ * defined in the package <code>org.jboss.tm.remoting.interfaces<code>.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class Invocation
+ implements Serializable
+{
+ static final long serialVersionUID = -7256134284357215230L;
+
+ // Nested interfaces ---------------------------------------------
+
+ /**
+ * Interface of an auxiliary object that knows how to perform an invocation
+ * to a particular method. All the nested interfaces which follow have an
+ * Invoker-valued static final field for each method of the corresponding
+ * transaction service interface. For example, the nested interface
+ * <code>ITransactionFactory</code> (see below) corresponds to the interface
+ * <code>org.jboss.tm.remoting.interfaces.TransactionFactory</code>. It has
+ * an Invoker-valued field <code>CREATE</code> and a method
+ * <code>create</code>, which mirrors the similarly named method in
+ * <code>org.jboss.tm.remoting.interface.TransactionFactory</code>.
+ *
+ * The nested interfaces below should be implemented by one or more servant
+ * objects associated with the <code>ServerInvocationHandler</code>. Note
+ * that all the "mirror methods" in those interfaces take an additional
+ * parameter <code>targetId</code>, which allows a single servant to
+ * incarnate multiple target objects. At each invocation, that parameter
+ * identifies the target object for that invocation.
+ */
+ private static interface Invoker
+ {
+ Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable;
+ }
+
+ /**
+ * This interface mirrors the interface <code>TransactionFactory</code> in
+ * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They also
+ * must be unique among all mirror interfaces.
+ */
+ public static interface ITransactionFactory
+ {
+ /** Method id for TransactionFactory.create */
+ static final int M_CREATE = 0;
+
+ /** Method id for TransactionFactory.recreate */
+ static final int M_RECREATE = 1;
+
+ /** Invoker for TransactionFactory.create */
+ static final Invoker CREATE = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ int timeout = ((Integer)args[0]).intValue();
+ return ((ITransactionFactory)servant).create(targetId, timeout);
+ }
+ };
+
+ /** Invoker for TransactionFactory.recreate */
+ static final Invoker RECREATE = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ TxPropagationContext tpc = (TxPropagationContext)args[0];
+ return ((ITransactionFactory)servant).recreate(targetId, tpc);
+ }
+ };
+
+ /** Mirror method for TransactionFactory.create */
+ TxPropagationContext create(long targetId, int timeout)
+ throws RemoteException;
+
+ /** Mirror method for TransactionFactory.recreate */
+ TxPropagationContext recreate(long targetId, TxPropagationContext tpc)
+ throws RemoteException;
+
+ }
+
+ /**
+ * This interface mirrors the interface <code>Coordinator</code> in
+ * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They
+ * also must to be unique among all mirror interfaces.
+ */
+ public static interface ICoordinator
+ {
+ /** Method id for Coordinator.getStatus */
+ static final int M_GET_STATUS = 2;
+
+ /** Method id for Coordinator.isSameTransaction */
+ static final int M_IS_SAME_TRANSACTION = 3;
+
+ /** Method id for Coordinator.hashTransaction */
+ static final int M_HASH_TRANSACTION = 4;
+
+ /** Method id for Coordinator.registerResource */
+ static final int M_REGISTER_RESOURCE = 5;
+
+ /** Method id for Coordinator.registerSynchronization */
+ static final int M_REGISTER_SYNCHRONIZATION = 6;
+
+ /** Method id for Coordinator.rollbackOnly */
+ static final int M_ROLLBACK_ONLY = 7;
+
+ /** Method id for Coordinator.getTransactionContext */
+ static final int M_GET_TRANSACTION_CONTEXT = 8;
+
+ /** Method id for Coordinator.getTransactionId */
+ static final int M_GET_TRANSACTION_ID = 9;
+
+ /** Invoker for Coordinator.getStatus */
+ static final Invoker GET_STATUS = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ return ((ICoordinator)servant).getStatus(targetId);
+ }
+ };
+
+ /** Invoker for Coordinator.isSameTransaction */
+ static final Invoker IS_SAME_TRANSACTION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ Coordinator other = (Coordinator)args[0];
+ boolean retVal =
+ ((ICoordinator)servant).isSameTransaction(targetId, other);
+ return Boolean.valueOf(retVal);
+ }
+ };
+
+ /** Invoker for Coordinator.hashTransaction */
+ static final Invoker HASH_TRANSACTION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ int retVal = ((ICoordinator)servant).hashTransaction(targetId);
+ return new Integer(retVal);
+ }
+ };
+
+ /** Invoker for Coordinator.registerResource */
+ static final Invoker REGISTER_RESOURCE = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ Resource r = (Resource)args[0];
+ return ((ICoordinator)servant).registerResource(targetId, r);
+ }
+ };
+
+ /** Invoker for Coordinator.registerSynchronization */
+ static final Invoker REGISTER_SYNCHRONIZATION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ Synchronization sync = (Synchronization)args[0];
+ ((ICoordinator)servant).registerSynchronization(targetId, sync);
+ return null;
+ }
+ };
+
+ /** Invoker for Coordinator.rollbackOnly */
+ static final Invoker ROLLBACK_ONLY = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((ICoordinator)servant).rollbackOnly(targetId);
+ return null;
+ }
+ };
+
+ /** Invoker for Coordinator.getTransactionContext */
+ static final Invoker GET_TRANSACTION_CONTEXT = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ return ((ICoordinator)servant).getTransactionContext(targetId);
+ }
+ };
+
+ /** Invoker for Coordinator.getTransactionId */
+ static final Invoker GET_TRANSACTION_ID = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ return ((ICoordinator)servant).getTransactionId(targetId);
+ }
+ };
+
+ /** Mirror method for Coordinator.getStatus */
+ Status getStatus(long targetId)
+ throws RemoteException;
+
+ /** Mirror method for Coordinator.isSameTransaction */
+ boolean isSameTransaction(long targetId, Coordinator c)
+ throws RemoteException;
+
+ /** Mirror method for Coordinator.hashTransaction */
+ int hashTransaction(long targetId)
+ throws RemoteException;
+
+ /** Mirror method for Coordinator.registerResource */
+ RecoveryCoordinator registerResource(long targetId, Resource r)
+ throws RemoteException,
+ TransactionInactiveException;
+
+ /** Mirror method for Coordinator.registerSynchronization */
+ void registerSynchronization(long targetId, Synchronization sync)
+ throws RemoteException,
+ TransactionInactiveException;
+
+ /** Mirror method for Coordinator.rollbackOnly */
+ void rollbackOnly(long targetId)
+ throws RemoteException,
+ TransactionInactiveException;
+
+ /** Mirror method for Coordinator.getTransactionContext */
+ TxPropagationContext getTransactionContext(long targetId)
+ throws RemoteException,
+ TransactionInactiveException;
+
+ /** Mirror method for Coordinator.getTransactionId */
+ GlobalId getTransactionId(long targetId)
+ throws RemoteException;
+ }
+
+ /**
+ * This interface mirrors the interface <code>Terminator</code> in
+ * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They
+ * also must be unique among all mirror interfaces.
+ */
+ public static interface ITerminator
+ {
+ /** Method id for Terminator.commit */
+ static final int M_COMMIT = 10;
+
+ /** Method id for Terminator.rollback */
+ static final int M_ROLLBACK = 11;
+
+ /** Invoker for Terminator.commit */
+ static final Invoker COMMIT = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ boolean reportHeuristics = ((Boolean)args[0]).booleanValue();
+ ((ITerminator)servant).commit(targetId, reportHeuristics);
+ return null;
+ }
+ };
+
+ /** Invoker for Terminator.rollback */
+ static final Invoker ROLLBACK = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((ITerminator)servant).rollback(targetId);
+ return null;
+ }
+ };
+
+ /** Mirror method for Terminator.commit */
+ void commit(long targetId, boolean reportHeuristics)
+ throws RemoteException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ /** Mirror method for Terminator.commit */
+ void rollback(long targetId)
+ throws RemoteException;
+ }
+
+ /**
+ * This interface mirrors the interface <code>Resource</code> in
+ * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They
+ * also must be unique among all mirror interfaces.
+ */
+ public static interface IResource
+ {
+ /** Method id for Resource.prepare */
+ static final int M_PREPARE = 12;
+
+ /** Method id for Resource.rollback */
+ static final int M_ROLLBACK = 13;
+
+ /** Method id for Resource.commit */
+ static final int M_COMMIT = 14;
+
+ /** Method id for Resource.commitOnePhase */
+ static final int M_COMMIT_ONE_PHASE = 15;
+
+ /** Method id for Resource.forget */
+ static final int M_FORGET = 16;
+
+ /** Invoker for Resource.prepare */
+ static final Invoker PREPARE = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ return ((IResource)servant).prepare(targetId);
+ }
+ };
+
+ /** Invoker for Resource.rollback */
+ static final Invoker ROLLBACK = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((IResource)servant).rollbackResource(targetId);
+ return null;
+ }
+ };
+
+ /** Invoker for Resource.commit */
+ static final Invoker COMMIT = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((IResource)servant).commit(targetId);
+ return null;
+ }
+ };
+
+ /** Invoker for Resource.commitOnePhase */
+ static final Invoker COMMIT_ONE_PHASE = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((IResource)servant).commitOnePhase(targetId);
+ return null;
+ }
+ };
+
+ /** Invoker for Resource.forget */
+ static final Invoker FORGET = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((IResource)servant).forget(targetId);
+ return null;
+ }
+ };
+
+ /** Mirror method for Resource.prepare */
+ Vote prepare(long targetId)
+ throws RemoteException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ /** Mirror method for Resource.rollback -- its name is rollbackResource
+ * (rather than rollback) to avoid the name clash with Terminator.rollback
+ * within the DTMServant class */
+ void rollbackResource(long targetId)
+ throws RemoteException,
+ HeuristicCommitException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ /** Mirror method for Resource.commit */
+ void commit(long targetId)
+ throws RemoteException,
+ TransactionNotPreparedException,
+ HeuristicRollbackException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ /** Mirror method for Resource.commitOnePhase */
+ void commitOnePhase(long targetId)
+ throws RemoteException,
+ HeuristicHazardException;
+
+ /** Mirror method for Resource.forget */
+ void forget(long targetId)
+ throws RemoteException;
+ }
+
+ /**
+ * This interface mirrors the interface <code>RecoveryCoordinator</code>
+ * in <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They
+ * also must be unique among all mirror interfaces.
+ */
+ public static interface IRecoveryCoordinator
+ {
+ /** Method id for RecoveryCoordinator.replayCompletion */
+ static final int M_REPLAY_COMPLETION = 17;
+
+ /** Invoker for RecoveryCoordinator.replayCompletion */
+ static final Invoker REPLAY_COMPLETION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ Resource r = (Resource)args[0];
+ return ((IRecoveryCoordinator)servant).replayCompletion(targetId,
+ r);
+ }
+ };
+
+ /** Mirror method for RecoveryCoordinator.replayCompletion */
+ Status replayCompletion(long targetId, Resource r)
+ throws RemoteException,
+ TransactionNotPreparedException;
+ }
+
+ /**
+ * This interface mirrors the interface <code>Synchronization</code> in
+ * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+ * constants (<code>M_</code>* fields) are indices to an invoker array,
+ * so they must be consecutive integers in the range 0..max_index. They
+ * also must be unique among all mirror interfaces.
+ */
+ public static interface ISynchronization
+ {
+ /** Method id for Synchronization.beforeCompletion */
+ static final int M_BEFORE_COMPLETION = 18;
+
+ /** Method id for Synchronization.afterCompletion */
+ static final int M_AFTER_COMPLETION = 19;
+
+ /** Invoker for Synchronization.beforeCompletion */
+ static final Invoker BEFORE_COMPLETION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((ISynchronization)servant).beforeCompletion(targetId);
+ return null;
+ }
+ };
+
+ /** Invoker for Synchronization.afterCompletion */
+ static final Invoker AFTER_COMPLETION = new Invoker()
+ {
+ public Object invoke(Object servant, long targetId, Object[] args)
+ throws Throwable
+ {
+ ((ISynchronization)servant).afterCompletion(targetId);
+ return null;
+ }
+ };
+
+ /** Mirror method for Synchronization.beforeCompletion */
+ void beforeCompletion(long targetId);
+
+ /** Mirror method for Synchronization.afterCompletion */
+ void afterCompletion(long targetId);
+ }
+
+ // Static field --------------------------------------------------
+
+ /**
+ * Array of <code>Invoker</code> instances ordered by method id.
+ * Used for efficient (non-reflective) invocation of a method, given
+ * its id.
+ */
+ private static final Invoker[] invokerArray =
+ {
+ ITransactionFactory.CREATE,
+ ITransactionFactory.RECREATE,
+ ICoordinator.GET_STATUS,
+ ICoordinator.IS_SAME_TRANSACTION,
+ ICoordinator.HASH_TRANSACTION,
+ ICoordinator.REGISTER_RESOURCE,
+ ICoordinator.REGISTER_SYNCHRONIZATION,
+ ICoordinator.ROLLBACK_ONLY,
+ ICoordinator.GET_TRANSACTION_CONTEXT,
+ ICoordinator.GET_TRANSACTION_ID,
+ ITerminator.COMMIT,
+ ITerminator.ROLLBACK,
+ IResource.PREPARE,
+ IResource.ROLLBACK,
+ IResource.COMMIT,
+ IResource.COMMIT_ONE_PHASE,
+ IResource.FORGET,
+ IRecoveryCoordinator.REPLAY_COMPLETION,
+ ISynchronization.BEFORE_COMPLETION,
+ ISynchronization.AFTER_COMPLETION
+ };
+
+ // Static method--------------------------------------------------
+
+ /**
+ * Return the id of a given method,
+ */
+ private static int getMethodId(Method m)
+ {
+ Class clz = m.getDeclaringClass();
+
+ if (clz == TransactionFactory.class)
+ {
+ String name = m.getName();
+
+ if (name.equals("create"))
+ return ITransactionFactory.M_CREATE;
+ else /* name.equals("recreate") */
+ return ITransactionFactory.M_RECREATE;
+ }
+ else if (clz == Coordinator.class)
+ {
+ String name = m.getName();
+
+ if (name.equals("getStatus"))
+ return ICoordinator.M_GET_STATUS;
+ else if (name.equals("isSameTransaction"))
+ return ICoordinator.M_IS_SAME_TRANSACTION;
+ else if (name.equals("hashTransaction"))
+ return ICoordinator.M_HASH_TRANSACTION;
+ else if (name.equals("registerResource"))
+ return ICoordinator.M_REGISTER_RESOURCE;
+ else if (name.equals("registerSynchronization"))
+ return ICoordinator.M_REGISTER_SYNCHRONIZATION;
+ else if (name.equals("rollbackOnly"))
+ return ICoordinator.M_ROLLBACK_ONLY;
+ else if (name.equals("getTransactionContext"))
+ return ICoordinator.M_GET_TRANSACTION_CONTEXT;
+ else /* name.equals("getTransactionId") */
+ return ICoordinator.M_GET_TRANSACTION_ID;
+ }
+ else if (clz == Terminator.class)
+ {
+ String name = m.getName();
+
+ if (name.equals("commit"))
+ return ITerminator.M_COMMIT;
+ else /* name.equals("rollback") */
+ return ITerminator.M_ROLLBACK;
+
+ }
+ else if (clz == Resource.class)
+ {
+ String name = m.getName();
+
+ if (name.equals("prepare"))
+ return IResource.M_PREPARE;
+ else if (name.equals("rollback"))
+ return IResource.M_ROLLBACK;
+ else if (name.equals("commit"))
+ return IResource.M_COMMIT;
+ else if (name.equals("commitOnePhase"))
+ return IResource.M_COMMIT_ONE_PHASE;
+ else /* name.equals("forget") */
+ return IResource.M_FORGET;
+ }
+ else if (clz == RecoveryCoordinator.class)
+ {
+ return IRecoveryCoordinator.M_REPLAY_COMPLETION;
+ }
+ else if (clz == Synchronization.class)
+ {
+ String name = m.getName();
+
+ if (name.equals("beforeCompletion"))
+ return ISynchronization.M_BEFORE_COMPLETION;
+ else /* name.equals("afterCompletion") */
+ return ISynchronization.M_AFTER_COMPLETION;
+ }
+ else
+ {
+ throw new RuntimeException("Method " + m + " does not belong to" +
+ " a transaction service interface");
+ }
+ }
+
+ // Attributes ----------------------------------------------------
+
+ /** Specifies the logical target of this <code>Invocation</code>. */
+ private long targetId;
+
+ /** Specifies the method to be invoked. */
+ private int methodId;
+
+ /** The arguments for the method invocation. */
+ private Object[] args;
+
+ // Constructors --------------------------------------------------
+
+ /** Builds a new <code>Invocation</code> instance. */
+ public Invocation(long targetId,
+ Method method,
+ Object[] args)
+ {
+ this.targetId = targetId;
+ this.methodId = getMethodId(method);
+ this.args = args;
+ }
+
+ /** Uses the given servant to perform this <code>Invocation</code>. */
+ public Object perform(InvokerLocator locator, Object servant)
+ throws Throwable
+ {
+ return invokerArray[methodId].invoke(servant, targetId, args);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,95 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting;
+
+import java.lang.reflect.Proxy;
+
+import org.jboss.remoting.InvokerLocator;
+
+/**
+ * Utility class for creating remote proxies, converting them to strings and
+ * converting strings to remote proxies.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class RemoteProxy
+{
+ /**
+ * Create a remote proxy given a DTM interface, a target object id and
+ * an array of <code>InvokerLocator</code>s.
+ *
+ * @param interf the DTM interface to be implemented by the proxy
+ * @param oid the id of the remote object proxified
+ * @param locators the array of invoker locators
+ * @return a newly created proxy
+ */
+ public static Object create(Class interf,
+ long oid,
+ InvokerLocator[] locators)
+ {
+ try
+ {
+ ClientInvocationHandler handler =
+ new ClientInvocationHandler(interf, oid, locators);
+ return Proxy.newProxyInstance(interf.getClassLoader(),
+ new Class[] { interf },
+ handler);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Converts a DTM proxy to String
+ *
+ * @param p a DTM proxy
+ * @return the string representation of the proxy
+ */
+ public static String toString(Proxy p)
+ {
+ ClientInvocationHandler handler =
+ (ClientInvocationHandler) Proxy.getInvocationHandler(p);
+ return handler.toString();
+ }
+
+ /**
+ * Converts a stringfied DTM proxy back into a proxy instance.
+ *
+ * @param s the string representation of a DTM proxy
+ * @return a DTM proxy
+ * @throws Exception
+ */
+ public static Object fromString(String s)
+ throws Exception
+ {
+ ClientInvocationHandler handler = ClientInvocationHandler.fromString(s);
+ Class interf = handler.getClientInterface();
+ return Proxy.newProxyInstance(interf.getClassLoader(),
+ new Class[] { interf },
+ handler);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,371 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.client;
+
+import java.io.Serializable;
+import java.rmi.AccessException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.UserTransaction;
+
+import org.jboss.tm.TransactionPropagationContextFactory;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+
+
+/**
+ * The client-side UserTransaction implementation for JBoss remoting clients.
+ * This will delegate all UserTransaction calls to the DTM in the server.
+ *
+ * <em>Warning:</em> This is only for stand-alone JBoss remoting clients that
+ * do not have their own transaction service. No local work is done in
+ * the context of transactions started here, only work done in beans
+ * at the server.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class ClientUserTransaction
+ implements UserTransaction,
+ TransactionPropagationContextFactory,
+ Referenceable,
+ Serializable
+{
+ // Static --------------------------------------------------------
+
+ static final long serialVersionUID = -3704980350844202097L;
+
+ /** Our singleton instance. */
+ private static ClientUserTransaction singleton = null;
+
+ /** The JNDI name to which the server's transaction factory proxy is bound */
+ public static final String TX_FACTORY_JNDI_NAME = "DTMTransactionFactory";
+
+ /** Remote proxy to the server's transaction factory. */
+ private static TransactionFactory txFactory;
+
+ /** Transaction information associated with the current thread. */
+ private static ThreadLocal threadLocalData = new ThreadLocal() {
+ protected synchronized Object initialValue()
+ {
+ return new TransactionInfo(); // see nested class below
+ }
+ };
+
+ // Nested class -------------------------------------------------
+
+ /**
+ * The <code>TransactionInfo</code> class holds transaction information
+ * associated with the current thread. The <code>threadLocalData</code>
+ * field contains an instance of this class. The field timeout applies
+ * to new transactions started by the current thread; its value is not
+ * necessarily equal to the time out of the currrent transaction. The
+ * <code>TxPropagationContext</code> field refers to the currrent
+ * transaction.
+ */
+ private static class TransactionInfo
+ {
+ int timeout = 0; // for new transactions started by the current thread
+ TxPropagationContext tpc; // null if no current transaction
+ }
+
+ // Static accessors to thread-local data -------------------------
+
+ private static void setThreadLocalTimeout(int timeout)
+ {
+ ((TransactionInfo)threadLocalData.get()).timeout = timeout;
+ }
+
+ private static int getThreadLocalTimeout()
+ {
+ return ((TransactionInfo)threadLocalData.get()).timeout;
+ }
+
+ private static void setThreadLocalTPC(TxPropagationContext tpc)
+ {
+ ((TransactionInfo)threadLocalData.get()).tpc = tpc;
+ }
+
+ private static TxPropagationContext getThreadLocalTPC()
+ throws IllegalStateException
+ {
+ return ((TransactionInfo)threadLocalData.get()).tpc;
+ }
+
+ // Other auxiliary (and static) methods -------------------------
+
+ /**
+ * Returns a CORBA reference to the TransactionFactory implemented by
+ * the JBoss server.
+ */
+ private static TransactionFactory getTxFactory()
+ {
+ if (txFactory == null)
+ {
+ try
+ {
+ Context ctx = new InitialContext();
+ txFactory = (TransactionFactory) ctx.lookup(TX_FACTORY_JNDI_NAME);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException("Could not get transaction factory: ", e);
+ }
+ }
+ return txFactory;
+ }
+
+ /**
+ * Converts transaction status from org.omg.CosTransactions format
+ * to javax.transaction format.
+ */
+ private static int jbossToJavax(Status status)
+ {
+ return status.toInteger();
+ }
+
+ // Constructors --------------------------------------------------
+
+ /**
+ * Create a new instance.
+ */
+ private ClientUserTransaction()
+ {
+ }
+
+ // Public --------------------------------------------------------
+
+ /**
+ * Returns a reference to the singleton instance.
+ */
+ public static ClientUserTransaction getSingleton()
+ {
+ if (singleton == null)
+ singleton = new ClientUserTransaction();
+ return singleton;
+ }
+
+ //
+ // Implementation of interface UserTransaction
+ //
+
+ public void begin()
+ throws NotSupportedException, SystemException
+ {
+ if (getThreadLocalTPC() != null)
+ throw new NotSupportedException();
+ try
+ {
+ TxPropagationContext tpc =
+ getTxFactory().create(getThreadLocalTimeout());
+ setThreadLocalTPC(tpc);
+ }
+ catch (RemoteException e)
+ {
+ SystemException e2 = new SystemException("Unable to begin transaction");
+ e2.initCause(e);
+ throw e2;
+ }
+ }
+
+ public void commit()
+ throws RollbackException,
+ HeuristicMixedException,
+ HeuristicRollbackException,
+ SecurityException,
+ IllegalStateException,
+ SystemException
+ {
+ try
+ {
+ TxPropagationContext tpc = getThreadLocalTPC();
+
+ if (tpc == null)
+ throw new IllegalStateException();
+
+ tpc.terminator.commit(true /* reportHeuristics */);
+ }
+ catch (TransactionRolledbackException e)
+ {
+ RollbackException e2 = new RollbackException("Transaction rolled back");
+ e2.initCause(e);
+ throw e2;
+ }
+ catch (HeuristicHazardException e)
+ {
+ HeuristicRollbackException e2 = new HeuristicRollbackException("Heuristic hazard");
+ e2.initCause(e);
+ throw e2;
+ }
+ catch (AccessException e)
+ {
+ SecurityException e2 = new SecurityException("Access denied");
+ e2.initCause(e);
+ throw e2;
+ }
+ catch (RemoteException e)
+ {
+ SystemException e2 = new SystemException("Error during commit");
+ e2.initCause(e);
+ throw e2;
+ }
+ finally
+ {
+ setThreadLocalTPC(null);
+ }
+ }
+
+ public void rollback()
+ throws SecurityException,
+ IllegalStateException,
+ SystemException
+ {
+ try
+ {
+ TxPropagationContext tpc = getThreadLocalTPC();
+
+ if (tpc == null)
+ throw new IllegalStateException();
+
+ tpc.terminator.rollback();
+ }
+ catch (AccessException e)
+ {
+ SecurityException e2 = new SecurityException("Access denied");
+ e2.initCause(e);
+ throw e2;
+ }
+ catch (RemoteException e)
+ {
+ SystemException e2 = new SystemException("Error during rollback");
+ e2.initCause(e);
+ throw e2;
+ }
+ finally
+ {
+ setThreadLocalTPC(null);
+ }
+ }
+
+ public void setRollbackOnly()
+ throws IllegalStateException,
+ SystemException
+ {
+ try
+ {
+ TxPropagationContext tpc = getThreadLocalTPC();
+
+ if (tpc == null)
+ throw new IllegalStateException();
+
+ tpc.coordinator.rollbackOnly();
+ }
+ catch (TransactionInactiveException e)
+ {
+ IllegalStateException e2 = new IllegalStateException("Transaction is not active");
+ e2.initCause(e);
+ throw e2;
+ }
+ catch (RemoteException e)
+ {
+ SystemException e2 = new SystemException("Error during rollback");
+ e2.initCause(e);
+ throw e2;
+ }
+ }
+
+ public int getStatus()
+ throws SystemException
+ {
+ try
+ {
+ TxPropagationContext tpc = getThreadLocalTPC();
+
+ if (tpc == null)
+ return javax.transaction.Status.STATUS_NO_TRANSACTION;
+ else
+ return jbossToJavax(tpc.coordinator.getStatus());
+ }
+ catch (NoSuchObjectException e)
+ {
+ return javax.transaction.Status.STATUS_NO_TRANSACTION;
+ }
+ catch (RemoteException e)
+ {
+ SystemException e2 = new SystemException("Error getting status");
+ e2.initCause(e);
+ throw e2;
+ }
+ }
+
+ public void setTransactionTimeout(int seconds)
+ throws SystemException
+ {
+ setThreadLocalTimeout(seconds);
+ }
+
+ //
+ // implements interface TransactionPropagationContextFactory
+ //
+
+ public Object getTransactionPropagationContext()
+ {
+ return getThreadLocalTPC();
+ }
+
+ public Object getTransactionPropagationContext(Transaction tx)
+ {
+ // No need to implement in a stand-alone client.
+ throw new InternalError("Should not have been used.");
+ }
+
+ //
+ // Implementation of interface Referenceable
+ //
+
+ public Reference getReference()
+ throws NamingException
+ {
+ Reference ref = new Reference(
+ "org.jboss.tm.remoting.client.ClientUserTransaction",
+ "org.jboss.tm.remoting.client.ClientUserTransactionObjectFactory",
+ null);
+ return ref;
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,92 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.client;
+
+import org.jboss.tm.TransactionPropagationContextUtil;
+import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Reference;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.spi.ObjectFactory;
+
+import javax.transaction.UserTransaction;
+
+/**
+ * This is an object factory for producing client <code>UserTransaction</code>
+ * instances.
+ *
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @version $Revision: 37459 $
+ */
+public class ClientUserTransactionObjectFactory
+ implements ObjectFactory
+{
+ /**
+ * The <code>UserTransaction</code> this factory will return.
+ * This is evaluated lazily in {@link #getUserTransaction()}.
+ */
+ static private UserTransaction userTransaction = null;
+
+ /**
+ * Get the <code>UserTransaction</code> this factory will return.
+ * This may return a cached value from a previous call.
+ */
+ static private UserTransaction getUserTransaction()
+ {
+ if (userTransaction == null) {
+ // See if we have a local TM
+ try {
+ new InitialContext().lookup("java:/TransactionManager");
+
+ // We execute in the server.
+ userTransaction = ServerVMClientUserTransaction.getSingleton();
+ } catch (NamingException ex) {
+ // We execute in a stand-alone client.
+ ClientUserTransaction cut = ClientUserTransaction.getSingleton();
+
+ // Tell the proxy that this is the factory for
+ // transaction propagation contexts.
+ TransactionPropagationContextUtil.setTPCFactory(cut);
+ userTransaction = cut;
+ }
+ }
+ return userTransaction;
+ }
+
+ public Object getObjectInstance(Object obj, Name name,
+ Context nameCtx, Hashtable environment)
+ throws Exception
+ {
+ Reference ref = (Reference)obj;
+
+ if (!ref.getClassName().equals(ClientUserTransaction.class.getName()))
+ return null;
+
+ return getUserTransaction();
+ }
+}
+
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import org.jboss.tm.GlobalId;
+
+/**
+ * Interface used by the participants in a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface Coordinator extends Remote
+{
+
+ Status getStatus()
+ throws RemoteException;
+
+ boolean isSameTransaction(Coordinator c)
+ throws RemoteException;
+
+ int hashTransaction()
+ throws RemoteException;
+
+ RecoveryCoordinator registerResource(Resource r)
+ throws RemoteException,
+ TransactionInactiveException;
+
+ void registerSynchronization(Synchronization sync)
+ throws RemoteException,
+ TransactionInactiveException,
+ SynchronizationUnavailableException;
+
+ void rollbackOnly()
+ throws RemoteException,
+ TransactionInactiveException;
+
+ TxPropagationContext getTransactionContext()
+ throws RemoteException,
+ TransactionInactiveException;
+
+ GlobalId getTransactionId()
+ throws RemoteException;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,52 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a heuristic decision may have been made, the disposition of
+ * all relevant updates is not known, and for those updates whose disposition
+ * is knwon, either all have been committed or all have been rolled back. (In
+ * other words, the <code>HeuristicMixed</code> exception takes priority over
+ * this exception.
+ *
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HeuristicHazardException extends Exception
+{
+ private static final long serialVersionUID = -3687274256195397687L;
+
+ public HeuristicHazardException()
+ {
+ }
+
+ public HeuristicHazardException(String msg)
+ {
+ super(msg);
+ }
+
+ public HeuristicHazardException(Throwable cause)
+ {
+ super(cause);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Interface that allows a recoverable object to drive the recovery process.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryCoordinator extends Remote
+{
+
+ Status replayCompletion(Resource r)
+ throws RemoteException,
+ TransactionNotPreparedException;
+}
+
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,68 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+
+
+/**
+ * Interface that provides operations invoked by the transaction service
+ * on each resource.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface Resource extends Remote
+{
+
+ Vote prepare()
+ throws RemoteException,
+ TransactionAlreadyPreparedException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ void rollback()
+ throws RemoteException,
+ HeuristicCommitException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ void commit()
+ throws RemoteException,
+ TransactionNotPreparedException,
+ HeuristicRollbackException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ void commitOnePhase()
+ throws RemoteException,
+ HeuristicHazardException;
+
+ void forget()
+ throws RemoteException;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Type safe enumeration for the status of a transaction.
+ *
+ * @author Scott.Stark at jboss.org
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class Status implements Serializable
+{
+ static final long serialVersionUID = -498123265225476852L;
+
+ /**
+ * The max ordinal value in use for the Status enums. When you add a
+ * new key enum value you must assign it an ordinal value of the current
+ * MAX_TYPE_ID+1 and update the MAX_TYPE_ID value.
+ */
+ private static final int MAX_TYPE_ID = 9;
+
+ /** The array of Status indexed by ordinal value of the key */
+ private static final Status[] values = new Status[MAX_TYPE_ID + 1];
+
+ // IMPORTANT: The ordinal values below are equal to the corresponding
+ // values in org.omg.CosTransactions.Status and in javax.transaction.Status.
+ // This allows efficient conversion between
+ // org.jboss.tm.remoting.interfaces.Status instances
+ // and org.omg.CosTransactions.Status or javax.transaction.Status instances.
+ public static final Status ACTIVE = new Status("ACTIVE", 0);
+
+ public static final Status MARKED_ROLLBACK = new Status("MARKED_ROLLBACK", 1);
+
+ public static final Status PREPARED = new Status("PREPARED", 2);
+
+ public static final Status COMMITTED = new Status("COMMITTED", 3);
+
+ public static final Status ROLLEDBACK = new Status("ROLLEDBACK", 4);
+
+ public static final Status UNKNOWN = new Status("UNKNOWN", 5);
+
+ public static final Status NO_TRANSACTION = new Status("NO_TRANSACTION", 6);
+
+ public static final Status PREPARING = new Status("PREPARING", 7);
+
+ public static final Status COMMITTING = new Status("COMMITTING", 8);
+
+ public static final Status ROLLINGBACK = new Status("ROLLINGBACK", 9);
+
+ private final transient String name;
+
+ // this is the only value serialized
+ private final int ordinal;
+
+ private Status(String name, int ordinal)
+ {
+ this.name = name;
+ this.ordinal = ordinal;
+ values[ordinal] = this;
+ }
+
+ public String toString()
+ {
+ return name;
+ }
+
+ public int toInteger()
+ {
+ return ordinal;
+ }
+
+ public static Status fromInteger(int i)
+ {
+ return values[i];
+ }
+
+ Object readResolve() throws ObjectStreamException
+ {
+ return values[ordinal];
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Interface implemented by objects that want to be notified before the start
+ * of the two-phase commit protocol, and after its competion.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface Synchronization extends Remote
+{
+
+ void beforeCompletion()
+ throws RemoteException;
+
+ void afterCompletion(Status s)
+ throws RemoteException;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Exception thrown by <code>Coordinator.registerSynchronization</code>
+ * to indicate that the target coordinator does not support synchronization
+ * callbacks.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class SynchronizationUnavailableException extends Exception
+{
+ static final long serialVersionUID = -4736175108426365516L;
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,47 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicMixedException;
+
+
+/**
+ * Interface that provides operations to commit or rollback a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface Terminator extends Remote
+{
+
+ void commit(boolean reportHeuristics)
+ throws RemoteException,
+ HeuristicMixedException,
+ HeuristicHazardException;
+
+ void rollback()
+ throws RemoteException;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Exception thrown when <code>prepare</code> is called on a resource that is
+ * already prepared.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class TransactionAlreadyPreparedException extends Exception
+{
+ static final long serialVersionUID = 7862561036798964308L;
+
+ public TransactionAlreadyPreparedException()
+ {
+ }
+
+ public TransactionAlreadyPreparedException(String msg)
+ {
+ super(msg);
+ }
+
+ public TransactionAlreadyPreparedException(Throwable cause)
+ {
+ super(cause);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Interface that allows the transaction originator to begin a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface TransactionFactory extends Remote
+{
+
+ TxPropagationContext create(int timeout)
+ throws RemoteException;
+
+ TxPropagationContext recreate(TxPropagationContext tpc)
+ throws RemoteException;
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a transaction expected to be in the active state
+ * has already been prepared for commit or marked for rollback.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class TransactionInactiveException extends Exception
+{
+ static final long serialVersionUID = -846122012000355051L;
+
+ public TransactionInactiveException()
+ {
+ }
+
+ public TransactionInactiveException(String msg)
+ {
+ super(msg);
+ }
+
+ public TransactionInactiveException(Throwable cause)
+ {
+ super(cause);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a transaction expected to be in the prepared state
+ * is actually not prepared.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class TransactionNotPreparedException extends Exception
+{
+ static final long serialVersionUID = -4131393859923384723L;
+
+ public TransactionNotPreparedException()
+ {
+ }
+
+ public TransactionNotPreparedException(String msg)
+ {
+ super(msg);
+ }
+
+ public TransactionNotPreparedException(Throwable cause)
+ {
+ super(cause);
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,85 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.Serializable;
+
+
+/**
+ * The <code>TransactionFactory</code> operations return instances of this
+ * class, which defines the transaction context to be propagated along with
+ * outgoing invocations issued through the remoting framework. In the IIOP
+ * case, however, the transaction context is not propagated as a
+ * <code>TxPropagationContext</code> instance, but in the standard format
+ * defined by the CORBA Transaction Service specification.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class TxPropagationContext implements Serializable
+{
+ static final long serialVersionUID = 88513087659796235L;
+
+ private static String thisClassName;
+
+ static {
+ thisClassName = TxPropagationContext.class.getName();
+ thisClassName =
+ thisClassName.substring(thisClassName.lastIndexOf('.') + 1);
+ }
+
+ public int formatId;
+ public byte[] globalId;
+ public int timeout;
+ public Coordinator coordinator;
+ public Terminator terminator;
+
+ public TxPropagationContext(int formatId,
+ byte[] globalId,
+ int timeout,
+ Coordinator coordinator,
+ Terminator terminator)
+
+ {
+ this.formatId = formatId;
+ this.globalId = globalId;
+ this.timeout = timeout;
+ this.coordinator = coordinator;
+ this.terminator = terminator;
+ }
+
+ public TxPropagationContext(int formatId,
+ byte[] globalId,
+ int timeout,
+ Coordinator coordinator)
+ {
+ this(formatId, globalId, timeout, coordinator, null);
+ }
+
+ public String toString()
+ {
+ return thisClassName + "[formatId=" + formatId
+ + ", globalId=" + new String(globalId).trim()
+ + ", timeout=" + timeout + "]";
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,74 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Type safe enumeration for <code>Resource</code> votes.
+ *
+ * @author Scott.Stark at jboss.org
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class Vote implements Serializable
+{
+ static final long serialVersionUID = -6997498750766547785L;
+
+ /**
+ * The max ordinal value in use for the Vote enums. When you add a
+ * new key enum value you must assign it an ordinal value of the current
+ * MAX_TYPE_ID+1 and update the MAX_TYPE_ID value.
+ */
+ private static final int MAX_TYPE_ID = 2;
+
+ /** The array of Vote indexed by ordinal value of the key */
+ private static final Vote[] values = new Vote[MAX_TYPE_ID + 1];
+
+ public static final Vote COMMIT = new Vote("COMMIT", 0);
+ public static final Vote ROLLBACK = new Vote("ROLLBACK", 1);
+ public static final Vote READONLY = new Vote("READONLY", 2);
+
+ private final transient String name;
+
+ // this is the only value serialized
+ private final int ordinal;
+
+ private Vote(String name, int ordinal)
+ {
+ this.name = name;
+ this.ordinal = ordinal;
+ values[ordinal] = this;
+ }
+
+ public String toString()
+ {
+ return name;
+ }
+
+ Object readResolve() throws ObjectStreamException
+ {
+ return values[ordinal];
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,93 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.server;
+
+import javax.management.MBeanServer;
+
+import org.jboss.remoting.InvocationRequest;
+import org.jboss.remoting.ServerInvocationHandler;
+import org.jboss.remoting.ServerInvoker;
+import org.jboss.remoting.callback.InvokerCallbackHandler;
+import org.jboss.tm.remoting.Invocation;
+
+/**
+ * <code>ServerInvocationHandler</code> for the DTM subsystem.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class DTMInvocationHandler implements ServerInvocationHandler
+{
+ private DTMServant dtmServant;
+ private ServerInvoker invoker;
+
+ DTMInvocationHandler(DTMServant dtmServant)
+ {
+ this.dtmServant = dtmServant;
+ }
+
+ /**
+ * Empty method - this invocation handler does not keep a reference to the <code>MBeanServer</code>.
+ * @see org.jboss.remoting.ServerInvocationHandler#setMBeanServer(javax.management.MBeanServer)
+ */
+ public void setMBeanServer(MBeanServer server)
+ {
+ }
+
+ /**
+ * Sets this invovation handler's <code>ServerInvoker</code>.
+ * @see org.jboss.remoting.ServerInvocationHandler#setInvoker(org.jboss.remoting.ServerInvoker)
+ */
+ public void setInvoker(ServerInvoker invoker)
+ {
+ this.invoker = invoker;
+ }
+
+ /**
+ * Performs the invocation specified by a given <code>InvocationRequest</code>,
+ * @see org.jboss.remoting.ServerInvocationHandler#invoke(org.jboss.remoting.InvocationRequest)
+ */
+ public Object invoke(InvocationRequest invocationRequest)
+ throws Throwable
+ {
+ Invocation invocation = (Invocation)invocationRequest.getParameter();
+
+ return invocation.perform(invoker.getLocator(), dtmServant);
+ }
+
+ /**
+ * Empty method - listener registration is not supported.
+ * @see org.jboss.remoting.ServerInvocationHandler#addListener(org.jboss.remoting.callback.InvokerCallbackHandler)
+ */
+ public void addListener(InvokerCallbackHandler callbackHandler)
+ {
+ }
+
+ /**
+ * Empty method - listener registration is not supported.
+ * @see org.jboss.remoting.ServerInvocationHandler#removeListener(org.jboss.remoting.callback.InvokerCallbackHandler)
+ */
+ public void removeListener(InvokerCallbackHandler callbackHandler)
+ {
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,967 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.server;
+
+import java.lang.reflect.Proxy;
+import java.rmi.AccessException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.UnexpectedException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.xa.XAResource;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.CoordinatorFactory;
+import org.jboss.tm.GlobalId;
+import org.jboss.tm.LocalId;
+import org.jboss.tm.ResourceFactory;
+import org.jboss.tm.StringRemoteRefConverter;
+import org.jboss.tm.TransactionImpl;
+import org.jboss.tm.TMUtil;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.XidImpl;
+import org.jboss.tm.remoting.RemoteProxy;
+import org.jboss.tm.remoting.Invocation.ICoordinator;
+import org.jboss.tm.remoting.Invocation.IRecoveryCoordinator;
+import org.jboss.tm.remoting.Invocation.IResource;
+import org.jboss.tm.remoting.Invocation.ISynchronization;
+import org.jboss.tm.remoting.Invocation.ITerminator;
+import org.jboss.tm.remoting.Invocation.ITransactionFactory;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.Vote;
+
+/**
+ * Remoting servant for the following Distributed Transaction Manager
+ * interfaces: <code>TransactionFactory</code>, <code>Coordinator</code>,
+ * <code>Terminator</code>, <code>RecoveryCoordinator</code>,
+ * <code>Synchronization</code>.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class DTMServant implements ITransactionFactory,
+ ICoordinator,
+ ITerminator,
+ IResource,
+ IRecoveryCoordinator,
+ ISynchronization,
+ CoordinatorFactory,
+ ResourceFactory,
+ StringRemoteRefConverter
+{
+
+ // Constants ----------------------------------------------------
+
+ private static final Logger log =
+ Logger.getLogger(DTMServant.class);
+
+ // Private fields ------------------------------------------------
+
+ private DistributedTransactionManager dtm;
+
+ // Constructor ---------------------------------------------------
+
+ public DTMServant(DistributedTransactionManager dtm)
+ {
+ this.dtm = dtm;
+ }
+
+ // ITransactionFactory methods -----------------------------------
+
+ public TxPropagationContext create(long targetId, int timeout)
+ throws RemoteException
+ {
+ log.trace("TransactionFactory.create");
+ try
+ {
+ TransactionManager tm = TMUtil.getTransactionManager();
+
+ // Set timeout value
+ if (timeout != 0)
+ tm.setTransactionTimeout(timeout);
+
+ // Start tx
+ tm.begin();
+
+ // Suspend thread association
+ // and get the xid and the local id of the transaction
+ TransactionImpl tx = (TransactionImpl)tm.suspend();
+ XidImpl xid = tx.getXid();
+ long localId = xid.getLocalIdValue();
+
+ // Get the TxPropagationContext of the transaction
+ TxPropagationContext tpc = tx.getPropagationContext();
+
+ // Set the terminator in the TPC and return the TPC
+ tpc.terminator =
+ (Terminator) RemoteProxy.create(Terminator.class,
+ localId,
+ dtm.getLocators());
+
+ return tpc;
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException("Unexpected " + e, e);
+ }
+ catch (NotSupportedException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException("Unexpected " + e, e);
+ }
+ }
+
+ public TxPropagationContext recreate(long targetId,
+ TxPropagationContext tpc)
+ throws RemoteException
+ {
+ log.trace("TransactionFactory.recreate");
+
+ if (tpc == null)
+ throw new IllegalArgumentException(
+ "recreate: TxPropagationContext parameter cannot be null");
+
+ TxManager tm = (TxManager) TMUtil.getTransactionManager();
+ TransactionImpl tx = tm.importExternalTransaction(tpc.formatId,
+ tpc.globalId,
+ tpc.coordinator,
+ tpc.timeout * 1000);
+
+ // Create remote proxy to the local Coordinator
+ Coordinator subCoordinator =
+ (Coordinator) RemoteProxy.create(Coordinator.class,
+ tx.getLocalIdValue(),
+ dtm.getLocators());
+
+ // Return TPC with the interposed subCoordinator and a null Terminator
+ return new TxPropagationContext(tpc.formatId,
+ tpc.globalId,
+ tpc.timeout,
+ subCoordinator,
+ null);
+ }
+
+ // ICoordinator methods ------------------------------------------
+
+ public Status getStatus(long targetId)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.getStatus, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ Transaction tx = TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in getStatus: transaction not found");
+ // Not sure if here it would be better to return StatusNoTransaction
+ // instead of throwing NoSuchObjectException... (Francisco)
+ throw new NoSuchObjectException("No transaction.");
+ // return Status.NO_TRANSACTION; // ?
+ }
+
+ int status;
+
+ try
+ {
+ status = tx.getStatus();
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException("Unexpected " + e, e);
+ }
+ return javaxToJBoss(status);
+ }
+
+ public boolean isSameTransaction(long targetId, Coordinator c)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.isSameTransaction, targetId=" +
+ Long.toHexString(targetId));
+ return getTransactionId(targetId).equals(c.getTransactionId());
+ }
+
+ public int hashTransaction(long targetId)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.hashTransaction, targetId=" +
+ Long.toHexString(targetId));
+ return getTransactionId(targetId).hashCode();
+ }
+
+ public RecoveryCoordinator registerResource(long targetId, Resource r)
+ throws RemoteException,
+ TransactionInactiveException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.registerResource, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in registerResource: " +
+ "transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.enlistRemoteResource(r);
+ }
+ catch (RollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ throw new TransactionInactiveException(e);
+ }
+
+ // Create remote proxies to the RecoveryCoordinator
+ RecoveryCoordinator rc =
+ (RecoveryCoordinator) RemoteProxy.create(RecoveryCoordinator.class,
+ targetId,
+ dtm.getLocators());
+ return rc;
+ }
+
+ public void registerSynchronization(long targetId,
+ final Synchronization sync)
+ throws RemoteException,
+ TransactionInactiveException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.registerSynchronization, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ Transaction tx = TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in registerSynchronization: " +
+ "transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.registerSynchronization(
+ new javax.transaction.Synchronization()
+ {
+ public void beforeCompletion()
+ {
+ try
+ {
+ sync.beforeCompletion();
+ }
+ catch (RemoteException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("RemoteException in beforeCompletion: " + e);
+ }
+ }
+ public void afterCompletion(int status)
+ {
+ try
+ {
+ sync.afterCompletion(javaxToJBoss(status));
+ }
+ catch (RemoteException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("RemoteException in afterCompletion: " + e);
+ }
+ }
+ });
+ }
+ catch (RollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new TransactionInactiveException(e);
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException("Unexpected " + e, e);
+ }
+ }
+
+ public void rollbackOnly(long targetId)
+ throws RemoteException,
+ TransactionInactiveException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.rollbackOnly, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ Transaction tx = TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in rollbackOnly: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.setRollbackOnly();
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new TransactionInactiveException(e);
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException("Unexpected " + e, e);
+ }
+ }
+
+ public TxPropagationContext getTransactionContext(long targetId)
+ throws RemoteException,
+ TransactionInactiveException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.getTransactionContext, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in getTransactionContext: " +
+ "transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ TxPropagationContext txPropagationContext = tx.getPropagationContext();
+ if (txPropagationContext != null)
+ return tx.getPropagationContext();
+ else
+ throw new TransactionInactiveException();
+ }
+
+ public GlobalId getTransactionId(long targetId)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Coordinator.getTransactionId, targetId="
+ + Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in getTransactionId: " +
+ "transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ return tx.getGlobalId();
+ }
+
+ // ITerminator methods -------------------------------------------
+
+ public void commit(long targetId, boolean reportHeuristics)
+ throws RemoteException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Terminator.commit, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ Transaction tx = TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in commit: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.commit();
+ }
+ catch (RollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (HeuristicMixedException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ if (reportHeuristics)
+ {
+ HeuristicMixedException ex = new HeuristicMixedException();
+ ex.initCause(e);
+ throw ex;
+ }
+ }
+ catch (HeuristicRollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (SecurityException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new AccessException(e.toString(), e);
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new RemoteException(e.toString(), e);
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ }
+
+ public void rollback(long targetId)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Terminator.rollback, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ Transaction tx = TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in rollback: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.rollback();
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new RemoteException(e.toString(), e);
+ }
+ catch (SecurityException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new AccessException(e.toString(), e);
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ }
+
+ // IResource methods ------------------------------------------
+
+ public Vote prepare(long targetId)
+ throws RemoteException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Resource.prepare, targetId=" + Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in prepare: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ TransactionManager tm = TMUtil.getTransactionManager();
+ try
+ {
+ tm.resume(tx);
+ int vote = tx.prepare(null);
+
+ if (vote == XAResource.XA_OK)
+ return Vote.COMMIT;
+ else // (vote == XAResource.XA_RDONLY)
+ return Vote.READONLY;
+ }
+ catch (RollbackException e)
+ {
+ return Vote.ROLLBACK;
+ }
+ catch (javax.transaction.HeuristicMixedException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ HeuristicMixedException ex = new HeuristicMixedException();
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (javax.transaction.HeuristicRollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ HeuristicHazardException ex = new HeuristicHazardException();
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (Exception e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ finally
+ {
+ try
+ {
+ tm.suspend();
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ }
+ }
+
+ public void rollbackResource(long targetId)
+ throws RemoteException,
+ HeuristicCommitException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Resource.rollback, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in rollback: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.rollbackBranch();
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new RemoteException(e.toString(), e);
+ }
+ }
+
+ public void commit(long targetId)
+ throws RemoteException,
+ TransactionNotPreparedException,
+ HeuristicRollbackException,
+ HeuristicMixedException,
+ HeuristicHazardException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Resource.commit, targetId=" + Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in commit: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.commit(false);
+ }
+ catch (IllegalStateException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionNotPreparedException ex =
+ new TransactionNotPreparedException();
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (RollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (javax.transaction.HeuristicMixedException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ HeuristicMixedException ex = new HeuristicMixedException();
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (javax.transaction.HeuristicRollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ HeuristicRollbackException ex = new HeuristicRollbackException();
+ ex.initCause(e);
+ throw ex;
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ }
+
+ public void commitOnePhase(long targetId)
+ throws RemoteException,
+ HeuristicHazardException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Resource.commitOnePhase, targetId=" +
+ Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in commit: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ try
+ {
+ tx.commit(true);
+ }
+ catch (RollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ TransactionRolledbackException ex =
+ new TransactionRolledbackException(e.toString());
+ ex.detail = e;
+ throw ex;
+ }
+ catch (javax.transaction.HeuristicMixedException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ catch (javax.transaction.HeuristicRollbackException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ catch (SystemException e)
+ {
+ if (log.isTraceEnabled())
+ log.trace("Unexpected exception: ", e);
+ throw new UnexpectedException(e.toString(), e);
+ }
+ }
+
+ public void forget(long targetId)
+ throws RemoteException
+ {
+ if (log.isTraceEnabled())
+ log.trace("Resource.forget, targetId=" + Long.toHexString(targetId));
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in forget: transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ tx.forget();
+ }
+
+ // IRecoveryCoordinator method -----------------------------------
+
+ public Status replayCompletion(long targetId, final Resource r)
+ throws RemoteException,
+ TransactionNotPreparedException
+ {
+ if (log.isTraceEnabled())
+ log.trace("RecoveryCoordinator.replayCompletion, targetId=" +
+ Long.toHexString(targetId));
+
+ TxManager tm = (TxManager) TMUtil.getTransactionManager();
+ if (tm.isRecoveryPending())
+ {
+ if (log.isTraceEnabled())
+ log.trace("RecoveryCoordinator.replayCompletion called on" +
+ " targetId=" + Long.toHexString(targetId) +
+ " before recovery is complete.\n" +
+ " Throwing RemoteException.");
+ throw new RemoteException("Transaction manager not ready.");
+ }
+
+ LocalId localId = new LocalId(targetId);
+ TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+ if (tx == null)
+ {
+ log.trace("RemoteException in replayCompletion: " +
+ "transaction not found");
+ throw new NoSuchObjectException("No transaction.");
+ }
+
+ int status = tx.replayCompletion(r);
+
+ if (status == javax.transaction.Status.STATUS_MARKED_ROLLBACK ||
+ status == javax.transaction.Status.STATUS_NO_TRANSACTION ||
+ status == javax.transaction.Status.STATUS_ROLLEDBACK ||
+ status == javax.transaction.Status.STATUS_ROLLING_BACK)
+ {
+ Runnable runnable = new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ r.rollback();
+ }
+ catch (Exception ignore)
+ {
+ // We can ignore this exception. If the resource didn't get
+ // the rollback then it will eventually call replayCompletion
+ // again.
+ if (log.isTraceEnabled())
+ log.trace("Ignoring exception in remote resource rollback",
+ ignore);
+ }
+ }
+ };
+ Thread t = new Thread(runnable, "resourceRollbackThread");
+
+ t.start();
+ }
+ return javaxToJBoss(status);
+ }
+
+ // TODO: This class does not need to implement ISynchronization
+ // ISynchronization methods --------------------------------------
+
+ public void beforeCompletion(long targetId)
+ {
+ // TODO: remove beforeCompletion
+ if (log.isTraceEnabled())
+ log.trace("Synchronization.beforeCompletion, targetId=" +
+ Long.toHexString(targetId));
+ }
+
+ public void afterCompletion(long targetId)
+ {
+ // TODO: remove afterCompletion
+ if (log.isTraceEnabled())
+ log.trace("Synchronization.afterCompletion, targetId=" +
+ Long.toHexString(targetId));
+ }
+
+ // CoordinatorFactory implementation -----------------------------
+
+ /**
+ * Creates a reference for the DTM object with the given interface and
+ * <code>localId</code>.
+ * @see org.jboss.tm.ProxyFactory#create(java.lang.Class, long)
+ */
+ public Coordinator createCoordinator(long localId)
+ {
+ return (Coordinator) RemoteProxy.create(Coordinator.class,
+ localId,
+ dtm.getLocators());
+ }
+
+ // ResourceFactory implementation --------------------------------
+
+ /**
+ * Creates a reference for the DTM resource with the given
+ * <code>localId</code>.
+ * @see org.jboss.tm.ResourceFactory#create(long)
+ */
+ public Resource createResource(long localId)
+ {
+ return (Resource) RemoteProxy.create(Resource.class,
+ localId,
+ dtm.getLocators());
+ }
+
+ // StringRemoteRefConverter implementation -----------------------
+
+ /**
+ * Converts a stringfied reference to a remote resource back to a remote
+ * reference.
+ *
+ * @param strResource a stringfied reference to a remote resource
+ * @return a remote reference to the resource.
+ */
+ public Resource stringToResource(String strResource)
+ {
+ try
+ {
+ return (Resource) RemoteProxy.fromString(strResource);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Converts a stringfied reference to a remote recovery coordinator back
+ * to a remote reference.
+ *
+ * @param strRecCoordinator a stringfied reference to a remote recovery
+ * coordinator
+ * @return a remote reference to the recovery coordinator.
+ */
+ public RecoveryCoordinator stringToRecoveryCoordinator(
+ String strRecCoordinator)
+ {
+ try
+ {
+ return (RecoveryCoordinator) RemoteProxy.fromString(strRecCoordinator);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Takes a remote reference to a resource and converts it to a string.
+ *
+ * @param res a remote reference to a resource
+ * @return a string that represents the remote resource.
+ */
+ public String resourceToString(Resource res)
+ {
+ return RemoteProxy.toString((Proxy)res);
+ }
+
+ /**
+ * Takes a remote reference to recovery coordinator and converts it to a
+ * string.
+ *
+ * @param recoveryCoord a remote reference to a recovery coordinator
+ * @return a string that represents the remote recovery coordinator.
+ */
+ public String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+ {
+ return RemoteProxy.toString((Proxy)recoveryCoord);
+ }
+
+ // Private -------------------------------------------------------
+
+ private static Status javaxToJBoss(int status)
+ {
+ return Status.fromInteger(status);
+ }
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,204 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.server;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.management.ObjectName;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import javax.transaction.TransactionManager;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.TMUtil;
+import org.jboss.tm.remoting.RemoteProxy;
+import org.jboss.tm.remoting.client.ClientUserTransaction;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+
+/**
+ * Service MBean that implements the distributed transaction manager.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 57024 $
+ */
+public class DistributedTransactionManager extends ServiceMBeanSupport
+ implements DistributedTransactionManagerMBean
+{
+ // Constants -----------------------------------------------------
+
+ public static final String SUBSYSTEM = "DTM";
+
+ private static final String[] addInvocationHandlerSignature =
+ new String[] {
+ "java.lang.String",
+ "org.jboss.remoting.ServerInvocationHandler"
+ };
+
+ private static final String[] removeInvocationHandlerSignature =
+ new String[] { "java.lang.String" };
+
+ private static final Object[] removeInvocationHandlerParams =
+ new Object[] { SUBSYSTEM };
+
+ public static final String USER_TRANSACTION_JNDI_NAME = "UserTransaction";
+
+ // Attributes ----------------------------------------------------
+
+ private List connectors;
+ private InvokerLocator[] locators;
+ private String[] locatorURIs;
+ private boolean interpositionEnabled;
+
+ // ServiceMBeanSupport overrides ---------------------------------
+
+ protected void startService()
+ throws Exception
+ {
+ DTMServant dtmServant = new DTMServant(this);
+ List locatorList = new ArrayList();
+ List locatorURIList = new ArrayList();
+ Iterator i = connectors.iterator();
+
+ while (i.hasNext())
+ {
+ // Add DTM invocation handler to connector
+ ObjectName objectName = (ObjectName) i.next();
+ getServer().invoke(objectName,
+ "addInvocationHandler",
+ new Object[] {SUBSYSTEM,
+ new DTMInvocationHandler(dtmServant)},
+ addInvocationHandlerSignature);
+ getLog().debug("Added DTM invocation handler to connector " +
+ objectName);
+
+ // Get the connector's invoker locator and locator URI
+ InvokerLocator locator =
+ (InvokerLocator) getServer().getAttribute(objectName, "Locator");
+ locatorList.add(locator);
+ String locatorURI = locator.getLocatorURI();
+ locatorURIList.add(locatorURI);
+ }
+ locators = (InvokerLocator[]) locatorList.toArray(new InvokerLocator[0]);
+ locatorURIs = (String[]) locatorURIList.toArray(new String[0]);
+
+ // Set the TxManager's DTM CordinatorFactory and ResourceFactory.
+ TransactionManager tm = TMUtil.getTransactionManager();
+
+ if (tm instanceof TxManager)
+ {
+ TxManager txManager = (TxManager)tm;
+ txManager.setDTMEnabled(true);
+ txManager.setDTMCoordinatorFactory(dtmServant);
+ txManager.setDTMResourceFactory(dtmServant);
+ txManager.setDTMStringRemoteRefConverter(dtmServant);
+ txManager.setInterpositionEnabled(interpositionEnabled);
+ }
+
+ // Bind the DTM TransactionFactory proxy into JNDI
+ Context ctx = new InitialContext();
+ TransactionFactory transactionFactory =
+ (TransactionFactory) RemoteProxy.create(TransactionFactory.class,
+ 0,
+ locators);
+ ctx.bind(ClientUserTransaction.TX_FACTORY_JNDI_NAME, transactionFactory);
+
+ // Bind the UserTransaction reference into JNDI
+ ctx.bind(USER_TRANSACTION_JNDI_NAME,
+ ClientUserTransaction.getSingleton());
+ }
+
+ protected void stopService()
+ throws Exception
+ {
+ // Unset the TxManager's DTM ResourceFactory.
+ TxManager tm = (TxManager)TMUtil.getTransactionManager();
+ tm.setDTMResourceFactory(null);
+
+ Iterator i = connectors.iterator();
+
+ while (i.hasNext())
+ {
+ // Remove DTM invocation handler from connector
+ ObjectName objectName = (ObjectName) i.next();
+ getServer().invoke(objectName,
+ "removeInvocationHandler",
+ removeInvocationHandlerParams,
+ removeInvocationHandlerSignature);
+ getLog().debug("Removed DTM invocation handler from connector " +
+ objectName);
+ }
+
+ // Unset the TxManager's DTM CordinatorFactory and ResourceFactory.
+ tm.setDTMEnabled(false);
+ tm.setDTMCoordinatorFactory(null);
+ tm.setDTMResourceFactory(null);
+
+ // Unbind the DTM TransactionFactory proxy
+ // and the UserTransaction reference from JNDI
+ Context ctx = new InitialContext();
+ ctx.unbind(ClientUserTransaction.TX_FACTORY_JNDI_NAME);
+ ctx.unbind(USER_TRANSACTION_JNDI_NAME);
+ }
+
+ // DistributedTransactionManagerMBean methods --------------------
+
+ public List getConnectors()
+ {
+ return connectors;
+ }
+
+ public void setConnectors(List connectors)
+ {
+ this.connectors = connectors;
+ }
+
+ public InvokerLocator[] getLocators()
+ {
+ return locators;
+ }
+
+ public String[] getLocatorURIs()
+ {
+ return locatorURIs;
+ }
+
+ public boolean getInterpositionEnabled()
+ {
+ return interpositionEnabled;
+ }
+
+ public void setInterpositionEnabled(boolean newValue)
+ {
+ interpositionEnabled = newValue;
+ if (getState() == STARTED)
+ {
+ TxManager tm = (TxManager)TMUtil.getTransactionManager();
+ tm.setInterpositionEnabled(newValue);
+ }
+ }
+
+}
Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java 2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm.remoting.server;
+
+import java.util.List;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface exposed by the DTM.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface DistributedTransactionManagerMBean
+ extends ServiceMBean
+{
+ List getConnectors();
+
+ void setConnectors(List connectors);
+
+ InvokerLocator[] getLocators();
+
+ String[] getLocatorURIs();
+
+ boolean getInterpositionEnabled();
+
+ void setInterpositionEnabled(boolean newValue);
+}
More information about the jboss-cvs-commits
mailing list